hibernate mysql clob_Hibernate实现Clob和Blob对象的存取

本文详细介绍了在Oracle数据库中使用Hibernate操作Blob和Clob字段的过程,包括创建、读取和更新操作。在保存操作时,由于OracleJDBC的限制,需要特别处理批处理设置。在读取时,通过Session的方法组合实现数据获取。此外,通过自定义数据类型StringClobType,实现了对OracleClob字段的抽象和简化操作,避免了直接依赖OracleJDBC原生类,降低了代码的耦合度。

作为测试,我们新建一个TUser对象,其image属性中,保存了一个图片文件的二进制内容。而其resume属性,我们以一个简单的字符串作为填充。

1

a4c26d1e5885305701be709a3d33442f.png

TUser user

= new TUser();2

a4c26d1e5885305701be709a3d33442f.png 3

a4c26d1e5885305701be709a3d33442f.pnguser.setAge(

new Integer( 20 ));4 a4c26d1e5885305701be709a3d33442f.png 5

a4c26d1e5885305701be709a3d33442f.pnguser.setName(

" Shark "

);6 a4c26d1e5885305701be709a3d33442f.png 7

a4c26d1e5885305701be709a3d33442f.pngFilelnputStream imgis

= new FileinputStream(

" C:\\inimage.jpg " 8 a4c26d1e5885305701be709a3d33442f.png 9

a4c26d1e5885305701be709a3d33442f.pngBlob img

= Hibernate.createBlob(imgis);10 a4c26d1e5885305701be709a3d33442f.png 11 a4c26d1e5885305701be709a3d33442f.pnguser.setlmage(img);12 a4c26d1e5885305701be709a3d33442f.png 13 a4c26d1e5885305701be709a3d33442f.pngClob resume

= Hibernate.createClob(

" This is Clob

" );14 a4c26d1e5885305701be709a3d33442f.png 15 a4c26d1e5885305701be709a3d33442f.pnguser. setResume(resume);16 a4c26d1e5885305701be709a3d33442f.png 17 a4c26d1e5885305701be709a3d33442f.pngTransaction tx

= session.beginTransaction();18 a4c26d1e5885305701be709a3d33442f.png 19 a4c26d1e5885305701be709a3d33442f.pngsession.save(user);20 21 a4c26d1e5885305701be709a3d33442f.pngtx.commit();22 a4c26d1e5885305701be709a3d33442f.png 23 a4c26d1e5885305701be709a3d33442f.png

上面的代码中,我们通过Hibemate.createBlob和Hibemate.createClob创建了对应的Blob和Clob对象。其中Blob对象基于一个FileInputStream构建,而Clob对象基于一个字符串构建。

完成了写入操作,对应的读取操作代码如下:

1

a4c26d1e5885305701be709a3d33442f.png

// 假设库表记录的id字段等于3

2 a4c26d1e5885305701be709a3d33442f.png

TUser user

= (TUser) session.load(TUger.elaa., load(TUser.

class ,

new Integer( 3 ));3 a4c26d1e5885305701be709a3d33442f.pngClob resume

= user.getResume();4

a4c26d1e5885305701be709a3d33442f.png

// 通过Clob.getSubString()方法获取Clob字段内容

5 a4c26d1e5885305701be709a3d33442f.png

System.out.println(

" User resume=>

" + resume.getSubString( 1 ,(

int )resume.length()));6

a4c26d1e5885305701be709a3d33442f.pngBlob img

= user.getImage();7

a4c26d1e5885305701be709a3d33442f.png

// 通过Blob.getBinaryS=ream()方法获取二进制流

8 a4c26d1e5885305701be709a3d33442f.png

InputStream is

= img.getBinaryStream();9

a4c26d1e5885305701be709a3d33442f.pngFileOutputStream fos

= new FileOutputStream(

" C:\\outimage.jpg " );10 a4c26d1e5885305701be709a3d33442f.png

byte [] buf = new

byte (

102400 );11 a4c26d1e5885305701be709a3d33442f.png

int len;12

a4c26d1e5885305701be709a3d33442f.png

a4c26d1e5885305701be709a3d33442f.png while ((len = is.read(buf))

!=- 1 )

a4c26d1e5885305701be709a3d33442f.png {13 a4c26d1e5885305701be709a3d33442f.png fos.write(buf,

0 ,len);14 a4c26d1e5885305701be709a3d33442f.png

} 15 a4c26d1e5885305701be709a3d33442f.pngfos.close();16 a4c26d1e5885305701be709a3d33442f.pngis.close();17 a4c26d1e5885305701be709a3d33442f.png 18 a4c26d1e5885305701be709a3d33442f.png

通过上面的代码,我们完成了针对SQLServer的Blob/Clob型字段操作.看起来非常简单,不过,并非侮种数据库都如此友善。让我们接着来看看Oracle数据库下的Blob/Clob字段读写,

通过修改hibernate.cfg.xml中的Dialect配置完成数据库切换后,我们再次运行上面的TUser对象保存例程。

程序运行期间抛出异常:

Hibernate:select hibernate_sequence.nextval

from dual

Hibernate:insert into T_USER (name,

age, image,resume. id) values(?, ?, ?, ?,

?)

17:27:24,161 ERROR JDBCExceptionReporter:58 -

- 不允许的操作:

Streams type cannot be used in batching

17:27:24,171 ERROR Sessionlmpl:2399 - Could

not synchronize database state with session

net.sf.hibernate.exception.GenericJDBCException:could not

insert:[com.redsaga.hibernate.db.entity.TUser#6]

...

观察异常信息:streams type cannot be used in

batching.这意味着Oracle

JDBC不允许流操作以批量方式执行(Oracle

CLOB采用流机制作为数据读写方式)。

这种错误一般发生在hibernate.cfg.xml中的hibernate jdbc.batch_size设定大于0的情况,将hibernate.jdbc.batch_size修改为0即可消除。

...

preStmt.setint(l,5);

ResultSet

rset=preStmt.executeQuery();

// 注意我们这里需要引用Oracle原生BLOB定义,如果使用了Weblogic JDBC Vendor

// 则应使用weblogic.jdbc.vendor.oracle.

OracleThinBLob/OracleThinCLOb

rset.next();

oracle.sql.BLOB imqBlob = (oracle.sql.BLOB)

rset.getBlob(1);

oracle.sql.CLOB resClob = (oracle.sql.CLOB)

rset.getClob(2);

//======= 将二进创数据写入Blob

FileInputStream inStream = new

FileinputStream("c\\inimage.jpg");

OutputStream outStream =

imgBlob.getBinaryOutputStream();

byte[] buf=new byte[10240];//10K

读取缓存

int len;

while((len=inStream.read(buf))>0){

outStream.write(buf,0,len);

}

inStream.close();

outStream.close():

//======= 将字符串写入Clob

resClob.putString(1 ,"This is my Glob"

//======= 将Blob/Clob字段更新到数据序

preStmt= dbconn.prepareStatement("update

T_USER set image=?, resume=?

where id=?");

preStmt.setBlob(1,imgBlob);

preStmt.setClob(2,resClob):

preStmt.setlnt(3 ,5);

preStmt.executeUpdate();

preStmt.close():

dbconn.commit();

dbconn.close():

上面的代码说明了Oracle中Blob/Clob字段操作的一般机制,那么,基于Hibernate的持久层实现中,应该如何对Blob/Clob字段进行处理?

我们知道,Hibernate底层数据访问机制仍然是基于JDBC实现,那么也就意味着我们必须在Hibernate中模拟JDBC的访问流程:

TUser user=new TUser();

user.setAge(new

Integer(20));

user.setName("Shark');

user.setImage(Hibernate.createSlob(new byte

[1])):

user.setResume(Hibernate.createClob("

"));// 注意这里的参教是一个空格

Transaction

tx=session.beginTransaction();

session.save(user):

// 调用flush方法,强制Hibernate立即执行insert sql

session.flush();

// 通过refresh方法,强制Hibernate执行select for update

session.refresh(user,

LockMode.UPGRADE);

// 向Blob写入实际内容

oracle.sql.BLOB

blob=(oracle.sql.BLOB)user.getImage();

OutputStream out=blob.

getBinaryOutputStream();

FileInputStream imgis=new

FileInputStream("C:\\inimage.jpg");

byte[] buf=new byte[10240];//10K

缓存

int len;

while((len=imgis.read(buf))>0){

out.write(buf,0,len);

}

imgis.close();

out.close();

// 向Clob写入实际内容

oracle.sql.CLOB

clob=(oracle.sgl.CLOB)

user.getResume();

java.io.Writer writer =

clob.getCharacterOutputStream();

writer.write("this is my

resume');

writer.close();

session.save(user);

tx.commit();

实际应用中,对于Clob字段,我们可以简单地将其映射为String类型,不过在这种情况下需要注意,Oracle Thin Driver对Clob字段支持尚有欠缺,当Clob内容超出4000字节时将无法读取,而Oracle OCI Driver(需要在本地安装Oracle客户端组件)则可以成功地完成大容量Clob字段的操作。

上面的代码中,我们通过Session.save/flush/refresh方法的组合使用,实现了上面JDBC代码中的Blob/Clob访问逻辑。

Blob/Clob 字段的Hibernate保存实现如上所述,相对来讲,读取则没有太多的障碍,之前的读取代码依然可以正常运行。

对于上面的实现,相信大家都感觉到了一些Bad

Smell,如果Blob/Clob字段普遍存在,那么我们的持久层逻辑中可能遍布如此复杂的数据存储逻辑、并与数据库原生类紧密祸

如何解决这些问题?

回忆之前关于自定义数据类型的讨论。通过自定义数据类型我们可以对数据的通用特征进行抽象,那么,对于Oracle的Blob/Clob字段,我们是否可以也对其进行抽象,并以其作为所有Oracle Blob/Clob字段的映射类型?

下面的StringClobType实现了这一目标:

public class StringClobType implements

UserType{

private static final String ORACLE_DRIVER_NAME="Oracle JDBC

driver";

private static final int

ORACLE_DRIVER_MAJOR_VERSION=9;

private static final int

ORACLE_DRIVER_MINOR_VERSION=0;

public int[] sqlTypes(){

return new int[] {Types.CLOB};

}

public Class returnedClass{

return String.class;

}

public boolean equals(Object x, object y){

return org.apache.commons.lang.ObjectUtils.equals(x,

y);

}

public Object nullSafeGet(ResultSet rs, String[] names, Object

owner)

throws HibernateException,SQLException{

Clob clob=rs.getClob(names(O]);

return(clob==null ? null:clob.getSubString(l, (int)

clob.length())):

}

public void nullSafeSet(PreparedStatement st ,Object value, int index)

throws HibernateException, SQLException{

DatabaseMetaData

dbMetaData=st.getConnection().getMetaData();

if (value==null)

st.setNull(index,

sqiTypes()(0));

else

//

本实现仅仅适用于Oracle数据序9.0以上版本

if

(ORACLE_DRIVER_NAME.equals(dbMetaData.getDriverName(

,))(

if((dbMetaData.getDriverMajorVersion()

>=ORACLE-DRIVER-MAJOR-VERSION)

&&(dbMetaData.getDriverMinorVersion()

>=ORACLE-DRIVER-MINOR-VERSION)) {

try {

//

通过动态加载方式进免编译期对Oracle JDBC的依赖

Class

oracleClobClass=Class.forName('oracle.sgl.CLOB");

//

动态调用createTemporary方法

Class partypes[]=new Class[3];

partypes[0]=Connection.class;

partypes[1]=Boolean.TYPE;

partypes(2]=Integer.TYPE;

Method createTemporaryMethod=

oracleClobClass.getDeclaredMethod(

"createTemporaxy “,

partypes);

Field durationSessionField=

oracleClobClass.getField("DURATION-SESSION");

Object arglist[]=new 0bject[3]:

Connection conn=

st.getConnection().getMetaData().getConnection();

//

数据库连接类型必须为OracleConnection

//

莱些应用服务器会使用自带Oracle JDBC Wrapper,如Weblogic

//

这里需要特别注意

Class oracleConnectionClass=

Class.forName("oracle.jdbc.OracleConnection");

if(!oracleConnectionClass

.isAssignableFrom(conn.getClass())){

throw new HibernateException(

"Must be a oracle.jdbc.OracleConnection:.

+conn.getClass().getName());

}

arglist[0] = conn;

arglist(1] = Boolean.TRUE;

arolist[2] = durationSessionField.get(null);

Object tempClob

=createTemporaryMethod.invoke(null,arglist);

partypes=new Class[l];

partypes[0]=Integer.TYPE;

Method openMethod

=oracleClobClass.getDeclaredMethod("open",partypes);

Field modeReadWriteField

=oracleClobClass.getField("MODE_READWRITE");

arglist = new Object(l];

arglis[0] = modeReadWriteField.get(null);

openMethod.invoke(tempClob, arglist);

Method

getCharacterOutputStreamMethod=oracleClobClass.getDeclaredMethod("getCharacterOutputStream',null)

;

//

call the getCharacterOutpitStreammethod

Writer tempClobWriter

=(Writer)getCharacterOutputStreamMethod.invoke(tempClob,null);

//

将参数写入Clob

tempClobwriter.write((String) value);

tempClobWriter.flush();

tempClobWriter.Close();

//

close

clob

Method closeMethod=oracleClobClass.getDeclaredMethod("close",

null);

closeMethod.invoke(tempClob, null);

st.setClob(index, (Clob)

tempClob);

)catch

(ClassNotFoundException e){

throw new HibernateException("Unable to find a required

class.\n"+e.getMessage()):

}catch (NOSuchMethodException e){

throw new HibernateException("Unable to find a required

method.\n"+e.getMessage()):

}catch (NoSuchFieldException e){

throw new HibernateException("Unable to find a required

field.\n"+e.getMessage());

}catch (IllegalAccessException e){

throw new HibernateException("Unable to access a required method or

field.\n"+e.getMessage());

catch (InvocationTargetException e){

throw new HibernateException(e.getMessage());

{ catch

(IOException e){

throw new HibernateException(e.getMessage());

}

else {

throw new HibernateException(

"No CLOBS support.Use driver version"

+ORACLE_DRIVER_MAJOR_VERSION

+" ,minor"

+ORACLE_DRIVER_MINOR_VERSION);

}

}else {

String str = (String)

value;

StrinaReader r = new StringReader(str);

St.setCharacterStream(index, r, str.length());

}

}

public Object deepCopy(Object value){

if(value==null)

return null;

return new String((String)

value);

}

public boolean isMutable(){

return false

}

}

上面这段代码,重点在于nullSafeSet方法的实现,nullSafeSet中通过Java Reflection机制,解除了编译期的Oralce JDBC原生类依赖。同时,借助Oracle JDBC提供的原生功能完成了Clob字段的写入,Clob字段的写入操作由于涉及特定数据库内部实现细节,这里就不多费唇舌,大家可参见Oracle JDBC Java

Doc.

这段代码是由笔者根据Ali Ibrahim,

Scott Miller的代码修改而来的(原版请参见httpJ/www.hibemate, org /56.html ),支持Oracle 9以上版本,Oracle 8对应的实现请参见上述网址。

同样的道理,读者可以根据以上例程,编写自己的ByteBlobType以实现byte[]到Blob的映射。

另外,此代码必须运行在最新版的Oracle JDBC

Driver上(笔者所用版本为Oracle9i9.2.0.5 for JDK1.4,如果使用9.2.0.3或之前版本则在新建l更却删除数据时可能会遇到“nomore data read from socket”错误)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值