hibernate相关小知识点总结

因为项目要用到Hibernate,好长时间没有直接使用过了,看了一些资料,总结了一些知识点记在这里。主要来自官方文档(3.6.10),也有一些来自网络文章。

get和load的最大区别是,如果在缓存中没有找到相应的对象,get将会直接访问数据库并返回一个完全初始化好的对象,而这个过程有可能会涉及到多个数据库调用;而load方法在缓存中没有发现对象的情况下,只会返回一个代理对象,只有在对象getId()之外的其它方法被调用时才会真正去访问数据库,这样就能在某些情况下大幅度提高性能。
save()、saveOrUpdate()和persist()都是用于将对象保存到数据库中的方法,但其中有些细微的差别。例如,save()只能INSERT记录,但是saveOrUpdate()可以进行 记录的INSERT和UPDATE。还有,save()的返回值是一个Serializable对象,而persist()方法返回值为void。
SessionFactory就是一个用于创建Hibernate的Session对象的工厂。SessionFactory通常是在应用启动时创建好的,应用程序中的代码用它来获得Session对象。作为一个单个的数据存储,它也是 线程安全的,所以多个线程可同时使用同一个SessionFactory。Java JEE应用一般只有一个SessionFactory,服务于客户请求的各线程都通过这个工厂来获得Hibernate的Session实例,这也是为什么SessionFactory接口的实现必须是线程安全的原因。还有,SessionFactory的内部状态包含着同对象关系影射有关的所有元数据,它是 不可变的,一旦创建好后就不能对其进行修改了。
Session代表着Hibernate所做的一小部分工作,它负责维护者同数据库的链接而且不是线程安全的,也就是说,Hibernage中的Session不能在多个线程间进行共享。虽然Session会以主动滞后的方式获得数据库连接,但是Session最好还是在用完之后立即将其关闭。
Hibernate 支持两个第三方的开源 JDBC 连接池:c3p0 和 proxool。
Hibernate 的SessionFactory —
SessionFactory sessions = cfg.buildSessionFactory();
一个关联于特定数据库全局的工厂(factory),这个工厂将被应用程序的所有线程共享。如果你要使用多个数据库,就要用多个的 ,通常把它们放在多个配置文件中
SessionFactory 可以创建并打开新的 Session。一个 Session 代表一个单线程的单元操作,org.hibernate.SessionFactory 则是个线程安全的全局对象,只需要被实例化一次。
org.hibernate.Session 在第一次被使用的时候,即第一次调用 getCurrentSession() 的时候,其生命周期就开始。然后它被 Hibernate 绑定到当前线程。当事务结束的时候,不管是提交还是回滚,Hibernate 会自动把 org.hibernate.Session 从当前线程剥离,并且关闭它。假若你再次调用 getCurrentSession(),你会得到一个新的 org.hibernate.Session,并且开始一个新的工作单元。
所有的持久化类(persistent classes)都要求有无参的构造器,因为 Hibernate 必须使用 Java 反射机制来为你创建对象。
当处理映射文件时,Hibernate 用反射(reflection)来决定这个映射类型。这需要时间和资源,所以如果你注重启动性能,你应该考虑显性地定义所用的类型。

Hibernate 支持各种各样的集合映射, 使用的最为普遍。对于多对多关联(或叫 n:m 实体关系), 需要一个关联表(association table)。表里面的每一行代表从 person 到 event 的一个关联。表名是由 set 元素的 table 属性配置的。关联里面的标识符字段名,对于 person 的一端,是由 元素定义,而 event 一端的字段名是由 元素的 column 属性定义。

在查询类别对应的表格时,需注意到继承的问题,Hibernate会自动判定继承关系,如果查询的类别是某类别的父类别,则会返回与父类别、子类别对应的所有表格数据,例如如果查询java.lang.Object,由于Object在Java中是所有类别的父类别,所以下面这个查询会返回数据库中所有表格的数据:
Query query = session.createQuery("from java.lang.Object");

关于缓存
Hibernate缓存分为两级:一级缓存和二级缓存。查询时实现过程为:一级缓存-->二级缓存-->数据库,查询速度依次降低。
一级缓存为Hibernate默认带,不能卸载存在于session中。二级缓存由sessionFactory控制的进程级缓存,由全局共享。必须使用相应方法才能从缓存中获取数据,如Query.iterate()、load、get方法等。注意:session.find方法直接从库中获取数据,不读缓存。
hibernate会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性。删除、更新、增加数据的时候,同时更新缓存。
只要是调用hibernate API执行数据库相关的工作。hibernate都会为你自动保证 缓存数据的有效性!!

但是,如果你使用了JDBC绕过hibernate直接执行对数据库的操作。此时,Hibernate不会/也不可能自行感知到数据库被进行的变化改动,也就不能再保证缓存中数据的有效性!!
如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。

如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部分数据。此时,也可为其配置二级缓存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)。

财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要使用二级缓存。因为此时 “正确性”的重要性远远大于 “高性能”的重要性。

抓取策略(Fetching strategies)

当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate 使用 抓取策略(fetching strategy) 获取关联对象。抓取策略可以在 O/R 映射的元数据中声明,也可以在特定的 HQL 或条件查询(Criteria Query)中重载声明。

Hibernate3 定义了如下几种抓取策略:
连接抓取(Join fetching):Hibernate 通过在 SELECT 语句使用 OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。
查询抓取(Select fetching):另外发送一条 SELECT 语句抓取当前对象的关联实体或集合。除非你显式的指定 lazy="false" 禁止 延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条 select 语句。


子查询抓取(Subselect fetching):另外发送一条 SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。除非你显式的指定 lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条 select 语句。
批量抓取(Batch fetching):对查询抓取的优化方案,通过指定一个主键或外键列表,Hibernate 使用单条 SELECT 语句获取一批对象实例或集合。

Hibernate 会区分下列各种情况:
Immediate fetching,立即抓取:当宿主被加载时,关联、集合或属性被立即抓取。
Lazy collection fetching,延迟集合抓取:直到应用程序对集合进行了一次操作时,集合才被抓取(对集合而言这是默认行为)。
"Extra-lazy" collection fetching,"Extra-lazy" 集合抓取:对集合类中的每个元素而言,都是直到需要时才去访问数据库。除非绝对必要,Hibernate 不会试图去把整个集合都抓取到内存里来(适用于非常大的集合)。
Proxy fetching,代理抓取:对返回单值的关联而言,当其某个方法被调用,而非对其关键字进行 get 操作时才抓取。
"No-proxy" fetching,非代理抓取:对返回单值的关联而言,当实例变量被访问的时候进行抓取。与上面的代理抓取相比,这种方法没有那么“延迟”得厉害(就算只访问标识符,也会导致关联抓取)但是更加透明,因为对应用程序来说,不再看到 proxy。这种方法需要在编译期间进行字节码增强操作,因此很少需要用到。
Lazy attribute fetching,属性延迟加载:对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取。需要编译期字节码强化,因此这一方法很少是必要的。
这里有两个正交的概念:关联何时被抓取,以及被如何抓取(会采用什么样的 SQL 语句)。注意不要混淆它们。我们使用抓取来改善性能。我们使用延迟来定义一些契约,对某特定类的某个脱管的实例,知道有哪些数据是可以使用的。

21.1.1. 操作延迟加载的关联

默认情况下,Hibernate 3 对集合使用延迟 select 抓取,对返回单值的关联使用延迟代理抓取。对几乎是所有的应用而言,其绝大多数的关联,这种策略都是有效的。
假若你设置了 hibernate.default_batch_fetch_size,Hibernate 会对延迟加载采取批量抓取优化措施(这种优化也可能会在更细化的级别打开)。
然而,你必须了解延迟抓取带来的一个问题。在一个打开的 Hibernate session 上下文之外调用延迟集合会导致一次意外。比如:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")

.setString("userName", userName).uniqueResult();

Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
在 Session 关闭后,permessions 集合将是未实例化的、不再可用,因此无法正常载入其状态。 Hibernate 对脱管对象不支持延迟实例化。这里的修改方法是将 permissions 读取数据的代码移到事务提交之前。

除此之外,通过对关联映射指定 lazy="false",我们也可以使用非延迟的集合或关联。但是,对绝大部分集合来说,更推荐使用延迟方式抓取数据。如果在你的对象模型中定义了太多的非延迟关联,Hibernate 最终几乎需要在每个事务中载入整个数据库到内存中。
但是,另一方面,在一些特殊的事务中,我们也经常需要使用到连接抓取(它本身上就是非延迟的),以代替查询抓取。 下面我们将会很快明白如何具体的定制 Hibernate 中的抓取策略。在 Hibernate3 中,具体选择哪种抓取策略的机制是和选择 单值关联或集合关联相一致的。
调整抓取策略(Tuning fetch strategies)
查询抓取(默认的)在 N+1 查询的情况下是极其脆弱的,因此我们可能会要求在映射文档中定义使用连接抓取:

fetch="join">








在映射文档中定义的抓取策略将会对以下列表条目产生影响:
通过 get() 或 load() 方法取得数据。
只有在关联之间进行导航时,才会隐式的取得数据。

条件查询
使用了 subselect 抓取的 HQL 查询

不管你使用哪种抓取策略,定义为非延迟的类图会被保证一定装载入内存。注意这可能意味着在一条 HQL 查询后紧跟着一系列的查询。

通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用 HQL 的左连接抓取(left join fetch) 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。在条件查询 API 中,应该调用 setFetchMode(FetchMode.JOIN)语句。

也许你喜欢仅仅通过条件查询,就可以改变 get() 或 load() 语句中的数据抓取策略。例如:
User user = (User) session.createCriteria(User.class)

.setFetchMode("permissions", FetchMode.JOIN)

.add( Restrictions.idEq(userId) )

.uniqueResult();

这就是其他 ORM 解决方案的“抓取计划(fetch plan)”在 Hibernate 中的等价物。

截然不同的一种避免 N+1 次查询的方法是,使用二级缓存。

你可能感兴趣的:(Hibernate)