关于OneToOne的延迟加载(fetch = FetchType.LAZY),我觉得是非常需要注意的一个问题。它不只是存在于Hibernate中,其他的ORM persistence framekwork也有同样的问题,至少我用过的eclipseLink也是一样的。这个问题之所以重要,是因为它在背后偷偷的降低了查询数据库的效率,但又极难发现,特别是当你进入一个老项目,发现软件的运行效率极低时,你就应该检查一下已有代码中是否合理定义了OneToOne的关联。
N+1 Select
我们先简单的回顾一下ORM里面常见的N+1 Select的问题:
在Java Persistence with Hibernate的13章有比较清楚的解释,这里举个简单的例子:
假设我们在数据库里定义了Car这种数据类型,每辆Car都关联了Wheel的Object。简单的就是Car和Wheel之间是一种OneToMany的关联。
如果我们需要从数据库里面取出所有的cars。那么典型的ORM框架会做如下的步骤:
首先,取出所有的Cars:
SELECT * FROM Cars;
然后针对每一辆Car:
SELECT * FROM Wheel WHERE CarId = ?
换句话说,ORM用了一条select语句来取出所有的Car,然后用了额外的N条select语句来取出和Car相关联的对象,这是相当损耗效率的一种行为。哪怕我们自己写一条:
SELECT * FROM Wheel
将select的执行次数由N+1变为2,然后在内存中自己查找car和wheel,也比执行N+1要强很多。
特别是有的时候,当我们在查询Car的时候,未必需要用到Wheel的数据,这时更是一种额外的开销。
延迟加载
所以,ORM框架为我们提供了一种机制,延迟加载,只有确定要使用关联对象的时候我们才把它从数据库里面读取出来。Hibernate 的延迟加载本质上就是代理模式的应用,当程序通过 Hibernate 装载一个实体时,默认情况下,Hibernate 并不会立即抓取它的集合属性、关联实体所以对应的记录,而是通过生成一个代理来表示这些集合属性、关联实体,这就是代理模式应用带来的优势。
而这里要讨论的OneToOne就是上面所提到的关联实体。
OneToOne无法延迟加载
那OneToOne的延迟加载有什么问题呢?
我们先来简单创建两个Entity(Car和SteelingWheel),并在两个Entity之间创建1对1的关联。
Car:
@Entity
@Table( name = "car" )
public class Car
{
@Id
@Column( name = "id" )
@GeneratedValue( strategy = GenerationType.AUTO )
Long id;
@Column( name = "brand" )
String brand;
/*关联SteelingWheel,同时指定延迟加载,当然hibernate是默对关联实体进行延迟加载的*/
@OneToOne( cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "car" )