文章目录
1、延迟加载定义
从字面意义上来讲,是对某种信息推迟加载。在MyBatis中,通常会进行多表联合查询,但是有的时候并不会立即用到所有的联合查询结果。例如在上一篇系列博客 《MyBatis使用篇(六)—— MyBatis关联查询》中使用的演示案例中,先查询一个理财产品批次订单下的明细,而不直接展示每列明细对应的理财产品的详细信息,等到用户需要取出某理财产品详细信息的时候,在进行单表查询。此时就可以通过延迟加载机制来实现。
延迟加载可以做到,先从单表查询,需要时再从关联表关联查询。这样大大提高了数据库的性能,因为查询单表要比关联查询多张表速度快。但是需要注意的是:MyBatis的延迟加载只是对关联对象的查询有延迟设置,对于主家在对象都是直接执行查询语句的。
在上一篇系列博客的测试案例中使用resultMap以及collection标签和association标签实现一对多和多对一映射,而collection、association就具有延迟加载功能。
MyBatis中对于延迟加载的设置,可以应用到一对一、一对多、多对一、多对多的所有关联关系查询中。
2、关联对象加载时机
MyBatis根据对关联对象查询的select语句的执行实际,分为三种类型:直接加载、侵入式延迟加载与深度延迟加载。
直接加载: 执行完对主加载对象的select语句,马上执行关联对象的select查询。
侵入式延迟加载: 执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,就会马上执行关联对象的select查询。即对关联对象的查询执行,侵入到主加载对象的详情访问中。也可以这样理解:将关联对象的详情侵入到了主加载对象的详情中,即将关联对象的详情作为主加载对象的详情的一部分出现。
深度延迟加载: 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。
需要注意的是:延迟加载的应用要求,关联对象的查询与主加载对象的查询必须是分别进行的select语句,不能是使用多表连接所进行的select查询。 因为,多表连接查询,其实质是对一张表的查询,对由多个表连接后形成的一张表的查询,会一次性将多张表的所有信息查询出来。
3、测试环境搭建
本测试案例以一个一对多的关联查询为例演示MyBatis框架中的延迟加载功能。测试案例中具有两个实体,分别为国家实体(country)和部长实体(minister)。其中,一个国家有多个部长,一个部长只属于一个国家,因此国家和部长之间具有一对多的关联关系。
3.1 创建数据表
首先在mybatis数据库中创建名为“country”的数据表并插入测试数据,具体建表和插入测试数据的SQL语句如下所示:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `country`
-- ----------------------------
DROP TABLE IF EXISTS `country`;
CREATE TABLE `country` (
`cid` int(5) NOT NULL auto_increment,
`cname` varchar(20) default NULL,
PRIMARY KEY (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of country
-- ----------------------------
INSERT INTO `country` VALUES ('1', 'USA');
INSERT INTO `country` VALUES ('2', 'England');
然后在mybatis数据库中创建名为“minister”的数据表并插入测试数据,具体建表和插入测试数据的SQL语句如下所示:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `minister`
-- ----------------------------
DROP TABLE IF EXISTS `minister`;
CREATE TABLE `minister` (
`mid` int(5) NOT NULL auto_increment,
`mname` varchar(20) default NULL,
`countryId` int(5) default NULL,
PRIMARY KEY (`mid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of minister
-- ----------------------------
INSERT INTO `minister` VALUES ('1', 'aaa', '1');
INSERT INTO `minister` VALUES ('2', 'bbb', '1');
INSERT INTO `minister` VALUES ('3', 'ccc', '1');
INSERT INTO `minister` VALUES ('4', 'ddd', '2');
INSERT INTO `minister` VALUES ('5', 'eee', '2');
最终,在数据库中形成country表和minister表,具体如下:
3.2 创建实体类
根据数据库中的表结构,在com.ccff.mybatis.model包下创建名为“Country”和“Minister”的两个类,并提供get、set和toString方法。
Country类具体代码如下:
package com.ccff.mybatis.model;
import java.util.Set;
public class Country {
private int cid;
private String cname;
private Set<Minister> ministers;
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public Set<Minister> getMinisters() {
return ministers;
}
public void setMinisters(Set<Minister> ministers) {
this.ministers = ministers;
}
@Override
public String toString() {
return "Country{" +
"cid=" + cid +
", cname='" + cname + '\'' +
", ministers=" + ministers +
'}';
}
}
Minister类具体代码如下:
package com.ccff.mybatis.model;
public class Minister {
private int mid;
private String mname;
public int getMid() {
return mid;
}
public void setMid(int mid) {
this.mid = mid;
}
public String getMname() {
return mname;
}
public void setMname(String mname) {
this.mname = mname;
}
@Override
public String toString() {
return "Minister{" +
"mid=" + mid +
", mname='" + mname + '\'' +
'}';
}
}
3.3 创建接口Dao文件
该演示案例的关联查询需求为:根据国家id查询国家全部信息,并关联查询属于该国家的各个部长信息。
在com.ccff.mybatis.dao包下创建名为“ILazyLoadingDao”的接口Dao文件,并在接口中创建名为“selectCountryById”的方法,利用该方法来进行关联查询演示延迟加载功能。具体代码如下所示:
package com.ccff.mybatis.dao;
import com.ccff.mybatis.model.Country;
public interface ILazyLoadingDao {
//测试延迟加载方法:一对多查询:根据cid查询国家信息
Country selectCountryById(int cid);
}
3.4 创建SQL映射文件
在config/sqlmap文件夹下创建名为“LazyLoadingMapper”的SQL映射文件,添加关联查询SQL语句,通过多表单独查询的方式来实现关联查询。具体配置如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ccff.mybatis.dao.ILazyLoadingDao">
<select id="selectMinisterByCountry" resultType="Minister">
select mid,mname from minister where countryId = #{cid}
</select>
<resultMap id="countryMapper" type="Country">
<id column="cid" property="cid" />
<result column="cname" property="cname" />
<collection property="ministers" ofType="Minister" select="selectMinisterByCountry" column="cid" />
</resultMap>
<select id="selectCountryById" resultMap="countryMapper">
select cid,cname from country where cid = #{cid}
</select>
</mapper>
完成SQL映射文件的配置后,需要把该SQL映射文件的路径配置到MyBatis全局配置文件SqlMapConfig.xml中,具体配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--注册DB连接四要素的属性文件-->
<properties resource="jdbc.properties" />
<!--全局参数设置-->
<settings>
<!-- 配置LOG信息 -->
<setting name="logImpl" value="LOG4J" />
</settings>
<!--配置别名-->
<typeAliases>
<!--<typeAlias type="com.ccff.mybatis.model.User" alias="User"/>-->
<package name="com.ccff.mybatis.model"/>
</typeAliases>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--配置SQL映射文件的位置-->
<mappers>
<mapper resource="sqlmap/UserMapper.xml"/>
<mapper resource="sqlmap/StudentMapper.xml"/>
<mapper resource="sqlmap/BasketballPlayerMapper.xml"/>
<mapper resource="sqlmap/FinacialMapper.xml"/>
<mapper resource="sqlmap/NewsLabelMapper.xml" />
<mapper resource="sqlmap/LazyLoadingMapper.xml" />
</mappers>
</configuration>
3.5 创建测试类
在com.ccff.mybatis.test包下创建名为“LazyLoadingTest”的测试类,并在该类中创建名为“TestSelectCountryById”的测试方法用来测试延迟加载功能,具体代码如下:
@Test
public void TestSelectCountryById(){
System.out.println("语句1执行...");
Country country = lazyLoadingDao.selectCountryById(1); //语句1
System.out.println("语句2执行...");
System.out.println(country.getCname()); //语句2:
System.out.println("语句3执行...");
System.out.println(country.getMinisters().size()); //语句3:
}
4、直接加载
4.1 修改主配置文件
修改MyBatis全局配置文件SqlMapConfig.xml,在setting标签中配置全局属性lazyLoadingEnabled的值只要设置为false,那么,对于关联对象的查询,将采用直接加载。即在查询过主加载对象后,会马上查询关联对象。
lazyLoadingEnabled的默认值为false,即直接加载。具体配置如下:
<!--全局参数设置-->
<settings>
<!-- 配置LOG信息 -->
<setting name="logImpl" value="LOG4J" />
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
4.2 测试直接加载
直接运行测试方法TestSelectCountryById,当程序执行完语句1时,发现已经进行了关联查询,即不仅对country表进行了查询操作,而且还对minister表进行了查询操作。
5、深度延迟加载
5.1 修改主配置文件
修改MyBatis全局配置文件SqlMapConfig.xml,将延迟加载开关lazyLoadingEnabled开启(设置为true),将侵入式延迟加载开关aggressiveLazyLoading关闭(设置为false),具体配置如下:
<!--全局参数设置-->
<settings>
<!-- 配置LOG信息 -->
<setting name="logImpl" value="LOG4J" />
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
5.2 测试深度延迟加载
再次运行测试方法TestSelectCountryById,我们发现在执行语句1时,只对country表进行了查询操作,而并没有对minister表进行查询操作,说明延迟加载已经启动了。对于minister表的查询操作延迟到了当语句3执行时,获取Minister对象详情时才对minister表进行了查询。
6、侵入式延迟加载
6.1 修改主配置文件
修改全局配置文件的setting标签,将延迟加载开关lazyLoadingEnabled和侵入式延迟加载开关aggressiveLazyLoading的value值全部设置为true,即全部设置为开启,具体配置如下:
<!--全局参数设置-->
<settings>
<!-- 配置LOG信息 -->
<setting name="logImpl" value="LOG4J" />
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
5.2 测试侵入式延迟加载
再次直接运行测试方法TestSelectCountryById,我们发现当程序执行到语句1时,仅对country表(查询主体表)进行了查询,并没有对minister表进行查询,说明延迟加载已经启动了。
但当执行到语句2处时,即开始对Country对象进行访问时,与深度延迟加载不同的是,侵入式延迟加载在此时开始对minister表进行了查询,尽管此时还没有使用Minister对象。这是因为侵入式加载的策略已经将主加载对象的关联属性值也作为主加载对象的基本信息了,而前面已经查询出了主加载对象的基本信息,但其关联对象基本信息尚无。所以,马上对minister表进行了查询。
换个角度来说,该延迟加载策略使关联对象的数据侵入到了主加载对象的数据中,所以称为侵入式延迟加载。
需要注意的是,该延迟策略也是一种延迟加载,需要在延迟加载开关lazyLoadingEnabled开启时才会起作用。若lazyLoadingEnabled为false,则aggressiveLazyLoading无论取何值均不起作用。
7、延迟加载策略总结
加载策略 | lazyLoadingEnabled取值 | aggressiveLazyLoading取值 |
---|---|---|
直接加载 | false | false |
深度延迟加载 | true | false |
侵入式延迟加载 | true | true |