application objects
(jvm reference) <-> session cache
(hibernate id) <-> session factory cache
(hibernate id) <-> database
1. Application objects are objects referenced by application thread.
2. Session cache are cache provided in session level (first level cache), its merely an object pool, so they refer to the same object as application thread, note that though session also contains snapshots, they are merely used for dirty-checking, not cache.
3. Session factory cache are second level cache, objects to be put inside this cache may be compressed, written to a file or even hosted in cluster.
4. Objects inside database are stored as one or more rows inside table(s).
All the methods in Session interface operate on the aboving model, they are only valid when the model is in certain state, they manipulate the mode in different way and change the mode to different state.
the detail semantic of each operations are descripted in http://xlz98.iteye.com/blog/265945
(a copy in case the link was broken...)
2 Hibernate 数据操纵基础
Hibernate中持久对象有三种状态:瞬时(transient)状态;持久化 (persistent) 状态;脱管(detached) 状态。持久对象的三种状态的转变依赖session对象上的操作进行。
上面提到Hibernate 中一个重要的类SESSION。作用相当于JDBC中CONNECTION对象。是执行持久化操作(crud)的主要类。当一个对象被持久化时,会被加到session缓存中。Session缓存的作用是减少数据库的访问频率,提高性能。正是由于其缓存的存在,在各个方法的差异中最大的不同就是处理缓存数据的方式不同。从而导致性能各异。理解HIBERNATE的工作原理,主要就是要理解其缓存工作机制,以及缓存与数据库的之间维护,从而能了解到其提供的各种操作方法上的区别。
下面描述了持久对象状态转化时操作方法:
3 持久对象操作方法分析
3.1 Save persist saveOrUpdate update merge lock
Save 、 persist与saveOrUpdate方法都可以将对象从临时状态转化为持久状态。
Save 方法作用是将一个临时对象保存并持久化。会触发sql insert操作。
Persist方法与save 方法作用很相像。实际上persist把一个瞬态的实例持久化,但是并"不保证"标识符被立刻填入到持久化实例中,标识符的填入可能被推迟到flush的时间。它可能不会马上触发sql insert ,直到调用flush方法时才触发。另外当它在一个transaction外部被调用的时候并不触发一个Sql Insert,这个功能是很有用的,当我们通过继承Session/persistence context来封装一个长会话流程的时候,一个persist这样的函数是需要的。
saveOrUpdate 方法被调用时,如果对象在当前session中己经持久化,将不做任何事情。如果对象没有持久化标识(identifier)属性,对其调用save() 方法。如果对象的持久标识(identifier)表明其是一个新实例化的对象,对其调用save()方法 。如果对象是附带版本信息的(通过<version>或<timestamp>) 并且版本属性的值表明其是一个新实例化的对象,对其调用save()方法。如果与当前session关联的另一个对象拥有相同的持久化标识(identifier),抛出一个异常 。 否则update() 这个对象。当对一个对象的状态不是很清楚时候,使用此方法最为安全。
Update、merge 、saveOrUpdate与lock方法可以用于将对象从游离状态转化为持久状态。也就是操作对象曾经被持久化过。不过saveOrUpdate方法是个例外,它即可以用于将临时状态转化为持久状态也可以用于将游离状态转化为持久状态。
Update方法把一个已经更改过的脱管状态的对象变成持久状态。会触发sql update操作。
Merge 方法用于不改变客户端提供对象的状态。分为两种情况:一是如果session中存在相同持久化标识(identifier)的实例,用客户端给出的对象的状态覆盖旧有的持久实例;二是:如果session没有相应的持久实例,则尝试从数据库中加载,或创建新的持久化实例,最后返回该持久实例。客户端提供的对象依然是脱管的,并没有被关联到当前SESSION上。需要特别说明的是,使用update的时候,执行完成后,客户端提供的对象A的状态变成持久化状态,但使用merge的时候,执行完成,客户端提供的对象A还是脱管状态,hibernate或者new了一个B,或者检索到一个持久对象B,并把我们提供的对象A的所有的值拷贝到B,执行完成后B是持久状态,而客户端提供的A还是托管状态。
Lock方法是把一个没有更改过的脱管状态的对象变成持久状态。但它不象update方法立即触发sql update操作。其操作步骤是这样的:把对象从脱管状态变成持久状态-->更改持久状态对象的内容-->等待flush或者手动flush。此操作并没有马上触发sql update操作。
3.2 Refresh vs. flush
Refresh 与flush方法是用于数据库和hibernate缓存同步的。Hibernate的操作是依赖其缓存的。这在用户自建JDBC连接操作数据库或在hibernate操作的某个时间点上,有可能缓存与数据库之间的数据是不同步的。用户可以主动使用这个两个方法来进行同步。
Flush 方法用于hibernate缓存到数据库同步。将持久状态己变更的对象同步到数据库中。默认情况下,持久对象是不需要update的,只要更改持久化对象值,等待hibernate flush就自动保存到数据库了。下面几种情况会发生flush:
调用某些查询的时候
transaction commit的时候
手动调用flush的时候
Hibernate 自动flush的时候
Refresh方法用于数据库到缓存同步。将数据库中状态同步到缓存持久对象状态上。此操作用于客户端绕开HIBERNATE而用JDBC直接操作了数据库内容,为了保证数据库内容与hibernate缓存中持久对象同步,手动调用此方法进行同步。当需要进行批量操作时,由于hibernate 操作性能比较差,可以使用jdbc直接操作后,调用此方法将修改同步到hibernate缓存中。
理解这个两个方法差异可以帮助理解hibernate的缓存机制。
3.3 Evict close clear contains
Evict 方法用于清除缓存中指定的持久对象,无论这个对象是否在使用。所以使用这个方法需要小心,如果在一个事务中对当前事务中的持久对象进行了清除操作,会在事务提交后同步缓存中对象状态时抛出错误。因为被清除后,对象不再是持久状态而是游离状态 。这个方法通常会用于大数据量操作后,可能会引起OutOfMemory 问题,而主动清除持久对象,使其可以被回收。使用时一定要注意其安全性。
Clear方法用于清除缓存中所有对象,但不包括操作中的对象。相对于evict,此方法操作是安全的,不会操成当前正操作的持久对象转化为脱管对象,从而在状态同步时出错。
Close 方法用于关闭当前session。与当前session绑定的持久对象将全部转化为游离状态,可以被垃圾回收。
contains()方法判断一个实体对象是否与当前的Session对象相关联。判断是不是Persistent状态。
3.4 Get load
Get方法 hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,如果没有,就查询数据库,数据库中没有,就返回null。在3.0以后版本中get方法也会查找二级缓存。
Load 方法load方法加载实体对象的时候,根据映射文件上类级别的lazy属性的配置(默认为true),分情况讨论:
(1)若为true,则首先在Session缓存中查找,看看该id对应的对象是否存在,不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。等到具体使用该对象(除获取Object ID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
(2)若为false,就跟get方法查找顺序一样,只是最终若没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
get和load有两个重要区别:
如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException。 load方法可返回没有加载实体数据的代理类实例,而get方法永远返回有实体数据的对象。(对于load和get方法返回类型:好多书中都说:“get方法永远只返回实体类”,实际上并不正确,get方法如果在session缓存中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是原先的代理对象,而不是实体类对象,如果该代理对象还没有加载实体数据(就是id以外的其他属性数据),那么它会查询二级缓存或者数据库来加载数据,但是返回的还是代理对象,只不过已经加载了实体数据。)
get和load的根本区别是,load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。
3.5 查询与数据过滤
Hibernate 提供四种方式查询。分别是query 、filter、Criteria 、Example
3.5.1 Query
Query接口是hibernate中用于hql查询的接口。它是构成hibernate hql 强大与复杂的基础。其有两个结果返回方法:list 、iterate 。对应到spring的hibernateTemplate类上分别为find 、iterat。在性能上,这两个方法有一些差别:
1. 如果我们在使用find,iterate进行查询时分别构建他们的HQL,那么我们在控制台输出当中看到iterate()方法所使用的SQL语句的次数要远远超过Find()。这是由于find 方法不使用缓存,直接执行sql查询返回结果,并将结果写到缓存中。而iterate方法实际上是返回所有对象标识,然后根据标识去查找缓存,如果缓存存在就直接返回结果,如果不存在发送sql 查询。最坏的可能是发送n+1条查询。
2. 为什么还存在iterate呢?当我们在使用find构建HQL后,而同样的查询也让iterate()来执行一次的话(前提是必须使用find()构建的HQL),那么iterate()的执行并不再像前面介绍的那样会比find()方法多执行SQL,而是根本再执行一条SQL。这就是iterate()方法的使用。它使用了Hibernate缓存机制.Find()方法在查询出结果后把查询结果集置入缓存,而iterate()执行的时候先执行一条Select SQL,查询所有符合条件的结果集,接下来iterate()根据查询的id在本地缓存中查找符合条件的结果集,如果有完全符合条件的结果集,则直接以此作为返回结果。如果没有找到再执行相应的SQL,并且把结果纳入缓存当中。以备使用
3.缓存机制并不对find()方法起使用。如果你两次执行相同条件的或相近条件的HQL,第二个方法的SQL并不减少。还会照常执行。这就是说缓存机制并不对find()方法起作用。find()对缓存只写不读,而iterate()方法就可以充分利用缓存的优势。
4.内存方面的考虑,除了缓存的优势之外,我们还可以利用iterate()与evict()方法来提升查询性能上的优势。
这个问题主要是大数据量所带来的考虑。因为find()方法可以对大数据量的结果集进行缓存,但是如果数据量超大的话,也会带来内存溢出方面的问题。利用iterate(),evict()可以对记录进行逐条处理,将内存消耗保持在可接受的范围之内。 它包括从session中消除对对象,及从sessionFactory当中消除对象。
5. 对于find()方法在读取缓存问题方面的解决。Query Cache是一个解决方案,不过目前的使用受限比较大。
使用Query Cache的条件是:
A。数据库表结构不变。即未发生过update,insert,delete等操作
B。相同的HSQL的重复执行操作。
由于以上两个原因,query Cache在实际使用中对性能影响的效果受到了很大的限制。
3.5.2 filter
Hibernate3 提供了一种创新的方式来处理具有“显性(visibility)”规则的数据,那就是使用Hibernate filter。 Hibernate filter是全局有效的、具有名字、可以带参数的过滤器, 对于某个特定的Hibernate session您可以选择是否启用(或禁用)某个过滤器。
对某个类或者集合使用预先定义的过滤器条件(filter criteria)的功能。过滤器条件相当于定义一个非常类似于类和各种集合上的“where”属性的约束子句,但是过滤器条件可以带参数。应用程序可以在运行时决定是否启用给定的过滤器,以及使用什么样的参数值。 过滤器的用法很像数据库视图,只不过是在应用程序中确定使用什么样的参数的。
3.5.3 Criteria
Criteria 查询通过面向对象化的设计,将数据查询条件封装为一个对象。简单来讲,Criteria Query可以看作是传统SQL的对象化表示。此方法只提供了session list方法操作,也就是此操作不会利用缓存,但会向缓存写数据。
如:
Criteria criteria = session.createCriteria(User.class);
criteria.add(Expression.eq("name","Erica"));
criteria.add(Expression.eq("sex",new Integer(1)));
这里的criteria 实例实际上是SQL “Select * from t_user where name=’Erica’ and sex=’1’的封装。
3.5.4 Example
org.hibernate.criterion.Example类允许通过一个给定实例 构建一个条件查询。此方法本质与 Criteria查询相同,只是在构建查询条件时通过一个给定的实例来完成。
例如:
Cat cat = new Cat();
cat.setSex('F');
cat.setColor(Color.BLACK);
List results = session.createCriteria(Cat.class).add( Example.create(cat) ).list();
4 平台数据操作方法4.1
持久对象操作
平台所有持久对象的操作方法完全是封装hibernate 的操作方法,并且语义是相同的。这些方法之间差异在3节中己经阐述比较清楚了。请参阅。
4.2 查询操作
平台中为结合实际情况增加了一些查询方法。所有这些方法最终是调用hibernate查询方法下面加以说明。
Find 方法与hibernate list 方法作用相同。此方法不会利用缓存。
Iterate 方法与hibernate 的 iterate 方法作用相同。此方法可以有效的利用缓存。
findBy, findByCriteria , findByExample , findAllByCriteria(DetachedCriteria, Object...) , pagedQuery ,findUniqueBy执行的是session Criteria操作,返回数据时最终调用是session list 操作。
findByNamedParam,findByNamedQuery , findByNamedQueryAndNamedParam ,pagedQueryByHQL , findByValueBean执行的session list 操作。
在实际开发中,大多数据会使用findXXX方法去执行查询操作,最终它们都是使用session list方法,这种不能有效利用缓存,但是会把查询结果写入缓存的方法,在小数据量操作很有优势。在一些大数据量查询时,推荐使用iterate结合evict一起使用,以提升性能。
4.3 批量处理
Hibernate批量处理机制是,先把符合条件的数据查出来,放到内存当中,然后再进行操作。从性能上考虑,浪费了很大的内存,同时存在查询动作与维护缓存时间。所以hibernater批量处理性能非常不理想,个人认为可以采用下面的三种方案加以优化,但前两个方案都需要客户端手动维护hibernate缓存与数据库的一致性,客户端使用需要自己加以区别。
1:绕过Hibernate API ,直接通过 JDBC API 来做,这个方法性能上是最快的。但会引起hibernate缓存可能与数据库内容不一致,需要客户端非常清楚自己的行为,并手动维护被变更的数据在缓存中的状态。在IGenericDao中,提供方法可以直接通过jdbc来执行批量操作。
2:运用存储过程。由于存储过程带来移植的问题,一般不推荐使用。它也和上面一样存在手动维护缓存持久对象状态的问题。
3:用Hibernate API 来进行常规的批量处理。但需要分批处理。可以在查找出一定的量的时候,及时的将这些数据做完操作就删掉,session.flush(),session.evict(XX对象集)。根据当前操作对象的数据量大小,选取一个合适的范围进行操作可以提升一些性能。特别是特大数据量时,可以保证系统不会出现outofmemory的错误。
4:使用hibernate bulkUpdate 方法。可以直接使用hibernateTemplate 己经封装的bulkUpdate 方法进行操作。此方法虽然使用的是hibernate接口,但最终也只会执行一条sql,并且也是不维护缓存状态的。同样存在上面的风险。