MyBatis延迟加载
Lazy Loading(延迟加载)是一种优化数据获取的机制
在需要用到数据时才进行加载,不需要用到数据时就不加载数据。
延迟加载也称懒加载
合理使用 MyBatis 的延迟加载机制,可以根据实际业务需求,有效地优化数据的获取和处理,提升系统的整体性能。
优点:
减少不必要的数据查询,降低数据库的压力
在处理复杂关系时提高系统性能
缺点:
可能会增加一定的代码复杂性,需要适当配置和处理
如果配置不当,可能会导致多次数据库查询,反而降低性能
全局开启
MyBatisConfig.xml:
<!--开启全局的懒加载(默认值:false)-->
<setting name="lazyLoadingEnabled" value="true"/>
局部开启
一对一
在Mapper.xml中对单个SQL语句开启
DeptDao.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.xxx.dao.DeptDao">
<select id="findById" resultType="dept">
select * from dept where id=#{id}
</select>
</mapper>
EmpDao.xml:
<resultMap id="empAllMap" type="com.xxx.entity.Emp">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="age" property="age"></result>
<result column="addr" property="addr"></result>
<result column="salary" property="salary"></result>
<!--
fetchType: 是否开启懒加载
lazy: 开启
eager: 立即加载
-->
<association property="dept" javaType="com.xxx.entity.Dept"
select="com.xxx.dao.DeptDao.findById" column="dept_id" fetchType="lazy">
</association>
</resultMap>
<select id="findAll" resultMap="empAllMap">
select * from emp
</select>
参数:
select:使用SQL方法名
column:当前SQL查询结构中作为后续SQL语句中的参数的列
fetchType: 是否开启懒加载
一对多
EmpDao.xml:
<select id="findByDeptId" resultType="emp">
select * from emp where dept_id=#{deptId}
</select>
DeptDao.xml:
<resultMap id="deptResultMap" type="dept">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="location" property="location"></result>
<!--
property: dept中的需要映射的属性名称
ofType: 集合中泛型的类型
column: 在结果集中选择column这一列当做参数传递到select指定的方法中
fetchType: 是否延迟加载 lazy: 延迟加载 eager: 立即加载
select: 映射empList的方法名
-->
<collection property="empList" ofType="com.xxx.entity.Emp"
column="id" fetchType="lazy"
select="com.xxx.dao.EmpDao.findByDeptId">
<result column="name" property="name"></result>
<result column="addr" property="addr"></result>
<result column="salary" property="salary"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="deptResultMap">
select * from dept;
</select>
MyBatis缓存机制:
当一级缓存和二级缓存同时存在时,先查询二级缓存再查询一级缓存
都没有才会查询数据库,再将数据的数据存入一级缓存
一级缓存
基于session级别的缓存,也称为本地缓存。默认开启,作用域是当前Session。
当session flush或close后,该session中的一级缓存将被清空
默认开启,但不能被关闭,可以调用clearCache() 来清空一级缓存
多次查询同一语句的结果集存入的内容是内存地址值
清空一级缓存:
localCacheScope
将一级缓存的作用域设置为语句级别(localCacheScope设置为STATEMENT)
MyBatisConfig.xml
<!--日志配置-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--
更改一级缓存的作用域
SESSION: 会话级别缓存,默认值
STATEMENT: 语句级别缓存,缓存只对当前执行的语句生效(类似于关闭一级缓存)
-->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
clearCache
测试代码:
@Test
public void test2() throws Exception { // 测试clearCache
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 首先从一级缓存里面查询,没查询到,然后去数据库查询,将查询的结果放入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
session.clearCache(); // 清空一级缓存
// 首先从一级缓存里面查询(没有),去数据库查询,将查询的结果放入一级缓存
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // false
// 关闭session(一级缓存清空)
session.close();
}
flushCache
执行完当前语句是否清空缓存,默认为false
EmpDao.xml:
<mapper namespace="com.xxx.dao.EmpDao">
<!--
flushCache:
true: 每次执行完这个查询语句都清空一级缓存
false: 默认值,执行完该SQL语句不清空一级缓存
-->
<select id="findById" resultType="emp" flushCache="true">
select * from emp where id=#{id}
</select>
</mapper>
增删改:
在MyBatis中,执行任何的增删改都会导致一级缓存清空
增删改会导致select标签中的flushCache参数(false)失效,依旧清空一级缓存
@Test
public void test5() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 将查询结果放入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
Emp saveEmp = new Emp();
saveEmp.setName("test");
mapper.save(saveEmp); // 任何的增删改都会导致缓存失效
// 首先从一级缓存里面查询(没有),去数据库查询,将查询的结果放入一级缓存
Emp emp2 = mapper.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // false
// 关闭session(一级缓存清空)
session.close();
}
session关闭
测试代码:
@Test
public void test4() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 将查询的结果放入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
// session关闭一级缓存清空(session关闭了代表与数据库的会话一级结束了,不可以再发送任何的SQL语句了)
session.close();
// 重新获取一次会话
SqlSession session2 = factory.openSession();
EmpDao mapper2 = session2.getMapper(EmpDao.class);
// 新的session的一级缓存中并没有数据,发送SQL去数据库查询
Emp emp2 = mapper2.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2);
// session关闭,一级缓存清空
session2.close();
}
一级缓存的引用问题
当多次查询同一SQL语句时,MyBatis会优先查询一级缓存中的内容,并且引用地址值给后续查询的结果集
这就导致了当其中之一的结果集对内容进行修改时,会间接修改其他结构集的值
二级缓存
在全局设置中默认开启(支持),但是还需要在Mapper文件中单独开启,作用域是当前Mapper文件
当一级缓存的session关闭的时候,会将一级缓存中的内容存入(序列化)二级缓存
因为一级缓存的内容是序列化到二级缓存的,因此存入二级缓存的对象需要实现序列化接口(Serializable),当查询到二级缓存中有对应数据时再反序列化到结果集中
开启Mapper的二级缓存
MyBatisConfig.xml:
<settings>
<!--日志配置-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--开启二级缓存,默认是开启状态,false为关闭二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
Mapper.xml
<mapper namespace="com.dfbz.dao.EmpDao">
<!--
<cache />: 开启当前Mapper的二级缓存
eviction: 缓存的回收策略
• LRU – 最近最少使用的:移除最长时间不被使用的对象。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval:缓存刷新间隔,缓存多长时间清空一次,默认不清空,可以通过此参数设置一个毫秒值
readOnly:是否只读
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。
mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:二级缓存中最大能够缓存存放多少元素
type:指定自定义缓存的全类名(实现Cache接口)
-->
<cache />
<!--
useCache: 当一级缓存关闭时,是否将本次SQL语句的结果集存入二级缓存
true: 存入(默认值)
false: 不存入
-->
<select id="findById" resultType="emp">
select * from emp where id=#{id}
</select>
<insert id="save" >
insert into emp(id,name) values(null,#{name});
</insert>
</mapper>
二级缓存的清空/失效
useCache
可以在select标签中添加参数useCache以用于当前SQL语句是否存入二级缓存,默认为true
<select id="findById" resultType="emp" useCache="true">
select * from emp where id=#{id}
</select>
增删改
会清空一级缓存也会清空二级缓存
flushCache
让当前SQL语句执行完后决定是否清空缓存,默认为false
会清空一级缓存也会清空二级缓存
增删改不会使该参数(false)失效,依旧保留二级缓存
PerpetualCache缓存类
MyBatis 跟缓存相关的类都在cache 包里面,其中有一个Cache 接口,
只有一个默认的实现类 PerpetualCache,该类是MyBatis的缓存实现类;
包括一级缓存和二级缓存都是采用PerpetualCache类来实现的
一级缓存和二级缓存都在MyBatis加载时(创建session工厂时)创建,不过只在session关闭时才会将此次session的一级缓存序列号写入二级缓存,再将一级缓存清空
执行任意增删改操作或者flushCache时销毁
重写二级缓存
Redis替换二级缓存
Cache接口
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
default ReadWriteLock getReadWriteLock() {
return null;
}
}
自定义缓存
依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
重写Cache接口
public class MyCache implements Cache {
private Jedis jedis = new Jedis();
private String id;
public MyCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
/**
* 添加缓存
* @param key: 缓存的key
* @param val: 需要存储到缓存的值(是一个List类型)
*/
@Override
public void putObject(Object key, Object val) {
jedis.set(key.toString(), JSON.toJSONString(val));
System.out.println("putObject..............");
}
/**
* 获取缓存中的内容
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
System.out.println("getObject................");
String jsonStr = jedis.get(key.toString());
List<Emp> list = JSON.parseArray(jsonStr, Emp.class);
return list;
}
/**
* 删除缓存
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
System.out.println("removeObject................");
return jedis.del(key.toString());
}
/**
* 清空缓存
*/
@Override
public void clear() {
System.out.println("clear................");
jedis.flushDB();
}
/**
* 获取缓存中的key数量
* @return
*/
@Override
public int getSize() {
System.out.println("getSize................");
return Integer.parseInt(jedis.dbSize() + "");
}
}
Emp.xml
<mapper namespace="com.dfbz.dao.EmpDao">
<!--替换为我们自定义的缓存-->
<cache type="com.dfbz.cache.MyCache"></cache>
<select id="findById" resultType="Emp">
select * from emp where id=#{id}
</select>
</mapper>
测试类
public class Demo01 {
SqlSessionFactory factory;
@Before
public void before() throws Exception {
factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("MyBatisConfig.xml"));
}
@Test
public void test1() throws Exception {
// 开启一级缓存
SqlSession session = factory.openSession();
EmpDao mapper = session.getMapper(EmpDao.class);
// 先从二级缓存里面查询,再从一级缓存里面查询,再从数据库查询(发送SQL),将查询到的结果集存入一级缓存
Emp emp = mapper.findById(1);
System.out.println(emp);
// 关闭一级缓存,把数据写入二级缓存(写入到redis)
session.close();
// 开启一级缓存
SqlSession session2 = factory.openSession();
EmpDao mapper2 = session2.getMapper(EmpDao.class);
// 先从二级缓存里面获取(从redis里面获取)
Emp emp2 = mapper2.findById(1);
System.out.println(emp2);
System.out.println(emp == emp2); // false ,两个对象的属性值一样,但是内存地址值不一样(反序列化出来的)
session2.close();
}
}
MyBatis注解开发
替代xml
注解 | 功能 |
---|---|
@Insert | 新增 |
@Update | 更新 |
@Delete | 删除 |
@Select | 查询 |
@Result | 结果集封装 |
@Results | 与@Result 一起使用,封装多个结果集 |
@One | 一对一结果集封装 |
@Many | 一对多结果集封装 |
增删改查:
public interface UserDao {
//查
@Select("select * from user where id=#{id}")
User findById(int id);
//查
@Select("select * from user")
List<User> findAll();
//增
@Insert("insert into user values(null,#{username},#{birthday},#{sex},#{address})")
void save(User user);
//改
@Update("update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}")
void update(User user);
//删
@Delete("delete from user where id=#{id}")
void delete(int id);
}
一对一映射:
public interface EmpDao {
//查
@Select("select * from emp where id=#{id}")
//取结果集
@Results({
//封装结果集
@Result(id = true, property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "age", column = "age"),
@Result(property = "addr", column = "addr"),
@Result(property = "salary", column = "salary"),
@Result(property = "dept", column = "dept_id", javaType = Dept.class,
//一对一结果集封装,开启懒加载
one = @One(select = "com.dfbz.dao.DeptDao.findById",fetchType = FetchType.LAZY)
),
})
Emp findById(Integer id);
}
一对多映射:
DeptDao:
//查
@Select("select * from dept where id=#{id}")
//取结果集
@Results({
//结果集封装
@Result(id = true,property = "id",column = "id"),
@Result(property = "name",column = "name"),
@Result(property = "location",column = "location"),
@Result(property = "empList",column = "id",javaType = List.class,
//一对多封装,开启懒加载
many = @Many(select = "com.dfbz.dao.EmpDao.findByDeptId",fetchType = FetchType.LAZY)
)
})
Dept findDeptAllById(Integer id);
EmpDao:
@Select("select * from emp where dept_id=#{deptId}")
List<Emp> findByDeptId(Integer deptId);