小蔡学Java

MyBatis(三):多表映射及优化

2024-05-15 15:36 783 0 SSM / SpringBoot 框架篇 MybatisMySQL

七、 查询优化

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对应一个二级缓存

工作机制:

  1. 一个会话,查询一条数据,这个数据会被放在当前会话的一级缓存中
  2. 如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,可以参考二级缓存
  3. 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 )

  • 博主 Mr Cai
  • 坐标 河南 信阳
  • 标签 Java、SpringBoot、消息中间件、Web、Code爱好者

文章目录