1. 导入MyBatis依赖
<dependencies>
<!--mybatis依赖包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<!--mysql数据库连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--单元测试Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
- 注意:在build中配置resources,来防止资源导出失败的问题
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
2. MyBatis 核心配置
- 创建 MyBatis 的配置⽂件 mybatis-config.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>
<!--配置MyBatis运行环境-->
<environments default="dev">
<environment id="dev">
<!--配置JDBC事务管理-->
<transactionManager type="JDBC"/>
<!--POOLED配置JDBC数据源连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
</dataSource>
</environment>
</environments>
</configuration>
3. 基于SqlSession操作数据库
- 新建数据表
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`age` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
- 关系映射实体类User
public class User {
private int id;
private String username;
private String password;
private int age;
getter()...
setter()...
tostring()...
}
- 创建USerMapper.xml
<?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.yauyukbiu.demo.dao.UserDao">
<select id="getAll" resultType="com.yauyukbiu.demo.entity.User">
select * from user
</select>
</mapper>
- 在全局配置⽂件 mybatis-config.xml 中注册 UserMapper.xml
<!--注册mapper.xml-->
<mappers>
<mapper resource="com/yauyukbiu/demo/dao/UserMapper.xml"/>
</mappers>
- 从 XML 中构建 SqlSessionFactory,使用mybatis原生接口
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
//加载xml配置文件
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
//封装获取SqlSession
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
- 测试查询结果
public interface UserDao {
List<User> getAll();
}
@Test
public void test() {
//获得sqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//使用和指定语句的参数和返回值相匹配的接口
UserDao mapper = sqlSession.getMapper(UserDao.class);
//执行查询方法
List<User> userList = mapper.getAll();
for (User user : userList) {
System.out.println(user.toString());
}
//关闭SqlSession
sqlSession.close();
}
4. 作用域(Scope)和生命周期
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder
- 可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
- 局部变量
SqlSessionFactory
- 数据库连接创建工厂,也就是相当于数据库连接池
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
- SqlSessionFactory 的最佳作用域是应用作用域。
- 使用单例模式或者静态单例模式。
SqlSession
- 连接到连接池的请求
- SqlSession 的实例不是线程安全的,因此是不能被共享的
- 最佳的作用域是请求或方法作用域
- 用完之后需要关闭连接,否则将一直占用数据库连接资源,造成资源浪费
5. #{ } 和 ${ }
#{}
是预编译处理
Mybatis 在处理#{}
时,会将 sql 中的#{}替换为?号,创建PreparedStatement
的参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)
${}
是字符串替换
Mybatis 在处理${}
时,就是把${}
替换成变量的值
注意: 使${}
用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击;使用#{}可以有效的防止 SQL 注入,提高系统安全性
6. 级联查询(结果映射)
结果映射(resultMap)
- constructor - 用于在实例化类时,注入结果到构造方法中
- idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
- arg - 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能- result – 注入到字段或 JavaBean 属性的普通结果
association
– 一个复杂类型的关联;许多结果将包装成这种类型嵌套结果映射 – 关联可以是 resultMap 元素,或是对其它结果映射的引用collection
– 一个复杂类型的集合嵌套结果映射 – 集合可以是 resultMap 元素,或是对其它结果映射的引用- discriminator – 使用结果值来决定使用哪个
- resultMapcase – 基于某些值的结果映射嵌套结果映射 – case 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射ResultMap 的属性列表
6.1 一对一
- association(关联)
@Data
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private Address address;//地址:关联对象
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
关联类
@Data
@NoArgsConstructor
public class Address {
private int userId;
private String country;
private String city;
}
- resultMap
关联对象:
association
关联对象的类型:javaType
<resultMap id="userMap" type="com.yauyukbiu.demo.entity.User">
<id property="id" column="id"/>
<id property="username" column="username"/>
<id property="password" column="password"/>
<association property="address" javaType="com.yauyukbiu.demo.entity.Address">
<id property="userId" column="user_id"/>
<id property="country" column="country"/>
<id property="city" column="city"/>
</association>
</resultMap>
- select语句
//获取所有用户信息
<select id="getAll" resultMap="userMap">
select * from user left join address on id=user_id;
</select>
6.2 一对多
- collection(集合)
@Data
@NoArgsConstructor
public class User {
private int id;
private String username;
private String password;
private List<PhoneNumber> phoneNumber;//多值属性
}
多值属性表
@Data
@NoArgsConstructor
public class PhoneNumber {
private int userId;
private int number;
}
- resultMap
集合:
collection
集合中的对象类型:ofType
<collection property="phoneNumber" ofType="com.yauyukbiu.demo.entity.PhoneNumber">
<id property="userId" column="id"/>
<id property="number" column="number"/>
</collection>
- select语句
<select id="getAll" resultMap="userMap">
select * from user left join phone_number on user.id=phone_number.id
</select>
7. 日志门面
- 日志工厂
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
- STDOUT_LOGGING ——>mybatis标准日志工厂
- SLF4J
需要slf4j-api
支持以及slf4j
的日志实现(推荐使用logback
)
- pom.xml添加日志依赖
- mybatis设置日志类型
- 日志打印结果
8. 分页
思考:为什么要分页?
节约资源优化查询性能,避免一次查询大量数据,增大数据库的压力
8.1 使用Limit分页
语法:
SELECT * FROM table_ LIMIT startIndex, pageSize
startIndex: 索引下标,mysql默认从第0个
开始查询
pageSize: 查询的数据个数
//业务场景:分页查询用户
public interface UserDao {
//分页demo
List<User> getAll(@Param("CurrentPageOn") int CurrentPageOn,
@Param("pageSize") int pageSize);
}
UserMapper.xml:编写对应dao的业务sql语句
<select id="getAll" resultMap="userMap">
select *
from user u
left join address a on u.id = a.user_id
limit #{CurrentPageOn},#{pageSize};
</select>
test
@Test
public void test() {
//获取SqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//使用和指定语句的参数和返回值相匹配的接口
UserDao mapper = sqlSession.getMapper(UserDao.class);
//查询获取全部用户
//分页---------------
int pageOn = 1;//查询页数
int pageSize = 5;//每页查询的数据个数
int CurrentPageOn = ((pageOn) - 1) * pageSize;//当前页数,limit默认从0开始
List<User> userList = mapper.getAll(CurrentPageOn,pageSize);
for (User user : userList) {
System.out.println(user.toString());
}
//关闭SqlSession
sqlSession.close();
}
8.2 RowBounds
- 通过java代码层面实现分页
mybatis提供的SqlSession
接口查询方法selectList
sqlSession.selectList(String var1, Object var2, RowBounds var3);
返回值:list< Object >
sql语句不进行分页,由mybatis执行分页操作
<!--RowBounds分页-->
<select id="getAllByRowBounds" resultMap="userMap">
select *
from user u
left join address a on u.id = a.user_id
</select>
test
//获取SqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
//RowBounds:从第几个开始,获取数据的总数
RowBounds rowBounds = new RowBounds(0, 5);
List<User> userList = sqlSession.selectList("com.yauyukbiu.demo.dao.UserDao.getAllByRowBounds",
null, rowBounds);
for (User user : userList) {
System.out.println(user.toString());
}
//关闭SqlSession
sqlSession.close();
8.3 分页插件(PageHelper)
分页插件的原理:实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。
举例:select * from student,拦截 sql 后重写为:select t.* from (select * from student)tlimit 0,10
PageHelper官方文档:https://pagehelper.github.io/docs/
9. 使用注解开发
10. MyBatis 动态 SQL
- mybatis动态sql是做什么的?
Mybatis 动态 sql 可以让我们在 xml 映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能
- 有哪些动态sql
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
- bind
- 动态sql的执行原理
使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
if
使用动态 SQL 最常见情景是根据条件包含 where
子句的一部分
<select id="findUser" resultType="com.yauyukbiu.demo.entity.User">
SELECT * FROM user
WHERE id=#{id}
<if test="username!=null">
AND username=#{username}
</if>
</select>
choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="findUserByChoose" resultType="com.yauyukbiu.demo.entity.User">
SELECT * FROM user
<where>
<choose>
<when test="id!=0">
id=#{id}
</when>
<when test="username!=null">
username=#{username}
</when>
<otherwise>
username='令狐冲' //当没有条件匹配时,执行查询username=‘令狐冲’的用户
</otherwise>
</choose>
</where>
</select>
//id=0,username=null:不执行when语句,执行otherwise语句
List<User> userList = userDao.findUserByChoose(0 ,null);
//id=10
List<User> userList = userDao.findUserByChoose(10 ,null);
choose语句只执行第一个满足when的子条件
trim、where、set
- trim
trim
标签中的 prefix
和 suffix
属性会被⽤于⽣成实际的 SQL 语句,会和标签内部的语句进⾏拼接,如果语句前后出现了 prefixOverrides
或者 suffixOverrides
属性中指定的值,MyBatis 框架会⾃动将其删除。
prefix:前缀
suffix:后缀
select查询语句使用trim动态拼接
<select id="findByAccount" parameterType="com.demo.entity.Account"
resultType="com.southwind.entity.Account">
select * from t_account
<trim prefix="where" prefixOverrides="and">//prefixOverrides若下边拼接的sql语句前缀为and则将其删除
<if test="id!=0">
id = #{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
<if test="age!=0">
and age = #{age}
</if>
</trim>
</select>
insert插入语句使用trim动态拼接
<insert id="addUser" parameterType="User" useGeneratedKeys="true">
INSERT INTO user
<trim prefix="(" suffix=")" suffixOverrides=",">//suffixOverrides去掉最后一个”,“
<if test="loginName != null">login_name,</if>
<if test="email != null">email,</if>
<if test="phone != null">phone,</if>
<if test="password != null">`password`,</if>
<if test="status != null">`status`,</if>
<if test="icon != null">icon,</if>
<if test="role != null">role,</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">//suffixOverrides去掉最后一个”,“
<if test="loginName != null">#{loginName},</if>
<if test="email != null">#{email},</if>
<if test="phone != null">#{phone},</if>
<if test="password != null">#{password},</if>
<if test="status != null">#{status},</if>
<if test="icon != null">#{icon},</if>
<if test="role != null">#{role},</if>
</trim>
</insert>
- where
- 动态的匹配
where
条件语句 - 假如第一条条件查询不匹配,则后面的若干条件语句智能去掉首个
and
连接符
where
元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
<select id="findUser" resultType="com.yauyukbiu.demo.entity.User">
SELECT * FROM user
<where>
<if test="id!=0">
id=#{id}
</if>
<if test="username!=null">
AND username=#{username}
</if>
</where>
</select>
使用普通where执行多条件查询时的问题↓
- set
set
标签⽤于update
操作,会⾃动根据参数选择⽣成 SQL 语句
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
foreach
标签可以迭代⽣成⼀系列值,这个标签主要⽤于 SQL 的in
语句
可以将任何可迭代对象(如 List
、Set
等)、Map
对象或者Array
数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
普通sql语句
SELECT * FROM `user` WHERE id in(1,5,7,9,11,16)
foreach动态拼接语句
<select id="findUserByIds" parameterType="_int" resultType="com.demo.entity.User">
SELECT * FROM user
<where>
<foreach collection="array" item="id"
open="id in(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
test
List<User> findUserByIds(int[] ids);
//获取sqlSession连接
sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
int[] ids = new int[]{5, 6, 8, 12, 15,};
List<User> userList = userDao.findUserByIds(ids);
if (!userList.isEmpty()) {
for (User user : userList) {
System.out.println(user.toString());
}
}
SQL片段
将sql语句中重复的sql片段提取出来放置为公共的,便于复用该sql片段
<sql id="if-id-username">//sql片段id
<if test="id!=0">
id=#{id}
</if>
<if test="username!=null">
AND username=#{username}
</if>
</sql>
调用某一sql片段时,只需添加include
标签即可
refid
为sql片段映射id
<select id="findUserByWhere" resultType="com.demo.entity.User">
SELECT * FROM user
<where>
<include refid="if-id-username"/>
</where>
</select>
bind
bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文.
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
11. MyBatis 缓存
- 什么是缓存【Cache】?
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存[内存]中,当用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中直接获取存在的数据,从而提高查询效率,解决了高并发系统的性能问题。 - 为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率 - 什么样的数据推荐使用缓存?
- 经常查询并且不经常改变的数据,热点数据
11.1 MyBatis 缓存分类
1、⼀级缓存:SqlSession 级别,默认开启,并且不能关闭。
操作数据库时需要创建 SqlSession 对象,在对象中有⼀个 HashMap ⽤于存储缓存数据,不同的
SqlSession 之间缓存数据区域是互不影响的。
⼀级缓存的作⽤域是 SqlSession 范围的,当在同⼀个 SqlSession 中执⾏两次相同的 SQL 语句事,第⼀次执⾏完毕会将结果保存到缓存中,第⼆次查询时直接从缓存中获取。
需要注意的是,如果 SqlSession 执⾏了 DML 操作(insert、update、delete),MyBatis 必须将缓存
清空以保证数据的准确性。
2、⼆级缓存:Mapper 级别,默认关闭,可以开启。
使⽤⼆级缓存时,多个 SqlSession 使⽤同⼀个 Mapper 的 SQL 语句操作数据库,得到的数据会存在⼆级缓存区,同样是使⽤ HashMap 进⾏数据存储,相⽐较于⼀级缓存,⼆级缓存的范围更⼤,多个
SqlSession 可以共⽤⼆级缓存,⼆级缓存是跨 SqlSession 的。
⼆级缓存是多个 SqlSession 共享的,其作⽤域是 Mapper 的同⼀个 namespace,不同的 SqlSession两次执⾏相同的 namespace 下的 SQL 语句,参数也相等,则第⼀次执⾏成功之后会将数据保存到⼆级缓存中,第⼆次可直接从⼆级缓存中取出数据
11.2 一级缓存
每次创建sqlSession默认开启一级缓存,该缓存仅在当前会话内有效
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(
LRU
, Least Recently Used)算法来清除不需要的缓存。 - 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
11.3 二级缓存
MyBatis ⾃带的⼆级缓存
- mybatis-config,xml 配置开启二级缓存
<setting name="cacheEnabled" value="true"/>
- Mapper.xml 中配置⼆级缓存
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的清除策略有:
LRU
– 最近最少使用:移除最长时间不被使用的对象。
FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。
SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU
。
flushInterval
(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size
(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
readOnly
(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
提示: 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。
- 需要实体类实现序列化接⼝
@Data
public class User implements Serializable {
private int id;
private String username;
private String password;
private Address address;
private List<PhoneNumber> phoneNumber;//多值属性
}
自定义缓存——ehcache
- pom.xml 添加相关依赖
<!--ehcache二级缓存-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.5</version>
</dependency>
- 添加 ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<diskStore/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
overflowToDisk="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
- config.xml 配置开启⼆级缓存
<!--设置日志-->
<setting name="logImpl" value="SLF4J"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
- Mapper.xml中设置二级缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<!-- 缓存创建之后,最后⼀次访问缓存的时间⾄缓存失效的时间间隔 -->
<property name="timeToIdleSeconds" value="3600"/>
<!-- 缓存⾃创建时间起⾄失效的时间间隔 -->
<property name="timeToLiveSeconds" value="3600"/>
<!-- 缓存回收策略,LRU表示移除近期使⽤最少的对象 -->
<property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>
- 实体类不需要实现序列化接⼝
@Data
public class User {
private int id;
private String username;
private String password;
private Address address;
private List<PhoneNumber> phoneNumber;//多值属性
}
12. MyBatis 延迟加载
- 什么是延迟加载?
延迟加载也叫懒加载、惰性加载,使⽤延迟加载可以提⾼程序的运⾏效率,针对于数据持久层的操作,在某些特定的情况下去访问特定的数据库,在其他情况下可以不访问某些表,从⼀定程度上减少了 Java应⽤与数据库的交互次数
查询学⽣和班级的时,学⽣和班级是两张不同的表,如果当前需求只需要获取学⽣的信息,那么查询学⽣单表即可,如果需要通过学⽣获取对应的班级信息,则必须查询两张表。
不同的业务需求,需要查询不同的表,根据具体的业务需求来动态减少数据表查询的⼯作就是延迟加载
在 config.xml 中开启延迟加载
<settings>
<!--设置日志-->
<setting name="logImpl" value="SLF4J"/>
<!--开启懒加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
将多表关联查询拆分成多个单表查询
实体类User
@Data
public class User {
private int id;
private String username;
private String password;
private Address address;//与Address类关联
}
UserDao
User findUserByLazy(int id);
UserMapper.xml
<!--懒加载查询(多表嵌套查询)-->
<resultMap id="userMapLazy" type="com.demo.entity.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<association property="address" javaType="com.demo.entity.Address"
select="com.demo.dao.AddressDao.findAddressByLazy" column="id"/>
</resultMap>
<select id="findUserByLazy" parameterType="int" resultMap="userMapLazy">
SELECT * FROM user where id=#{id}
</select>
关联实体类Address
@Data
@NoArgsConstructor
public class Address {
private int userId;
private String country;
private String city;
}
AddressDao
Address findAddressByLazy(int id);
AddressMapper.xml
<select id="findAddressByLazy" parameterType="int" resultType="com.demo.entity.Address">
select * from address where user_id=#{id}
</select>
懒加载测试
1、业务场景一:只需要查询用户的用户名
@Test
public void test() {
//获取sqlSession连接
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findUserByLazy(5);
System.out.println(user.getUsername());//只需要用户的username
}
结果
从结果看出:由于用户的username在user表中,并不需要关联表的信息;所以只查询了一张表
2、业务场景二:需要查询用户的所有信息,包括关联信息
@Test
public void test() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
User user = userDao.findUserByLazy(5);
System.out.println(user.toString());//打印用户所有信息
}
结果
从结果得出:获取用户所有信息走了嵌套查询来获取关联类Address的信息