Hibernate 性能提升

性能永远都是在编程中需要考虑的重要问题之一,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实例,如下所示:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值