此方法用于在现有 Select 语句基础上,根据各数据库自身特性,构造对应的记录返 回限定子句。如 MySQL 中对应的记录限定子句为 Limit,而 Oracle 中,可通过 rownum 子句实现。
我们来看 MySQLDialect 中的 getLimitString 实现:
public String getLimitString(String sql, boolean hasOffset) { return new StringBuffer( sql.length()+20 )
.append(sql)
下面是 Oracle9Dialect 中的 getLimitString 实现,其中通过 Oracle 特有的 rownum 子句实现了数据的部分读取。
public String getLimitString(String sql, boolean hasOffset)
{StringBuffer pagingSelect =
new StringBuffer( sql.length()+100 );
if (hasOffset) {
pagingSelect.append(
"select * from ( select row_.*, rownum rownum_ from ( "
);
}else {
pagingSelect.append("select * from ( ");
}
pagingSelect.append(" ) where rownum <= ?");
}
return pagingSelect.toString();
}
大多数主流数据库都提供了数据部分读取机制,而对于某些没有提供相应机制的数据 库而言,Hibernate 也通过其他途径实现了分页,如通过 Scrollable ResultSet,如 果 JDBC 不支持 Scrollable ResultSet,Hibernate 也会自动通过 ResultSet 的 next 方法进行记录定位。这样,Hibernate 通过底层对分页机制的良好封装,使得开发 人员无需关心数据分页的细节实现,将数据逻辑和存储逻辑分离开来,在提高生产效率的
同时,也大大加强了系统在不同数据库平台之间的可移植性。
Hibernate 中实现了良好的 Cache 机制,我们可以借助 Hibernate 内部的 Cache 迅速提高系统数据读取性能。
需要注意的是:Hibernate 做为一个应用级的数据访问层封装,只能在其作用范围内 保持 Cache 中数据的的有效性,也就是说,在我们的系统与第三方系统共享数据库的情况 下,Hibernate 的 Cache 机制可能失效。
Hibernate 在本地 JVM 中维护了一个缓冲池,并将从数据库获得的数据保存到池中 以供下次重复使用(如果在 Hibernate 中数据发生了变动,Hibernate 同样也会更新池 中的数据版本)。
此时,如果有第三方系统对数据库进行了更改,那么,Hibernate 并不知道数据库中 的数据已经发生了变化,也就是说,池中的数据还是修改之前的版本,下次读取时,
Hibernate 会将此数据返回给上层代码,从而导致潜在的问题。
外部系统的定义,并非限于本系统之外的第三方系统,即使在本系统中,如果出现了 绕过 Hibernate 数据存储机制的其他数据存取手段,那么 Cache 的有效性也必须细加考 量。如,在同一套系统中,基于 Hibernate 和基于 JDBC 的两种数据访问方式并存,那么 通过 JDBC 更新数据库的时候,Hibernate 同样无法获知数据更新的情况,从而导致脏数 据的出现。
基于 Java 的 Cache 实现,最简单的莫过于 HashTable,hibernate 提供了基于 Hashtable 的 Cache 实现机制,不过,由于其性能和功能上的局限,仅供开发调试中使 用。同时,Hibernate 还提供了面向第三方 Cache 实现的接口,如 JCS、EHCache、
OSCache、JBoss Cache、SwarmCache 等。
Hibernate 中的 Cache 大致分为两层,第一层 Cache 在 Session 实现,属于事务 级数据缓冲,一旦事务结束,这个 Cache 也就失效。此层 Cache 为内置实现,无需我们
2 DOS 下的磁盘读写缓冲程序
进行干涉。
第二层 Cache,是 Hibernate 中对其实例范围内的数据进行缓存的管理容器。也是 这里我们讨论的主题。
Hibernate 早期版本中采用了 JCS(Java Caching System -Apache Turbine 项目中的一个子项目)作为默认的第二层 Cache 实现。由于 JCS 的发展停顿,以及其内在 的一些问题(在某些情况下,可能导致内存泄漏以及死锁),新版本的 Hibernate 已经将 JCS 去除,并用 EHCache 作为其默认的第二级 Cache 实现。
相对 JCS,EHCache 更加稳定,并具备更好的缓存调度性能,缺陷是目前还无法做到 分布式缓存,如果我们的系统需要在多台设备上部署,并共享同一个数据库,必须使用支 持分布式缓存的 Cache 实现(如 JCS、JBossCache)以避免出现不同系统实例之间缓存 不一致而导致脏数据的情况。
Hibernate 对 Cache 进行了良好封装,透明化的 Cache 机制使得我们在上层结构的 实现中无需面对繁琐的 Cache 维护细节。
目前 Hibernate 支持的 Cache 实现有:
名称 类 集群支持 查询缓冲
HashTable net.sf.hibernate.cache.HashtableCacheP rovider
N Y
EHCache
net.sf.ehcache.hibernate.Provider N YOSCache
net.sf.hibernate.cache.OSCacheProvider N YSwarmCache
net.sf.hibernate.cache.SwarmCacheProvider
Y
JBossCache
net.sf.hibernate.cache.TreeCacheProvid erY
其中 SwarmCache 和 JBossCache 均提供了分布式缓存实现(Cache 集群)。
(注:最新版本的 OSCache 也提供了分布式实现。)
其中 SwarmCache 提供的是 invalidation 方式的分布式缓存,即当集群中的某个 节点更新了缓存中的数据,即通知集群中的其他节点将此数据废除,之后各个节点需要用 到这个数据的时候,会重新从数据库中读入并填充到缓存中。
而 JBossCache 提供的是 Reapplication 式的缓冲,即如果集群中某个节点的数据 发生改变,此节点会将发生改变的数据的最新版本复制到集群中的每个节点中以保持所有 节点状态一致。
使用第二层 Cache,需要在 hibernate.cfg.xml 配置以下参数(以 EHCache 为例):
<hibernate-configuration>
<session-factory>
……
<property name="hibernate.cache.provider_class">
net.sf.ehcache.hibernate.Provider
</property>
……
</session-factory>
</hibernate-configuration>
另外还需要针对 Cache 实现本身进行配置,如 EHCache 的配置文件:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
<class name=" org.hibernate.sample.TUser" .... >
<cache usage="read-write"/>
....
“read-write”,即缓冲中的 TUser 实例为可读可写,而集合属性 addresses 的缓存 策略为只读。
3. nonstrict-read-write
如果程序对并发数据修改要求不是非常严格,只是偶尔需要更新数据,可以采用 本选项,以减少无谓的检查,获得较好的性能。
4. transactional
事务性 cache。在事务性 Cache 中,Cache 的相关操作也被添加到事务之中,
如果由于某种原因导致事务失败,我们可以连同缓冲池中的数据一同回滚到事务 开始之前的状态。目前 Hibernate 内置的 Cache 中,只有 JBossCache 支持
事务性的 Cache 实现。
不同的 Cache 实现,支持的 usage 也各不相同:
名称 read-only read-write nonstrict-read-write transactional
HashTable Y Y Y
EHCache Y Y Y
OSCache
Y Y YSwarmCache
Y YJBossCache
Y Y配置好 Cache 之后,Hibernate 在运行期会自动应用 Cache 机制,也就是说,我们 对 PO 的更新,会自动同步到 Cache 中去,而数据的读取,也会自动化的优先从 Cache 中 获取,对于上层逻辑代码而言,有没有应用 Cache 机制,并没有什么影响。
需要注意的是 Hibernate 的数据库查询机制。我们从查询结果中取出数据的时候,
用的最多的是两个方法:
Query.list();
Query.iterate();
对于 list 方法而言,实际上 Hibernate 是通过一条 Select SQL 获取所有的记录。
并将其读出,填入到 POJO 中返回。
而 iterate 方法,则是首先通过一条 Select SQL 获取所有符合查询条件的记录的 id,再对这个 id 集合进行循环操作,通过单独的 Select SQL 取出每个 id 所对应的记 录,之后填入 POJO 中返回。
也就是说,对于 list 操作,需要一条 SQL 完成。而对于 iterate 操作,需要 n+1 条 SQL。
看上去 iterate 方法似乎有些多余,但在不同的情况下确依然有其独特的功效,如对 海量数据的查询,如果用 list 方法将结果集一次取出,内存的开销可能无法承受。
另一方面,对于我们现在的 Cache 机制而言,list 方法将不会从 Cache 中读取数据,
它总是一次性从数据库中直接读出所有符合条件的记录。而 iterate 方法因为每次根据 id 获取数据,这样的实现机制也就为从 Cache 读取数据提供了可能,hibernate 首先会 根据这个 id 在本地 Cache 内寻找对应的数据,如果没找到,再去数据库中检索。如果系 统设计中对 Cache 比较倚重,则请注意编码中这两种不同方法的应用组合,有针对性的改 善代码,最大程度提升系统的整体性能表现。
通观以上内容,Hibernate 通过对 Cache 的封装,对上层逻辑层而言,实现了 Cache
的透明化实现,程序员编码时无需关心数据在 Cache 中的状态和调度,从而最大化协调了 性能和开发效率之间的平衡。
Session 管理
无疑,Session 是 Hibernate 运作的灵魂,作为贯穿 Hibernate 应用的关键,Session 中包含了数据库操作相关的状态信息。如对 JDBC Connection 的维护,数据实体的状态维持 等。
对 Session 进行有效管理的意义,类似 JDBC 程序设计中对于 JDBC Connection 的调 度管理。有效的 Session 管理机制,是 Hibernate 应用设计的关键。
大多数情况下,Session 管理的目标聚焦于通过合理的设计,避免 Session 的频繁创建 和销毁,从而避免大量的内存开销和频繁的 JVM 垃圾回收,保证系统高效平滑运行。
在各种 Session 管理方案中, ThreadLocal 模式得到了大量使用。ThreadLocal 是 Java 中一种较为特殊的线程绑定机制。通过 ThreadLocal 存取的数据,总是与当前线程相关,
也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出 现的并发访问问题提供了一种隔离机制。
首先,我们需要知道,SessionFactory 负责创建 Session,SessionFactory 是线程 安全的,多个并发线程可以同时访问一个 SessionFactory 并从中获取 Session 实例。而 Session 并非线程安全,也就是说,如果多个线程同时使用一个 Session 实例进行数据存取,
则将会导致 Session 数据存取逻辑混乱。下面是一个典型的 Servlet,我们试图通过一个类 变量 session 实现 Session 的重用,以避免每次操作都要重新创建: