MyBatis中三级缓存的理解

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: MyBatis中三级缓存的理解

文章目录

前言

缓存的作用:不加缓存 1。查询的效率降低 2。增大了数据库的压力

  • 一级缓存:会话级别的
  • 二级缓存:进程级别的
  • 三级缓存:自定义缓存

项目中要使用缓存 全局配置文件中 settings 我们需要打开, 在对应的映射文件中 <cache>,一级缓存是默认使用的。二级缓存我们需要自己开启:一级和二级缓存的执行的先后顺序:先查二级缓存。二级缓存没有进行一级缓存。一级缓存如果还是没有那么走数据库查询

作用域:一级缓存的作用域是session级别的,命中率很低。

1. 环境搭建

1.1 依赖引入

<!--引入mybatis依赖-->

   <dependency>

     <groupId>org.mybatis</groupId>

     <artifactId>mybatis</artifactId>

     <version>3.5.13</version>

   </dependency>

   <!--mysql-->

   <dependency>

     <groupId>mysql</groupId>

     <artifactId>mysql-connector-java</artifactId>

     <version>8.0.22</version>

   </dependency>

   <dependency>

     <groupId>junit</groupId>

     <artifactId>junit</artifactId>

     <version>4.12</version>

     <scope>test</scope>

   </dependency>

1.2 mybatis-config.xml配置

配置db.properties

driver=com.mysql.cj.jdbc.Driver

url=jdbc:mysql://ip:3306/mybatisplus?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai

username=root

password=123456

在mybatis-config.xml引入db.properties

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE configuration

       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

       "https://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

   <!---导入配置文件-->

   <properties resource="db.properties"></properties>

   <environments default="development">

       <environment id="development">

             <transactionManager type="JDBC"/>

           <dataSource type="POOLED">

               <property name="driver" value="${driver}"/>

               <property name="url" value="${url}"/>

               <property name="username" value="${username}"/>

               <property name="password" value="${password}"/>

           </dataSource>

       </environment>

   </environments>

   <mappers>

       <mapper resource="mapper/PersonMapper.xml"/>

   </mappers>

</configuration>

1.3 实体类

省略set get方法

/**

* person实体类

*/

public class Person {

   private Long id;

   private String name;

   private Integer age;

   private String email;

}

1.4 mapper

mapper接口

/**

* 查询患者

*/

public interface PersonMapper {

   /**

    * 根据id查询人员信息

    * @param id

    * @return

    */

   Person selectPerson(long id);

}

mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper

       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

       "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.elite.mybatis.mapper.PersonMapper">

   <!--查询人员信息-->

   <select id="selectPerson" resultType="com.elite.mybatis.entity.Person">

       select * from person where id = #{id}

   </select>

</mapper>

1.5 测试

/**

    * 查询人员信息

    */

   @Test

   public void testSelect() throws IOException {

       InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

       SqlSession sqlSession = sqlSessionFactory.openSession();

       Person person = sqlSession.selectOne("com.elite.mybatis.mapper.PersonMapper.selectPerson", 1L);

       System.out.println(person.toString());

       ///Person{id=1, name='elite', age=22, email='elite@qq.com'}

   }

项目结构:

0b2c74042c664f3bb6a0f6053654eaf1.png

2.缓存

2.1 一级缓存

一级缓存默认是开启的,作用域是SqlSession 级别的,所以命中率极低。相同SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数,只会进行一次数据库查询,然后把数据缓存到缓冲中,以后直接先从缓存中取出数据,不会直接去查数据库

mybatis-config.xml 配置日志

<!--设置日志实现-->

   <settings>

       <setting name="logImpl" value="STDOUT_LOGGING"/>

   </settings>

开启日志

   <!--加入日志-->

   <dependency>

     <groupId>ch.qos.logback</groupId>

     <artifactId>logback-classic</artifactId>

     <version>1.4.2</version>

配置日志文件logback.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE configuration>

<configuration>

  <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">

       <encoder>

           <pattern>%5level [%thread] - %msg%n</pattern>

       </encoder>

   </appender>

   <logger name="com.elite.mybatis.mapper.PersonMapper">

       <level value="trace"/>

       <Root level="info" >

           <AppenderRef ref="stdout"/>

       </Root>

   </logger>

</configuration>

测试

   /**

    * 查询人员信息

    */

   @Test

   public void testSelect() throws IOException {

       InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

       SqlSession sqlSession = sqlSessionFactory.openSession();

       System.out.println("==========第一次调用========================");

       Person person = sqlSession.selectOne("com.elite.mybatis.mapper.PersonMapper.selectPerson", 1L);

       System.out.println(person.toString());

       System.out.println("==========第二次调用========================");

       Person person1 = sqlSession.selectOne("com.elite.mybatis.mapper.PersonMapper.selectPerson", 1L);

       System.out.println(person1.toString());

   }

相同mapper,相同参数

7e2a68e5d6c94666abcd94d20203b1af.png

相同mapper,不同参数

b945eed7af664303b05a386dfeec641c.png

2.2 二级缓存

二级缓存:

是多个 SqlSession 共享的,其作用域是 mapper 的同一个 namespace,不同 的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行 相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从 缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存 需要在 setting 全局参数中配置开启二级缓存。

mybatis-config.xml配置文件开启缓存配置

 <settings>

       <!--设置日志实现-->

       <setting name="logImpl" value="STDOUT_LOGGING"/>

       <!--开启二级缓存-->

       <setting name="cacheEnabled" value="true"/>

   </settings>

实体类实现序列化

public class Person implements Serializable {

}

mapper的xml配置缓存

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper

       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

       "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.elite.mybatis.mapper.PersonMapper">

     <!--eviction: 清空缓存的策略

       readOnly: 是否只读

       flushInterval: 每个60秒刷新一次缓存

       size: 内存大小,最多存储结果对象或者列表的512个引用 -->

   <cache readOnly="true" eviction="FIFO" flushInterval="60000" size="512"/>

 

   <!--查询人员信息-->

   <select id="selectPerson" resultType="com.elite.mybatis.entity.Person" flushCache="false" useCache="true" >

       select * from person where id = #{id}

   </select>

</mapper>

测试代码

   /**

    * 查询人员信息

    */

   @Test

   public void testSelect1() throws IOException {

       InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

       SqlSession sqlSession = sqlSessionFactory.openSession(true);

       System.out.println("==========第一个sqlSession="+sqlSession.hashCode()+"========================");

       PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

       System.out.println( personMapper.selectPerson(1L).toString());

       sqlSession.close();

       SqlSession sqlSession1 = sqlSessionFactory.openSession(true);

       System.out.println("==========第二个sqlSession="+sqlSession1.hashCode()+"========================");

       PersonMapper personMapper1 = sqlSession1.getMapper(PersonMapper.class);

       System.out.println(personMapper1.selectPerson(1L).toString());

       sqlSession1.close();

   }

测试,缓存命中率只有0.5

==========第一个sqlSession=236230========================

Cache Hit Ratio [com.elite.mybatis.mapper.PersonMapper]: 0.0

Opening JDBC Connection

Created connection 17500244.

==>  Preparing: select * from person where id = ?

==> Parameters: 1(Long)

<==    Columns: id, name, age, email

<==        Row: 1, elite, 22, elite@qq.com

<==      Total: 1

Person{id=1, name='elite', age=22, email='elite@qq.com'}

Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10b0854]

Returned connection 17500244 to pool.

==========第二个sqlSession=29457283========================

Cache Hit Ratio [com.elite.mybatis.mapper.PersonMapper]: 0.5

Opening JDBC Connection

Checked out connection 17500244 from pool.

==>  Preparing: select * from person where id = ?

==> Parameters: 1(Long)

<==    Columns: id, name, age, email

<==        Row: 1, elite, 22, elite@qq.com

<==      Total: 1

Person{id=1, name='elite', age=22, email='elite@qq.com'}

Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10b0854]

Returned connection 17500244 to pool.

2.3 三级缓存

由于一级缓存二级缓存的命中率极低,都是在单个进程之类进行缓存,多进程缓存就不好使,mybatis默认提供了接口可以自定义缓存。

此处我们整合redis进行缓存。

引入缓存的依赖

 <!--mybatis-redis缓存依赖-->

   <dependency>

     <groupId>org.mybatis.caches</groupId>

     <artifactId>mybatis-redis</artifactId>

     <version>1.0.0-beta2</version>

   </dependency>

配置文件redis.properties

host=redis ip

port=6379

connectionTimeout=500000

password=

database=0

自定义缓存实现cache接口

package com.elite.mybatis.cache;

import org.apache.ibatis.cache.Cache;

import org.mybatis.caches.redis.*;

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

import java.io.IOException;

import java.io.InputStream;

import java.util.Map;

import java.util.Properties;

import java.util.concurrent.locks.ReadWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**

* 自定义com.elite.mybatis.cache.MyRedisCache;

*/

public class MyRedisCache implements Cache {

   private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

   private String id;

   private static JedisPool pool;

   public MyRedisCache(String id) throws IOException {

       if (id == null) {

           throw new IllegalArgumentException("Cache instances require an ID");

       } else {

           this.id = id;

           Properties properties = new Properties();

           // 使用ClassLoader加载properties配置文件生成对应的输入流

           InputStream in = MyRedisCache.class.getClassLoader().getResourceAsStream("redis.properties");

           // 使用properties对象加载输入流

           properties.load(in);

            //获取key对应的value值

           String host = properties.getProperty("host");

           String port = properties.getProperty("port");

           in.close();

           RedisConfig redisConfig =new RedisConfig();

           pool = new JedisPool(redisConfig,host,Integer.valueOf(port), 5000, 5000,null,0,"mybatis-redis" );

       }

   }

   private Object execute(RedisCallback callback) {

       Jedis jedis = pool.getResource();

       Object var3;

       try {

           var3 = callback.doWithRedis(jedis);

       } finally {

           jedis.close();

       }

       return var3;

   }

   public String getId() {

       return this.id;

   }

   public int getSize() {

       return (Integer)this.execute(new RedisCallback() {

           public Object doWithRedis(Jedis jedis) {

               Map<byte[], byte[]> result = jedis.hgetAll(MyRedisCache.this.id.toString().getBytes());

               return result.size();

           }

       });

   }

   public void putObject(final Object key, final Object value) {

       this.execute(new RedisCallback() {

           public Object doWithRedis(Jedis jedis) {

               jedis.hset(MyRedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));

               return null;

           }

       });

   }

   public Object getObject(final Object key) {

       return this.execute(new RedisCallback() {

           public Object doWithRedis(Jedis jedis) {

               return SerializeUtil.unserialize(jedis.hget(MyRedisCache.this.id.toString().getBytes(), key.toString().getBytes()));

           }

       });

   }

   public Object removeObject(final Object key) {

       return this.execute(new RedisCallback() {

           public Object doWithRedis(Jedis jedis) {

               return jedis.hdel(MyRedisCache.this.id.toString(), new String[]{key.toString()});

           }

       });

   }

   public void clear() {

       this.execute(new RedisCallback() {

           public Object doWithRedis(Jedis jedis) {

               jedis.del(MyRedisCache.this.id.toString());

               return null;

           }

       });

   }

   public ReadWriteLock getReadWriteLock() {

       return this.readWriteLock;

   }

   public String toString() {

       return "Redis {" + this.id + "}";

   }

}

配置mapper xml配置

<!--设置三级缓存类地址-->

<cache type="com.elite.mybatis.cache.MyRedisCache" />

测试

/**

    * 查询人员信息

    */

   @Test

   public void testSelect3() throws IOException {

       InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");

       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

       SqlSession sqlSession = sqlSessionFactory.openSession(true);

       PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

       System.out.println( personMapper.selectPerson(1L).toString());

       sqlSession.close();

   }

db44b8e734f9444cadc5ccba5ff1cbd7.png

第二次再次执行查询命中率1.0

4e0490377ec34d728c1a484d44801c4a.png


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
缓存 Java 数据库连接
Mybatis缓存相关面试题有多卷
使用 MyBatis 缓存机制需要注意以下几点: 对于频繁更新和变动的数据,不适合使用缓存。 对于数据的一致性要求比较高的场景,不适合使用缓存。 如果配置了二级缓存,需要确保缓存的数据不会影响到其他业务模块的数据。 在使用缓存时,需要注意缓存的命中率和缓存的过期策略,避免缓存过期导致查询性能下降。
105 0
|
2月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
18天前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
19天前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
56 5
|
5月前
|
SQL 缓存 Java
MYBATIS缓存
MYBATIS缓存
|
1月前
|
缓存 Java 数据库连接
使用MyBatis缓存的简单案例
MyBatis 是一种流行的持久层框架,支持自定义 SQL 执行、映射及复杂查询。本文介绍了如何在 Spring Boot 项目中集成 MyBatis 并实现一级和二级缓存,以提高查询性能,减少数据库访问。通过具体的电商系统案例,详细讲解了项目搭建、缓存配置、实体类创建、Mapper 编写、Service 层实现及缓存测试等步骤。
|
4月前
|
SQL 缓存 Java
【面试官】Mybatis缓存有什么问题吗?
面试官:你说下对MyBatis的理解?面试官:那SqlSession知道吧?面试官:Mybatis的缓存有哪几种?面试官:那Mybatis缓存有什么问题吗?面试官:Mybatis分页插件是怎么
【面试官】Mybatis缓存有什么问题吗?
|
4月前
|
缓存 算法 Java
关于MyBatis的缓存详解
MyBatis 的缓存机制非常灵活,可以通过简单的配置来满足不同的性能需求。合理地使用缓存可以显著提高应用程序的性能,尤其是在处理大量数据库查询时。然而,开发者需要注意缓存的一致性和并发问题,特别是在使用可读写缓存时。
|
5月前
|
缓存 NoSQL Java
在 SSM 架构(Spring + SpringMVC + MyBatis)中,可以通过 Spring 的注解式缓存来实现 Redis 缓存功能
【6月更文挑战第18天】在SSM(Spring+SpringMVC+MyBatis)中集成Redis缓存,涉及以下步骤:添加Spring Boot的`spring-boot-starter-data-redis`依赖;配置Redis连接池(如JedisPoolConfig)和连接工厂;在Service层使用`@Cacheable`注解标记缓存方法,指定缓存名和键生成策略;最后,在主配置类启用缓存注解。通过这些步骤,可以利用Spring的注解实现Redis缓存。
79 2
|
5月前
|
SQL 缓存 Java
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件
Java框架之MyBatis 07-动态SQL-缓存机制-逆向工程-分页插件