七、 查询优化
7.1 延迟加载
什么是延迟加载
-
根据主表信息去关联查询从表信息时,如果需要从表信息,再去数据库进行查询,如果不需要,则使用代理对象。
延迟加载就是懒加载,也就是按需加载。
-
Mybatis中的association标签和collection标签具有延迟加载的功能。
需求: 查询车辆信息,按需加载用户信息
实操: PersonMapper接口中定义方法
public interface PersonMapper {
Person findById(Integer id);
}
CarMapper接口中定义方法
public interface CarMapper {
Car findById(Integer id);
}
定义车的实体/dto
public class Car {
private Integer id;
private String name;
private Integer age;
private Integer pId;
private Person person;
.....
}
在PersonMapper.xml中编写一个statement去查询人的信息:
<select id="findById" resultType="com.whitecamellia.entity.Person">
select * from person where id = #{id}
</select>
在CarMapper.xml中编写一个statement去查询车的信息:
<resultMap id="carMapper" type="com.whitecamellia.entity.Car" autoMapping="true">
<id property="id" column="id"/>
<!-- <result column="name" property="name"/>-->
<!-- <result column="age" property="age"/>-->
<!-- <result column="p_id" property="pId"/>-->
<association property="person" select="com.whitecamellia.mapper.PersonMapper.findById"
column="p_id" autoMapping="true">
</association>
</resultMap>
<select id="findCarById" resultMap="carMapper">
select *
from car
where id = #{id}
</select>
测试
@Autowired
private CarMapper carMapper;
@Test
void findCarById() {
Car car = carMapper.findCarById(1);
System.out.println(car.getName());
}//查询车辆指定信息
查看结果:
当我们查询Car表name信息,会发现Person的sql语句也会被执行
开启延迟加载
application.properties中配置
# 开启延迟加载
mybatis.configuration.lazy-loading-enabled=true
# 开启懒加载 设置false那么 会按需加载 3.4.1以下版本模式是true,需要手动修改为false-->
mybatis.configuration.aggressive-lazy-loading=false
查看结果:
当我们查询Car表信息,Person的sql语句就不会被查询
只有当我们按需查询Person数据信息时,Perosn的sql语句才会执行。比如我们查询Person的信息。
@Autowired
private CarMapper carMapper;
@Test
void findCarById() {
Car car = carMapper.findCarById(1);
System.out.println(car.getPerson().getName());
}//查询车辆指定信息
7.2 缓存
缓存简介
Mybatis包含一个非常强大的查询缓存特性,它可以非常方便配置和定制。缓存可以极大的提升查询效率。
例如:每个用户登入的页面的菜单功能选项都是固定的,点击每个选项都需要去数据库中查询数据,那么对于所有用户来说,数据都是一样的,那么我们就没必要每次点击菜单功能选项都去查询数据库,那样效率会很低,用户很多的时候,数据库服务器负担就会很严重。
所以我们就需要用到缓存。
Mybatis的查询分为:
-
一级缓存指的是sqlsession级别的。(本地缓存)
-
一级缓存只会在同一个sqlsession之间共享,多个sqlsession之间的一级缓存是互相独立的,默认一直开启,没法关闭。
-
与数据库同一次会话期间查询到的数据会放在本地缓存。以后如果需要获取相同数据,直接从缓存中拿,没必要再去查数据库。
-
-
二级缓存指的是mapper(namespace)级别的。(全局缓存)
- 二级缓存只会在同一个namespace下的mapper映射文件之间共享。
一级缓存
Mybatis默认支持一级缓存。
- 第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
- 如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
- 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Test
void testCache1() {
SqlSession sqlSession = sqlSessionFactory.openSession(true);
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
Person person1 = mapper.findById(1);
System.out.println(person1);
// mapper.deleteByIds(new int[]{1});
personMapper.deleteByIds(new int[]{1});
Person person2 = mapper.findById(1);
System.out.println(person2);
}
@Test
void testCache2() {
SqlSession sqlSession = sqlSessionFactory.openSession(true);
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
Person person1 = mapper.findById(1);
System.out.println(person1);
}
@Test
void testCache() {
testCache1();
testCache2();
}
结果:
使用缓存:
testCache1()
未使用缓存:
testCache()
搞点事情:
查询结果:
一级缓存失效:
- sqlSession不同:相同数据在sqlSession域中会被共享,不同sqlSession域之间数据不会共享
- sqlSession相同:查询条件不同,(一级缓存中还没有这个要查询的数据)
- sqlSession相同:两次查询之间,增加了 CRUD操作(有可能CRUD会影响当前数据)
- sqlSession相同:手动清除了一级缓存数据,(缓存清空)
代码:
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Test
public void test4() throws IOException {
SqlSession sqlSession = sqlSessionFactory.openSession(true);//true自动提交
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);//true自动提交
//1.分别获取不同的sqlSession对象
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order1 = mapper.findByIdWithUserLazy(1);
System.out.println(order1.getUser());//获取User的用户信息
OrderMapper mapper2 = sqlSession1.getMapper(OrderMapper.class);
Order order2 = mapper.findByIdWithUserLazy(1);
System.out.println(order2.getUser());//获取User的用户信息
//2.
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order1 = mapper.findByIdWithUserLazy(1);//sql语句相同,但是 查询条件不同,
Order order2 = mapper.findByIdWithUserLazy(3);//域中没有id为3的 用户信息
System.out.println(order1.getUser());//获取User的用户信息
System.out.println(order2.getUser());//获取User的用户信息
//3.
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order1 = mapper.findByIdWithUserLazy(1);//执行操作是一样的
System.out.println(order1.getUser());//获取User的用户信息
//执行修改操作
//mapper.updateOrdersWithUser(1);
Order order2 = mapper.findByIdWithUserLazy(1);//执行操作是一样的
System.out.println(order2.getUser());//获取User的用户信息 就是修改之后的
//4
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order1 = mapper.findByIdWithUserLazy(1);//执行操作是一样的
System.out.println(order1.getUser());//获取User的用户信息
sqlSession.clearCache();//清空一级缓存
Order order2 = mapper.findByIdWithUserLazy(1);//执行操作是一样的
System.out.println(order2.getUser());//获取User的用户信息
}
二级缓存
二级缓存指的是mapper(namespace)级别的。一个namespace对应一个二级缓存
工作机制:
- 一个会话,查询一条数据,这个数据会被放在当前会话的一级缓存中
- 如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,可以参考二级缓存
- sqlSession---> 不同namespace查出的数据会放在自己的对应缓存中(map)
使用:
1.开启全局二级缓存配置:
<!--默认是开启的-->
mybatis.configuration.cache-enabled=true
2.开启mapper级别的缓存开关,在对应的OrderMapper.xml中添加缓存开关
<cache></cache>
<!-- 里面的参数属性 作为了解,如果什么都不配置,就是默认如下配置-->
<!-- <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> -->
<!--
eviction:缓存的回收策略:
• LRU – 最近最少使用的:移除最长时间不被使用的对象。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
• 默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。
mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
-->
3.注意:测试二级缓存时Mapper要使用注入形式:
Person person = PersonMapper.findById(3);
4.Pojo类需要序列化,并定义UID
不是说因为学习了缓存我们才实现序列化,是因为POJO类都是实现数据持久化交互的
public Person implements Serializable {
private static final long serialVersionUID = 4829831994525772316L;
}
5.测试:我们把二级缓存配置注释。测试
@Test
void test() {
testCache1();
testCache2();
}
@Test
void testCache1() {
Person person = personMapper.findById(1);
System.out.println(person);
}
@Test
void testCache2() {
Person person = personMapper.findById(1);
System.out.println(person);
}
结果:会发现我们当关闭一个sqlSession时,另一个sqlSession就需要在此请求sql语句,发送2次请求
6.我们把二级缓存配置 激活,测试
会发现提示缓存命中率。Cache Hit Ratio。
证明我们发送二次请求的时候,是从二级缓存中拿的数据,并没有再次发送sql
注意:只有会话关闭了,该sqlSession数据才会跑到二级缓存中。
八、Mybatis执行流程
Configration会去找全局配置文件mybatis.xml,然后sesssion工厂去找sqlsession。
我们写servlet目前是为了接收前端请求和连接数据库,访问数据,
那么servlet和数据库的连接也算是请求数据和响应数据。
Sqlsession就是myBatis中最核心的了。它去加载mappedStatment。然后去执行TransAction。
评论( 0 )