Hibernate實(shí)現(xiàn)Clob和Blob對(duì)象的存取
Posted on 2006-12-07 10:27 sxt 閱讀(12452) 評(píng)論(8) 編輯 收藏作為測(cè)試,我們新建一個(gè)TUser對(duì)象,其image屬性中,保存了一個(gè)圖片文件的二進(jìn)制內(nèi)容。而其resume屬性,我們以一個(gè)簡(jiǎn)單的字符串作為填充。

?2

?3

?4

?5

?6

?7

?8

?9

10

11

12

13

14

15

16

17

18

19

20
21

22

23

上面的代碼中,我們通過(guò)Hibemate.createBlob和Hibemate.createClob創(chuàng)建了對(duì)應(yīng)的Blob和Clob對(duì)象。其中Blob對(duì)象基于一個(gè)FileInputStream構(gòu)建,而Clob對(duì)象基于一個(gè)字符串構(gòu)建。
完成了寫入操作,對(duì)應(yīng)的讀取操作代碼如下:

?2

?3

?4

?5

?6

?7

?8

?9

10

11

12



13

14

15

16

17

18

?
通過(guò)上面的代碼,我們完成了針對(duì)SQLServer的Blob/Clob型字段操作.看起來(lái)非常簡(jiǎn)單,不過(guò),并非侮種數(shù)據(jù)庫(kù)都如此友善。讓我們接著來(lái)看看Oracle數(shù)據(jù)庫(kù)下的Blob/Clob字段讀寫,
通過(guò)修改hibernate.cfg.xml中的Dialect配置完成數(shù)據(jù)庫(kù)切換后,我們?cè)俅芜\(yùn)行上面的TUser對(duì)象保存例程。
程序運(yùn)行期間拋出異常:
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不允許流操作以批量方式執(zhí)行(Oracle CLOB采用流機(jī)制作為數(shù)據(jù)讀寫方式)。
這種錯(cuò)誤一般發(fā)生在hibernate.cfg.xml中的hibernate jdbc.batch_size設(shè)定大于0的情況,將hibernate.jdbc.batch_size修改為0即可消除。
<hibernate-configuration>
<session-factory>
???????????? ...
???????????? <property name='hibernate. jdbc.batch_size">0</property>
???????????? ...
</session-factory>
</hibernate-configuration>
再次嘗試啟動(dòng)代碼,發(fā)現(xiàn)依然無(wú)法成功運(yùn)行,拋出異常如下:
Hibernate: select hibernate_sequence.nextval from dual
Hibernate: insert into T--USER? (name,? age,? image,resume,id) values(?,?,?,?,?)
19:02:21,054 ERROR JDBCExceptionReporter:58
一IO異常:Connection reset bypeer: socket write error
19:02:21,054 ERROR JDBCExceptionReporter:58
一I。異常:Connection reset by peer:socket write error
19:02:21
。064 ERROR JDBCExceptionReporter:58一Io異常:Connection reset by peer: socket wr主to error
19:02:21,064 ERROR Sessionlrnpl:2399
一Could not synchronize database state with session
net.sf.hibernate.exception.GenericJDSCException:? could not insert:[com.redsaga.hibernate.db.entity.TUser#27]
...
為什么會(huì)出現(xiàn)這樣的情況?
問(wèn)題出在Oracce中Blob/Clob字段獨(dú)特的訪問(wèn)方式,Oracle Blob/Clob字段本身?yè)碛幸粋€(gè)游標(biāo)(cursor) , JDBC必須通過(guò)游標(biāo)對(duì)Blob/ Clob字段進(jìn)行操作,在Blob/Clob字段被創(chuàng)建之前,我們無(wú)法獲取其游標(biāo)句柄,這也就意味著,我們必須首先創(chuàng)建一個(gè)空Blob/Clob字段,再?gòu)倪@個(gè)空Blob/Clob字段獲取游標(biāo),寫入我們所期望保存的數(shù)據(jù)。
如果用JDBC代碼來(lái)表示這個(gè)過(guò)程,則得到如下代碼:
//...
獲取JDBC連接
dbconn.setAutoCommit(falee);
//
=======插入數(shù)據(jù),BLOB CLOB字段插入空值
PreparedStatenent preStmt=
dbconn.prepareStatement(
??? "insert into T_USER (name, age,? id,image,resume) values
???? (?,?,?,?,?)");
preStmt.setString(1,"Shark");
preStmt.setInt(2,20);
preStmt.setInt(3,5);?
//
通過(guò)oracle.sgl.BLOB/CLOB.empty_lob()方法構(gòu)造空Blob/Clob對(duì)象
preStmt.setBlob(4
,oracle.sgl.BLOB.empty_lob());
preStmt.setClob(5,oracle.sgl.CLOB.empty_lob());
preStmt.executeUpdate();
preStmt.close():
//==========
再次從庫(kù)表讀出,獲得Blob/Clob句柄
preStmt=
? dbconn.prepareStatement(
????? "select? image,resume from T_USER where id=?for update');
preStmt.setint(l,5);
ResultSet rset=preStmt.executeQuery();
//
注意我們這里需要引用Oracle原生BLOB定義,如果使用了Weblogic JDBC Vendor
//
則應(yīng)使用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);
//=======
將二進(jìn)創(chuàng)數(shù)據(jù)寫入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字段更新到數(shù)據(jù)序
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():
上面的代碼說(shuō)明了Oracle中Blob/Clob字段操作的一般機(jī)制,那么,基于Hibernate的持久層實(shí)現(xiàn)中,應(yīng)該如何對(duì)Blob/Clob字段進(jìn)行處理?
我們知道,Hibernate底層數(shù)據(jù)訪問(wèn)機(jī)制仍然是基于JDBC實(shí)現(xiàn),那么也就意味著我們必須在Hibernate中模擬JDBC的訪問(wèn)流程:
TUser user=new TUser();
user.setAge(new Integer(20));
user.setName("Shark');
user.setImage(Hibernate.createSlob(new byte [1])):
user.setResume(Hibernate.createClob(" "));//
注意這里的參教是一個(gè)空格
Transaction tx=session.beginTransaction();
session.save(user):
//
調(diào)用flush方法,強(qiáng)制Hibernate立即執(zhí)行insert sql
session.flush();
//
通過(guò)refresh方法,強(qiáng)制Hibernate執(zhí)行select for update
session.refresh(user, LockMode.UPGRADE);
//
向Blob寫入實(shí)際內(nèi)容
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寫入實(shí)際內(nèi)容
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();
實(shí)際應(yīng)用中,對(duì)于Clob字段,我們可以簡(jiǎn)單地將其映射為String類型,不過(guò)在這種情況下需要注意,Oracle Thin Driver對(duì)Clob字段支持尚有欠缺,當(dāng)Clob內(nèi)容超出4000字節(jié)時(shí)將無(wú)法讀取,而Oracle OCI Driver(需要在本地安裝Oracle客戶端組件)則可以成功地完成大容量Clob字段的操作。
上面的代碼中,我們通過(guò)Session.save/flush/refresh方法的組合使用,實(shí)現(xiàn)了上面JDBC代碼中的Blob/Clob訪問(wèn)邏輯。
Blob/Clob
字段的Hibernate保存實(shí)現(xiàn)如上所述,相對(duì)來(lái)講,讀取則沒(méi)有太多的障礙,之前的讀取代碼依然可以正常運(yùn)行。
對(duì)于上面的實(shí)現(xiàn),相信大家都感覺(jué)到了一些Bad Smell,如果Blob/Clob字段普遍存在,那么我們的持久層邏輯中可能遍布如此復(fù)雜的數(shù)據(jù)存儲(chǔ)邏輯、并與數(shù)據(jù)庫(kù)原生類緊密禍
如何解決這些問(wèn)題?
回憶之前關(guān)于自定義數(shù)據(jù)類型的討論。通過(guò)自定義數(shù)據(jù)類型我們可以對(duì)數(shù)據(jù)的通用特征進(jìn)行抽象,那么,對(duì)于Oracle的Blob/Clob字段,我們是否可以也對(duì)其進(jìn)行抽象,并以其作為所有Oracle Blob/Clob字段的映射類型?
下面的StringClobType實(shí)現(xiàn)了這一目標(biāo):
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
//????????
本實(shí)現(xiàn)僅僅適用于Oracle數(shù)據(jù)序9.0以上版本
??????
??? if
?????????? (ORACLE_DRIVER_NAME.equals(dbMetaData.getDriverName(
,))(
????????????? if((dbMetaData.getDriverMajorVersion()?
?????????????????
???>=ORACLE-DRIVER-MAJOR-VERSION)
????????????????? &&(dbMetaData.getDriverMinorVersion()??
???????????????????? >=ORACLE-DRIVER-MINOR-VERSION))
{
???????????????????????? try
{
//???????????????
通過(guò)動(dòng)態(tài)加載方式進(jìn)免編譯期對(duì)Oracle JDBC的依賴
???????????????????????? Class oracleClobClass=Class.forName('oracle.sgl.CLOB");
//?????????????????????????????
動(dòng)態(tài)調(diào)用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();
//?????????????????????????????
數(shù)據(jù)庫(kù)連接類型必須為OracleConnection
//?????????????????????????????
萊些應(yīng)用服務(wù)器會(huì)使用自帶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 getCharacterOutpitStream method
?????????????????????????????????????? Writer tempClobWriter =(Writer)getCharacterOutputStreamMethod.invoke(tempClob,null);
//????????????????????????????????????
將參數(shù)寫入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
??? }
}
上面這段代碼,重點(diǎn)在于nullSafeSet方法的實(shí)現(xiàn),nullSafeSet中通過(guò)Java Reflection機(jī)制,解除了編譯期的Oralce JDBC原生類依賴。同時(shí),借助Oracle JDBC提供的原生功能完成了Clob字段的寫入,Clob字段的寫入操作由于涉及特定數(shù)據(jù)庫(kù)內(nèi)部實(shí)現(xiàn)細(xì)節(jié),這里就不多費(fèi)唇舌,大家可參見Oracle JDBC Java Doc.
這段代碼是由筆者根據(jù)Ali Ibrahim, Scott Miller的代碼修改而來(lái)的(原版請(qǐng)參見httpJ/www.hibemate, org /56.html ),支持Oracle 9以上版本,Oracle 8對(duì)應(yīng)的實(shí)現(xiàn)請(qǐng)參見上述網(wǎng)址。
同樣的道理,讀者可以根據(jù)以上例程,編寫自己的ByteBlobType以實(shí)現(xiàn)byte[]到Blob的映射。
另外,此代碼必須運(yùn)行在最新版的Oracle JDBC Driver上(筆者所用版本為Oracle9i9.2.0.5 for JDK1.4,如果使用9.2.0.3或之前版本則在新建l更卻刪除數(shù)據(jù)時(shí)可能會(huì)遇到“nomore data read from socket”錯(cuò)誤)。
?