Mybatis的使用(四)

延迟加载

延迟加载又称为懒加载或者按需加载,与之相反的概念为立即加载。从叫法上我喜欢称之为延迟加载,感觉更正式一些,从理解上称之为按需加载,更容易理解一些。所谓按需加载,就是在需要的时候才会去将其读取到内存,不需要时就不去加载它,从性能上来说这种方式更节约内存资源。

在Mybatis的association和collection中如果使用了select属性,就可以通过延迟加载来进行数据查询,此时主查询SQL中就不要先去进行关联查询,将关联查询放在select属性指向的从查询SQL中,如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<select id="queryHusbandInfoAndSonById" resultMap="queryHusbandInfoAndSonByIdMap">
<!--这里只进行单表查询,剩下的查询放到collection的select中进行-->
SELECT * FROM husband WHERE hid = #{hid}
</select>
<resultMap id="queryHusbandInfoAndSonByIdMap" type="Husband">
<id column="hid" property="hid" />
<result column="hname" property="hname" />

<!--这里会根据hid到select关联的SQL标签对应的SQL中去查询,然后对返回结果进行映射-->
<collection property="sons" column="hid" ofType="Son" select="querySonByHid">
<id column="sid" property="sid" />
<result column="sname" property="sname" />
<result column="hid" property="hid" />
</collection>
</resultMap>
<select id="querySonByHid" resultType="Son">
SELECT * FROM son WHERE hid = #{hid}
</select>

为了能够使用延迟加载,还需要在mybatis的主配置文件中添加设置,因为默认情况下延迟加载是关闭的。需要注意的是,在主配置文件中是全局配置,所以会将所有的符合条件的查询都变为延迟加载:

1
2
3
4
<!--延迟加载-->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>

如果要进行局部设置,那就在XML中对应的associationcollection标签中添加配置且优先级高于全局设置:

1
2
fetchType="lazy" 延迟加载策略 // 需要手动开启
fetchType="eager" 立即加载策略 // 默认开启

开启之后,就可以对上述的代码进行测试,这里使用logback在控制台打印日志信息,这样更能直观的判断SQL的执行流程,作为对比先使用默认的立即加载方式,然后再开启延迟加载。

附上单元测试代码:

1
2
3
4
@Test
public void loadTypeTest(){
Husband husband = husbandDao.queryHusbandInfoAndSonById(1001);
}

立即加载下控制台的打印信息:

1
2
3
4
5
6
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - ==>  Preparing: SELECT * FROM husband WHERE hid = ?  
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - ==> Parameters: 1001(Integer)
[main] DEBUG c.w.mapper.HusbandDao.querySonByHid - ====> Preparing: SELECT * FROM son WHERE hid = ?
[main] DEBUG c.w.mapper.HusbandDao.querySonByHid - ====> Parameters: 1001(Integer)
[main] DEBUG c.w.mapper.HusbandDao.querySonByHid - <==== Total: 3
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - <== Total: 1

从日志信息可以看出,一共执行了两次查询,虽然我仅仅是查询并没有使用到返回回来的数据

延迟加载下控制台的打印信息:

1
2
3
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - ==>  Preparing: SELECT * FROM husband WHERE hid = ?  
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - ==> Parameters: 1001(Integer)
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - <== Total: 1

从日志信息可以看出,只执行了一次查询,也就是主SQL查询执行了,collection中select对应的SQL并没有执行,因为我没有使用到从查询返回的数据

那么接下来,在延迟加载的方式下打印一下返回的数据,因为重写了toString(),会将所有查询的数据信息打印,也就是说会使用到从查询中的数据:

1
2
3
4
5
6
7
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - ==>  Preparing: SELECT * FROM husband WHERE hid = ?  
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - ==> Parameters: 1001(Integer)
[main] DEBUG c.w.m.H.queryHusbandInfoAndSonById - <== Total: 1
[main] DEBUG c.w.mapper.HusbandDao.querySonByHid - ==> Preparing: SELECT * FROM son WHERE hid = ?
[main] DEBUG c.w.mapper.HusbandDao.querySonByHid - ==> Parameters: 1001(Integer)
[main] DEBUG c.w.mapper.HusbandDao.querySonByHid - <== Total: 3
Husband(hid=1001, hname=tom, wife=null, sons=[Son(sid=1001, sname=son1, hid=1001, husband=null)........

从这次日志打印的结果来看,说明了只有在使用到从查询中返回的数据时,mybatis才会去执行从查询。

延迟加载的优缺点

优点在于: 当我们暂时只需要使用到实体类非关联属性时,只有在某种条件下才会使用关联属性,这种通过延迟加载可以先只进行主SQL查询,提高查询效率,也比较节省内存。

缺点在于:当数据量较大且频繁的使用关联属性时,延迟加载会增加查询的次数,消耗数据库性能