性能永远都是在编程中需要考虑的重要问题之一,Hibernate编程也不例外。
本篇文档将围绕批量处理、延迟加载、N+1查询等主题展开学习Hibernate的性能提升策略。
批量操作
在实际应用中,往往需要对数据库进行批量操作,可能是批量插入、批量查询等。
如下代码所示,批量往数据库中插入200000条记录:
public static void main(String args[]){
Session session = HibernateSessionFactory.getSession();
Transaction tran = session.beginTransaction();
for(int i=0;i<200000;i++){
session.save(new Person(i,"Name"+i,23));
}
tran.commit();
}
运行上述代码的过程中,有可能将发生内存溢出异常:
Exception in thread "main" java.lang.OutOfMemoryError:Java heap space
这是因为Hibernate将需要插入数据库的Person对象缓存到了session级别的缓存区,当缓存区满了,就抛出了内存溢出异常。
本文档将学习如何避免这种异常的两种方法。
1、使用Session的flush/clear方法
public static void main(String args[]){
Session session = HibernateSessionFactory.getSession();
Transaction tran = session.beginTransaction();
for(int i=0;i<200000;i++){
session.save(new Person(i,"Name"+i,23));
if(i%20==0){
session.flush();
session.clear();
}
}
tran.commit();
}
上述代码中每循环20次即使用session.flush和session.clear清空缓存,避免了内存溢出。
2、使用StatelessSession接口
StatelessSession 接口中提供了delete、update、insert方法,与SQL语句同名。
该接口中的方法不使用缓存策略,而是直接执行
public static void main(String args[]){
Configuration conf = new Configuration().configure();
SessionFactory factory = conf.buildSessionFactory();
StatelessSession session = factory.openStatelessSession();
Transaction tran = session.beginTransaction();
for(int i=0;i<200000;i++){
session.insert(new Person(i,"Name"+i,23));
}
tran.commit();
}
上述代码不会发生内存溢出异常,将在事务提交后向数据库中插入200000条记录。
延迟加载
当某实例有关联实例时,Hibernate中默认使用延迟加载。
public static void main(String args[]){
Session session = HibernateSessionFactory.getSession();
String hql="from Person p";
List<Person> list = session.createQuery(hql).list();
for(Person p:list){
System.out.println(p.getName());
}
}
Person实例关联了Address实例的Set集合,默认情况下使用延迟加载。
所以查询Person实例时,并不会查询Address实例。
可见仅执行了查询Person实例的SQL语句,并没有查询关联的Address。
只有显示使用方法返回Address实例时,才会查询Address。
public static void main(String args[]){
Session session = HibernateSessionFactory.getSession();
Person p = (Person)session.get(Person.class, 1);
Set<Address> set = p.getAddresses();
for(Address a:set){
System.out.println(a.getDetail());
}
}
上述代码中,返回一个Person实例后,调用Person中的getAddresses()方法返回关联的Address实例集合,所以将查询Address。
可以通过lazy=“false”,取消延迟加载,如在Person.hbm.xml中进行如下修改:
<set name="addresses" inverse="true" lazy="false" fetch="select">
<key>
<column name="person_id" />
</key>
<one-to-many class="onetomany.Address" />
</set>
配置lazy="false"后,将取消Person的延迟加载,也就是只要查询Person,就会同时查询Person所关联的Address实例
public static void main(String args[]){
Session session = HibernateSessionFactory.getSession();
String hql="from Person p";
List<Person> list = session.createQuery(hql).list();
}
上述代码中并没有显示查询Address实例,然而由于Person配置了取消延迟加载,所以运行后生成如下SQL语句:
batch-size属性
假设实体A和B存在one-to-many的关联关系,如果有N个A实例,则需要查询N次,
如果需要查询每个A所关联的B实例,则需要先将A查询得到,在查询关联B的实例,则需要N+1次查询,这就是所谓”N+1查询问题“。
表Person
表address
public static void main(String args[]){
Session session = HibernateSessionFactory.getSession();
String hql="select p,addresses from Person p join p.addresses addresses";
List<Object[]> list = session.createQuery(hql).list();
for(Object[] obj:list){
Person per=(Person)obj[0];
Address addr=(Address)obj[1];
System.out.println(per.getId()+" "+addr.getDetail());
}
}
上述代码通过连接查询返回Person和Address记录,在控制台查看生成的SQL语句,共三条,即N+1(N为主表的对象个数,即Person的记录数,为2)
假设N的值很大,那么将一定程度影响查询效率。
这种情况下可以使用bitch-size属性减少查询语句条数,从而提高性能。
batch-size属性可以定义批量处理实体个数,建议在5~30之间。
修改Person.hbm.xml
<set name="addresses" inverse="true" lazy="false" fetch="join" batch-size="5">
<key>
<column name="person_id" />
</key>
<one-to-many class="onetomany.Address" />
</set>
再次执行
public static void main(String args[]){
Session session = HibernateSessionFactory.getSession();
String hql="select p,addresses from Person p join p.addresses addresses";
List<Object[]> list = session.createQuery(hql).list();
for(Object[] obj:list){
Person per=(Person)obj[0];
Address addr=(Address)obj[1];
System.out.println(per.getId()+" "+addr.getDetail());
}
}
控制台输出两条SQL语句,一条负责返回Person实例,一条负责返回所有的Address实例,如下所示: