在 Java 中, java.lang.String 可用于表示长字符串(长度超过 255 ),字节数组 byte[] 可以用于存放图片户或文件二进制数据。此外,在 JDBC API 中还提供了 java.sql.CLOB 和 java.sql.BLOB 类型,他们分别表示标准 SQL 中的 CLOB (字符大对象)和 BLOB (二进制大对象)类型。表 2.4 列出了 Java 大对象, Hibernate 映射类型以及标准 SQL 的对应关系。
表 2.4
映射类型 | Java 类型 | 标准 SQL 类型 |
binary | byte[] | VARBINARY( 或者 BLOB) |
text | java.lang.String | CLOB |
serilizable | 实现 java.io.Serializable 的任何一个 Java 类 | VARBINARY( 或者 BLOB) |
clob | java.sql.CLOB | CLOB |
blob | java.sql.BLOB | BLOB |
注意:不允许用表 2.4 中列出的数据类型来定义持久化类的 OID
( 2 )、 BLOB,CLOB 数据的处理(以 Oracle 数据库为例):
假设我们有如下表
T_User |
id number <pk> name varchar2(50) age number image BLOB resume CLOB |
对应的映射文件如下:
<hibernate-mapping>
<class name=”com.neusoft.hibernate.db.entity.TUser” table=”T_User”>
<id name=”id” column=”id” type=”java.lang.Integer”>
<generator class=”native”/>
</id>
<property name=”name” type=”java.lang.String” column=”name”/>
<property name=”age” type=”java.lang.Integer” column=”age”/>
<property name=”image” type=”java.sql.Blob” column=”image”/>
<property name=”resume” type=”java.sql.Clob” column=”resume”/>
</class>
</hibernate-mapping>
实体类如下:
public class Tuser implements Serializable{
private Integer id;
private String name;
private Integer age;
private Blob image;
private Clob resume;
…getter/setter…..
}
对 BLOB 和 CLOB 这种大对象一般都是采用流机制作为数据读取方式,所以这种读取方式在 Oracle 这种自视为数据库中贵族的数据库中对这种操作就会有诸多的限制,有时候会叫人觉得不太友好(这是典型的店大欺客)
限制一: Oracle JDBC 不允许流操作以批量方式执行,如果发生这种错误一般会抛出
ERROR JDBCExceptionReport:streams type cannot be used in batching. 出现这种情况一般都需要将 hibernate.cfg.xml 中的 hibernate.jdbc.batch_size 设定为 0 即可消除,但是这就会影响其他的更新,插入,删除操作的性能,因此必须在一个数据库事务中对 Clob,Blob 进行操作,也只有在一个数据库事务中 Clob,Blob 对象才会有效。
限制二: Oalce Blob/Clob 具有独特的访问方式,这种类型字段拥有一个游标 (cursor) , JDBC 必须通过游标对 Blob/Clob 进行操作,在 Blob/Clob 创建之前我门无法获得其游标句炳,这就意味着必须首先创建一个空 Blob/Clob 字段,在丛空 Blob/Clob 获取游标,然后写入我们期望的数据。
首先看一下采用传统 JDBC 进行操作的代码:
//…. 获取 Connection 连接
conn.setAtuoCommit(false);
// 插入 Blob/Clob 空值字段
PrepareStatement prestmt=conn.prepareStatement(“insert into T_USER(name,age,id,image,resume) values(?,?,?,?,?)”);
prestmt.setString(1,”zx”);
prestmt.setInt(2,26);
prestmt.setInt(3,5);
// 通过 oracle.sql.BLOB/CLOB.empty_lob() 方法构造空 Blob/Clob 对象
prestmt.setBlob(4,oracle.sql.BLOB.empty_lob());
prestmt.setClob(5,oracle.sql.CLOB.empty_lob());
prestmt.executeUpdate();
prestmt.close();
// 再次从数据库中获得 Blob/Clob 句炳
prestmt=conn.prepareStatement(“select image,resume from T_USER where id=? for update ”);
prestmt.setInt(1,5);
ResultSet rset=prestmt.executeQuery();
rset.next();
oracle.sql.BLOB imgBlob=(oracle.sql.BLOB)rset.getBlob();
oracle.sql.CLOB resClob=(oracle.sql.CLOB)rset.getClob();
// 将二进制数据写入 Blob
FlieInputStream fin=new FileInputStream(“c://image.jpg”);
OutputStream out=imgBlob.getBinaryOutputStream();
byte[] buf=new byte[fin.available()];
int len;
while((len=fin.read(buf))!=-1){ out.write(buf,0,len);
}
fin.close();
out.close();
// 将字符串写入 Clob
resClob.putString(1,”This is my clob”);
// 将更新写回数据库
prestmt=conn.prepareStatement(“update T_USER set image=?,resume=? where id=? ”);
prestmt.setBlob(1,imgBlob);
prestmt.setClob(2,resClob);
prestmt.setInt(3,5);
prestmt.executeUpdate();
prestmt.close();
conn.commit();
conn.close();
以上是传统的采用 JDBC 方式处理,注意他将连接的自动提交属性设置为 false 然后将所有的操作并入一个事务中,然后进行提交,这是处理 Oracle 中 Blob/Clob 字段的一般机制,所以 Hibernate 的处理就应该模仿 JDBC 的处理方式,因为从某种角度来讲 Hibernate 是对 JDBC 的封装因为它的底层访问机制仍然是基于 JDBC 的。
Hibernate 的处理:
TUser user=new TUser();
user.setAge(new Integer(26));
user.setName(“zx”);
// 创建空 Blob/Clob 对象
user.setImage(Hibernate.createBlob(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.getImger();
OutputStream out=blob.getBinaryOutputStream();
FileInputStream fin=new FileInputStream(“c://image.jpg”);
byte[] buf=new byte[fin.available()];
int len;
while((len=fin.read())!=-1){
out.write(buf,0,len);
}
fin.close();
out.close();
// 向 Clob 中写入数据
oracle.sql.CLOB clob=user.getResume();
java.io.Writer writer=clob.getCharacterOutputStream();
writer.write(“This is my resume!”);
writer.close();
session.saveOrUpdate(user);
session.commit();
tx.commit();
在实际应用中,对于 Clob 字段可以简单的将其映射为 String 类型,不过在 Oracle Thin Driver 对 Clob 字段支持上有欠缺,当 Clob 内容超过 4000 字节时将无法读取,而 Oracle OCI Driver( 需要在本地安装客户端组件 ) 则可以完成大容量 Clob 字段操作。
对于上面的代码相信作为成熟的工程师来说都闻到一些 bad smell, 如果 Blob/Clob 字段普遍存在的话,那么我们的持久层逻辑可能遍布这种复杂的逻辑,不过不要着急在我即将讲解的客户自定义类中我们将会看到一个解决方案,通过自定义类型我们可以对数据类型的通用性进行抽象,对于 Blob/Clob 字段我们可以定义一种类型并以这种类型作为 Blob/Clob 字段的映射类型。(好了等到下一篇再说吧!)