抓取策略
对集合默认使用延迟select抓取
定义了如下几种抓取策略:
-
连接抓取(Join fetching) - Hibernate通过 在SELECT语句使用OUTER JOIN(外连接)来 获得对象的关联实例或者关联集合。lazy失效,因为关联对象一并被抓取了
单端:
xml配置<many-to-one name="person" fetch="join" ></many-to-one>
注解配置:
@ManyToOne
@Fetch(FetchMode.JOIN)
@JoinColumn(name = "personid")
@ForeignKey(name = "fperson")
private Person person;
多端:
xml配置: <set name="spro" inverse="true" fetch="join">
<key column="proid"></key>
<one-to-many class="hibernate.senssic.relevance.Pro"/>
</set>注解配置:
@OneToMany(mappedBy = "person")
@Fetch(FetchMode.JOIN)
private Set<Pro> spro; -
查询抓取(Select fetching) - 另外发送一条 SELECT 语句抓取当前对象的关联实体或集合。除非你显式的指定lazy="false"禁止 延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。
单端:
xml配置<many-to-one name="person" fetch="select" ></many-to-one>
注解配置:
@ManyToOne
@Fetch(FetchMode.SELECT)
@JoinColumn(name = "personid")
@ForeignKey(name = "fperson")
private Person person;多端:
xml配置:
<set name="spro" inverse="true" fetch="join">
<key column="proid"></key>
<one-to-many class="hibernate.senssic.relevance.Pro"/>
</set>注解配置:
@OneToMany(mappedBy = "person")
@Fetch(FetchMode.JOIN)
private Set<Pro> spro; -
子查询抓取(Subselect fetching) - 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。除非你显式的指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。sql不同会影响发送的查询关联对象的方式
xml配置: <set name="spro" inverse="true" fetch="subselect">
<key column="proid"></key>
<one-to-many class="hibernate.senssic.relevance.Pro"/>
</set>
注解配置:
@OneToMany(mappedBy = "person")
@Fetch(FetchMode.SUBSELECT)
private Set<Pro> spro;
-
批量抓取(Batch fetching) - 对查询抓取的优化方案, 通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合。
hibernate配置默认抓取hibernate.default_batch_fetch_size=5
映射文件中:
<class name="hibernate.senssic.relevance.Pro" lazy="true" batch-size="5">
集合上:
<set name="spro" inverse="true" batch-size="5">
<key column="proid"></key>
<one-to-many class="hibernate.senssic.relevance.Pro"/>
</set>注解配置:
类上:
@Entity
@BatchSize(size=5)
public class Person {集合上:
@OneToMany(mappedBy = "person")
@Fetch(FetchMode.SUBSELECT)
@BatchSize(size = 5)
private Set<Pro> spro;}
Hibernate会区分下列各种情况:
1、Immediate fetching,立即抓取 - 当宿主被加载时,关联、集合或属性被立即抓取。
2、Lazy collectionfetching,延迟集合抓取- 直到应用程序对集合进行了一次操作时,集合才被抓取。(对集合而言这是默认行为。)
3、"Extra-lazy"collection fetching,"Extra-lazy"集合抓取 -对集合类中的每个元素而言,都是直到需要时才去访问数据库。除非绝对必要,Hibernate不会试图去把整个集合都抓取到内存里来(适用于非常大的集合)。
4、Proxy fetching,代理抓取 - 对返回单值的关联而言,当其某个方法被调用,而非对其关键字进行get操作时才抓取。
5、"No-proxy"fetching,非代理抓取 - 对返回单值的关联而言,当实例变量被访问的时候进行抓取。与上面的代理抓取相比,这种方法没有那么“延迟”得厉害(就算只访问标识符,也会导致关联抓取)但是更加透明,因为对应用程序来说,不再看到proxy。这种方法需要在编译期间进行字节码增强操作,因此很少需要用到。
6、Lazy attributefetching,属性延迟加载 - 对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取。需要编译期字节码强化,因此这一方法很少是必要的。
使用无状态session
作为选择,Hibernate提供了基于命令的API,可以用detached object的形式把数据以流的方法加入到数据库,或从数据库输出。StatelessSession没有持久化上下文,也不提供多少高层的生命周期语义。特别是,无状态session不实现第一级cache,也不和第二级缓存,或者查询缓存交互。它不实现事务化写,也不实现脏数据检查。用stateless session进行的操作甚至不级联到关联实例。stateless session忽略集合类(Collections)。通过stateless session进行的操作不触发Hibernate的事件模型和拦截器。无状态session对数据的混淆现象免疫,因为它没有第一级缓存。无状态session是低层的抽象,和低层JDBC相当接近。
StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.getNamedQuery("GetCustomers")
.scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
Customer customer = (Customer) customers.get(0);
customer.updateStuff(...);
session.update(customer);
}
tx.commit();
session.close();
注意在上面的例子中,查询返回的Customer实例立即被脱管(detach)。它们与任何持久化上下文都没有关系。
StatelessSession 接口定义的insert(), update() 和 delete()操作是直接的数据库行级别操作,其结果是立刻执行一条INSERT, UPDATE 或 DELETE 语句。因此,它们的语义和Session接口定义的save(), saveOrUpdate() 和delete() 操作有很大的不同。
优化的几点建议
1. 对大数据量查询时,慎用list()或者iterator()返回查询结果,(1)。 使用List()返回结果时,Hibernate会所有查询结果初始化为持久化对象,结果集较大时,会占用很多的处理时间。
(2)。 而使用iterator()返回结果时,在每次调用 iterator.next()返回对象并使用对象时,Hibernate才调用查询将对应的对象初始化,对于大数据量时,每调用一次查询都会花费较多的时间。当结果集较大,但是含有较大量相同的数据,或者结果集不是全部都会使用时,使用iterator()才有优势。
2、在一对多、多对一的关系中,使用延迟加载机制,会使不少的对象在使用时方会初始化,这样可使得节省内存空间以及减少数据库的负荷,而且若PO中的集合没有被使用时,就可减少互数据库的交互从而减少处理时间。
3、对含有关联的PO(持久化对象)时,若default-cascade="all"或者 “save-update”,新增PO时,请注意对PO中的集合的赋值操作,因为有可能使得多执行一次update操作。
4、 对于大数据量新增、修改、删除操作或者是对大数据量的查询,与数据库的交互次数是决定处理时间的最重要因素,减少交互的次数是提升效率的最好途径,所以在开发过程中,请将show_sql设置为true,深入了解Hibernate 的处理过程,尝试不同的方式,可以使得效率提升。尽可能对每个页面的显示,对数据库的操作减少到100——150条以内。越少越好。
大体上,对于HIBERNATE性能调优的主要考虑点如下:
* 数据库设计调整
* HQL优化
* API的正确使用(如根据不同的业务类型选用不同的集合及查询API)
* 主配置参数(日志,查询缓存,fetch_size, batch_size等)
* 映射文件优化(ID生成策略,二级缓存,延迟加载,关联优化)
* 一级缓存的管理
* 针对二级缓存,还有许多特有的策略
* 事务控制策略。
事务方面对性能有影响的主要包括:事务方式的选用,事务隔离级别以及锁的选用
a) 事务方式选用:如果不涉及多个事务管理器事务的话,不需要使用JTA,只有JDBC的事务控制就可以。
b) 事务隔离级别:参见标准的SQL事务隔离级别
c) 锁的选用:悲观锁(一般由具体的事务管理器实现),对于长事务效率低,但安全。乐观锁(一般在应用级别实现),如在HIBERNATE中可以定义VERSION字段,显然,如果有多个应用操作数据,且这些应用不是用同一种乐观锁机制,则乐观锁会失效。因此,针对不同的数据应有不同的策略,同前面许多情况一样,很多时候我们是在效率与安全/准确性上找一个平衡点,无论如何,优化都不是一个纯技术的问题,你应该对你的应用和业务特征有足够的了解。
*方法选用
a) 完成同样一件事,HIBERNATE提供了可供选择的一些方式,但具体使用什么方式,可能用性能/代码都会有影响。显示,一次返回十万条记录(List/Set/Bag/Map等)进行处理,很可能导致内存不够的问题,而如果用基于游标(ScrollableResults)或Iterator的结果集,则不存在这样的问题。
b) Session的load/get方法,前者会使用二级缓存,而后者则不使用。
c) Query和list/iterator,如果去仔细研究一下它们,你可能会发现很多有意思的情况,二者主要区别(如果使用了Spring,在HibernateTemplate中对应find,iterator方法):
i. list只能利用查询缓存(但在交易系统中查询缓存作用不大),无法利用二级缓存中的单个实体,但list查出的对象会写入二级缓存,但它一般只生成较少的执行SQL语句,很多情况就是一条(无关联)。
ii. iterator则可以利用二级缓存,对于一条查询语句,它会先从数据库中找出所有符合条件的记录的ID,再通过ID去缓存找,对于缓存中没有的记录,再构造语句从数据库中查出,因此很容易知道,如果缓存中没有任何符合条件的记录,使用iterator会产生N+1条SQL语句(N为符合条件的记录数)
iii. 通过iterator,配合缓存管理API,在海量数据查询中可以很好的解决内存问题,如:
while(it.hasNext()){
YouObject object = (YouObject)it.next();
session.evict(youObject);
sessionFactory.evice(YouObject.class, youObject.getId());
}
如果用list方法,很可能就出OutofMemory错误了。
iv. 通过上面的说明,我想你应该知道如何去使用这两个方法了