【MyBatis】Mybatis 延迟加载
1. 延迟加载简介
MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力。MyBatis 的延迟加载只是对关联对象的查询有迟延设置,对于主加载对象都是直接执行查询语句的。
- 好处: 先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
- 坏处:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
2. 关联对象加载时机
MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载、侵入式延迟加载与深度延迟加载。
- 直接加载:执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询。
- 侵入式延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询。
- 深度延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。
需要注意的是, 延迟加载的应用要求,关联对象的查询与主加载对象的查询必须是分别进行的 select 语句,不能是使用多表连接所进行的select查询。因为多表连接查询,其实质是对一张表的查询,对由多个表连接后形成的一张表的查询。会一次性将多张表的所有信息查询出来。
MyBatis中对于延迟加载设置,只对于resultMap中的collection和association起作用,可以应用到一对一、一对多、多对一、多对多的所有关联关系查询中。
下面以一对多关联关系查询为例,讲解 MyBatis 中的延迟加载应用。
3. 直接加载
- 主配置文件SqlMapConfig.xml中设置lazyLoadingEnabled为false,默认为false。(因为默认式false,所以也可以不设置lazyLoadingEnabled)
<?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>
<!-- 配置properties -->
<properties resource="jdbcConfig.properties"></properties>
<!-- 配置参数 -->
<settings>
<!-- 关闭mybatis支持延迟加载 -->
<setting name="lazyLoadingEnabled" value="false"/>
</settings>
<!-- 使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<package name="com.siyi.domain"></package>
</typeAliases>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池 -->
<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>
<!-- 映射配置文件 -->
<mappers>
<mapper resource="com/siyi/dao/IUserDao.xml"/>
<mapper resource="com/siyi/dao/IAccountDao.xml"/>
</mappers>
</configuration>
- dao映射配置文件
<?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.siyi.dao.IUSerDao">
<!-- 定义User的resultMap -->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<collection property="accounts" ofType="account" select="com.siyi.dao.IAccountDao.findAccountByUid" column="id"/>
</resultMap>
<!-- 查询所有 -->
<select id="findAll" resultMap="userAccountMap">
<!-- select * from user; -->
select * from user
</select>
</mapper>
- 测试
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private IUSerDao iuSerDao;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取DAO的代理对象
iuSerDao = session.getMapper(IUSerDao.class);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
//事务提交
//session.commit();
//释放资源
session.close();
in.close();
}
/**
* 测试查询所有
* @throws IOException
*/
@Test
public void testFindAll() throws IOException {
//5.执行查询所有方法
List<User> users = iuSerDao.findAll();
}
}
运行过程中就查询了所有的信息:
4. 侵入式延迟加载
主配置文件SqlMapConfig.xml中设置lazyLoadingEnabled为true,默认为false,并且将aggressiveLazyLoading设置为true。(aggressiveLazyLoading默认值为false (在 3.4.1 及之前的版本中默认为 true))
<?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>
<!-- 配置properties -->
<properties resource="jdbcConfig.properties"></properties>
<!-- 配置参数 -->
<settings>
<!-- 开启mybatis支持延迟加载 -->
<setting name="lazyLoadingEnabled" value="false"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
<!-- 使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<package name="com.siyi.domain"></package>
</typeAliases>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池 -->
<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>
<!-- 映射配置文件 -->
<mappers>
<mapper resource="com/siyi/dao/IUserDao.xml"/>
<mapper resource="com/siyi/dao/IAccountDao.xml"/>
</mappers>
</configuration>
- dao映射配置文件不变
- 测试
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private IUSerDao iuSerDao;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取DAO的代理对象
iuSerDao = session.getMapper(IUSerDao.class);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
//事务提交
//session.commit();
//释放资源
session.close();
in.close();
}
/**
* 测试查询所有
* @throws IOException
*/
@Test
public void testFindAll() throws IOException {
//5.执行查询所有方法
List<User> users = iuSerDao.findAll();
}
}
没有使用主对象的属性(详情)时,只执行了主对象的查询。
- 修改测试方法为
@Test
public void testFindAll() throws IOException {
//5.执行查询所有方法
List<User> users = iuSerDao.findAll();
System.out.println(users.get(0).getUsername());
}
在执行主对象属性(详情)的时候,执行关联对象:
5. 深度延迟加载
主配置文件SqlMapConfig.xml中设置lazyLoadingEnabled为true,默认为false,并且将aggressiveLazyLoading设置为false。(aggressiveLazyLoading默认值为false (在 3.4.1 及之前的版本中默认为 true))
<?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>
<!-- 配置properties -->
<properties resource="jdbcConfig.properties"></properties>
<!-- 配置参数 -->
<settings>
<!-- 开启mybatis支持延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<package name="com.siyi.domain"></package>
</typeAliases>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池 -->
<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>
<!-- 映射配置文件 -->
<mappers>
<mapper resource="com/siyi/dao/IUserDao.xml"/>
<mapper resource="com/siyi/dao/IAccountDao.xml"/>
</mappers>
</configuration>
- dao映射配置文件不变
- 测试
package com.siyi.test;
import com.siyi.dao.IUSerDao;
import com.siyi.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class UserTest {
private InputStream in;
private SqlSession session;
private IUSerDao iuSerDao;
@Before//用于在测试方法执行之前执行
public void init() throws IOException {
//1.读取配置文件生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.获取SqlSession对象
session = factory.openSession(true);
//4.获取DAO的代理对象
iuSerDao = session.getMapper(IUSerDao.class);
}
@After//用于在测试方法之后执行
public void destroy() throws IOException {
//事务提交
//session.commit();
//释放资源
session.close();
in.close();
}
/**
* 测试查询所有
* @throws IOException
*/
@Test
public void testFindAll() throws IOException {
//5.执行查询所有方法
List<User> users = iuSerDao.findAll();
System.out.println(users.get(0).getUsername());
}
}
没有用到关联对象时,只查询主对象一次
- 修改测试方法
@Test
public void testFindAll() throws IOException {
//5.执行查询所有方法
List<User> users = iuSerDao.findAll();
System.out.println(users.get(0).getAccounts());
}
用到关联对象数据的时候执行关联对象的查询。
6. 延迟加载总结
-
延迟加载一般就是按需加载,在需要查询的时候再去查询,使用延迟加载可以避免表连接查询,表连接查询比单表查询的效率低,但是它需要多次与数据库进行交互,所以延迟加载使用需谨慎。
-
关于延迟加载有两个重要的设置:
lazyLoadingEnabled
表示延迟加载是否开启,如果设置为true
表示开启,此时还需要设置aggressiveLazyLoading
为false
,才能做到按需加载,如果aggressiveLazyLoading
设置为true
则按需加载关闭,此时只要加载了某个属性就会将所有属性都加载。
lazyLoadingEnabled
的默认值为false
aggressiveLazyLoading
的默认值为true
注意: 延迟加载的应用要求,关联对象的查询与主加载对象的查询必须是分别进行的 select 语句,不能是使用多表连接所进行的select查询。