Hibernate的n+1问题和基本映射总结

最近把hibernate又从头到尾的学了一遍,懂了不少原来没弄清的东西,今天简单总结下hibernate的n+1问题的优化吧。


我们先来总结下hibernate的基本映射:

一对一,一对多,多对一,多对多。有过数据库基础的同学应该很容易理解这几种关系

其中一对多和多对一是有区别的。

1.从业务上看,如果有两张表,订单order和订单商品项orderItem,每次我获得一个订单时也需要获得这个订单里面的商品信息,这时候适合使用一对多,这样foreach订单表的时候我可以获得它的items属性,再foreach下就得到订单商品信息了。

如果是这两张表,年级grade和学生student,显然用一对多就不太适合了,我不可能获得一个年级时需要取出这个年级的所有学生,不合逻辑空间消耗也大。所以要用多对一,我很可能获得一个学生时需要知道他的年级消息。

2.多对一的用的是比较广泛,公司老员工给出的一种解释是,一对多用的乱序的set集合,每次取值出来的顺序都不一样,实际应用比较麻烦。

3.一对多要注意的是inverse问题,和cascade有点类似,其实我理解的也不是很透彻,只明白一对多最好外键所在端(一般为one端)来维持关系,不然one端每次改变都要update many端的信息。

如order和orderItem配置了一对多和多对一的双向映射以及saveOrupdate的cascade,save一条order数据时会发现在插入了order表和orderItem表信息后,又重复更新了orderItem表中每一项商品的订单号列。

无代码无真相,上代码:

@Test
	public void test3() {
		session=HibernateUtil.getCurrentSession();
		transaction=session.beginTransaction();
		
		//订单
		Order order=new Order(null,new Date());
		//订单里面的商品
		OrderItem orderItem1=new OrderItem();
		orderItem1.setProductName("钢笔");
		orderItem1.setOrder(order);
		
		OrderItem orderItem2=new OrderItem();
		orderItem2.setProductName("草稿纸");
		orderItem2.setOrder(order);
		
		Set<OrderItem> items=new HashSet<OrderItem>();
		items.add(orderItem1);
		items.add(orderItem2);
		order.setItems(items);
		
		session.save(order);
		
		transaction.commit();
		
	}
运行的SQL语言:

Hibernate: 
    insert 
    into
        tab_order
        (createDate) 
    values
        (?)
Hibernate: 
    insert 
    into
        tab_orderItem
        (productName, price, amount, oid) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        tab_orderItem
        (productName, price, amount, oid) 
    values
        (?, ?, ?, ?)
Hibernate: 
    update
        tab_orderItem 
    set
        oid=? 
    where
        id=?
Hibernate: 
    update
        tab_orderItem 
    set
        oid=? 
    where
        id=?
如果在one端加上inverse=true,注解是mappedBy="类名首字母小写",则生成SQL语言为:

Hibernate: 
    insert 
    into
        tab_order
        (createDate) 
    values
        (?)
Hibernate: 
    insert 
    into
        tab_orderItem
        (productName, price, amount, oid) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        tab_orderItem
        (productName, price, amount, oid) 
    values
        (?, ?, ?, ?)

好啦,一说就说多了,inverse等以后更清楚了单独再说,最后回归主题吧。


现在说下hibernate的n+1问题

 n+1是什么呢?

对于一对多,例如订单表和订单商品表。查询订单表时会自动关联每个订单的订单商品。这样查询订单时用掉了一条sql语句,查出了n条订单数据。然后hibernate会挨个找出n个订单对应的订单商品,又用掉n条sql语句,一共是n+1条。

对于多对一,同样是订单表和订单商品表。查询订单商品时hibernate会自动关联每个订单商品的订单。这样查询订单商品时用了一条语句,查出了n条订单商品。然后hibernate挨个找到每个订单商品的订单,又用掉了n条sql语句,一共是n+1条。

一对多:

Hibernate: 
    select
        order0_.oid as oid1_2_,
        order0_.oname as name2_2_ 
    from
        tab_order order0_
订单一
Hibernate: 
    select
        items0_.oid as oid5_2_0_,
        items0_.id as id1_3_0_,
        items0_.id as id1_3_1_,
        items0_.productName as productN2_3_1_,
        items0_.price as price3_3_1_,
        items0_.amount as amount4_3_1_,
        items0_.oid as oid5_3_1_ 
    from
        tab_orderItem items0_ 
    where
        items0_.oid=?
钢笔
铅笔
草稿纸
订单二
Hibernate: 
    select
        items0_.oid as oid5_2_0_,
        items0_.id as id1_3_0_,
        items0_.id as id1_3_1_,
        items0_.productName as productN2_3_1_,
        items0_.price as price3_3_1_,
        items0_.amount as amount4_3_1_,
        items0_.oid as oid5_3_1_ 
    from
        tab_orderItem items0_ 
    where
        items0_.oid=?
笔记本
图书
多对一(订单表查出来的是订单创建的日期)

Hibernate: 
    select
        orderitem0_.id as id1_3_,
        orderitem0_.productName as productN2_3_,
        orderitem0_.price as price3_3_,
        orderitem0_.amount as amount4_3_,
        orderitem0_.oid as oid5_3_ 
    from
        tab_orderItem orderitem0_
铅笔Hibernate: 
    select
        order0_.oid as oid1_2_0_,
        order0_.createDate as createDa2_2_0_ 
    from
        tab_order order0_ 
    where
        order0_.oid=?
 2015-09-23 20:29:47.0
钢笔 2015-09-23 20:29:47.0
笔记本Hibernate: 
    select
        order0_.oid as oid1_2_0_,
        order0_.createDate as createDa2_2_0_ 
    from
        tab_order order0_ 
    where
        order0_.oid=?
 2015-09-30 20:29:53.0
图书 2015-09-30 20:29:53.0
草稿纸 2015-09-23 20:29:47.0

从上面可以看出,如果不优化的话,hibernate的查询效率真的是很低,其实这么多语句一条外连接的sql语句就可以搞定了。


下面说说优化的几种方法:(都是针对xml配置)

1.设置懒加载,lazy="true"

这样不用到关联对象时只有一条sql语句,但如果用到关联对象还是n+1条,没有从根本上解决问题

2.设置抓取方式,fetch="subselect"

这样查询到关联对象时会用sql语句中的'select ... from ... where .. in(.......) ',把n条语句化简为了一条,效率有提高。

但这种方法对多对一好像没效,在多对一里面写不进去,原因我也没弄明白

3.外连接

直接用左外连接语句,这样只需要一条sql语句,效率最高,推荐使用。

以一对多为例

"select distinct(o) from Order as o left join fetch o.items";

(之所以订单加distinct是因为左外连接查出来的会有数据上的重复。因为左外连接查出来的是一个笛卡尔积,而每查出来的一个订单都会关联对应的订单商品)

Hibernate: 
    select
        distinct order0_.oid as oid1_2_0_,
        items1_.id as id1_3_1_,
        order0_.createDate as createDa2_2_0_,
        items1_.productName as productN2_3_1_,
        items1_.price as price3_3_1_,
        items1_.amount as amount4_3_1_,
        items1_.oid as oid5_3_1_,
        items1_.oid as oid5_2_0__,
        items1_.id as id1_3_0__ 
    from
        tab_order order0_ 
    left outer join
        tab_orderItem items1_ 
            on order0_.oid=items1_.oid
2015-09-23 20:29:47.0
铅笔
钢笔
草稿纸
2015-09-30 20:29:53.0
笔记本
图书


最后在上面的基础上,总结一下注解优化方法:

oneToXXX延迟查询效率最高的几种方法
 第一种:lazy=true fetch=select batch-size= 5 然后,你的所有语句不用考虑联合查询 from Department d
 第二种:lazy=true fetch=subselect 然后,你的所有语句不用考虑联合查询from Department d
 第三种: 默认 lazy=true,fetch=select 你自己知道什么时候用Department : from  Department d 什么时候Deparment - Employee : from Department d left join fetch d.employees

XXXToOne延迟查询效率最高的方法
 第一种:左外连接,只需一句SQL语句查询
 第二种:否则用懒加载@LazyToOne(LazyToOneOption.PROXY)



参考网站:hibernate N+1问题

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值