最近把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问题