MyBatis中三级缓存的理解

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: 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


相关文章
|
6月前
|
存储 缓存 NoSQL
mybatisplus一二级缓存
MyBatis-Plus 继承并优化了 MyBatis 的一级与二级缓存机制。一级缓存默认开启,作用于 SqlSession,适用于单次会话内的重复查询;二级缓存需手动开启,跨 SqlSession 共享,适合提升多用户并发性能。支持集成 Redis 等外部存储,增强缓存能力。
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
8月前
|
缓存 Java 数据库连接
Mybatis一级缓存详解
Mybatis一级缓存为开发者提供跨数据库操作的一致性保证,有效减轻数据库负担,提高系统性能。在使用过程中,需要结合实际业务场景选择性地启用一级缓存,以充分发挥其优势。同时,开发者需注意其局限性,并做好事务和并发控制,以确保系统的稳定性和数据的一致性。
302 20
|
10月前
|
缓存 Java 数据库连接
Mybatis一级缓存、二级缓存详讲
本文介绍了MyBatis中的查询缓存机制,包括一级缓存和二级缓存。一级缓存基于同一个SqlSession对象,重复查询相同数据时可直接从缓存中获取,减少数据库访问。执行`commit`操作会清空SqlSession缓存。二级缓存作用于同一namespace下的Mapper对象,支持数据共享,需手动开启并实现序列化接口。二级缓存通过将数据存储到硬盘文件中实现持久化,为优化性能,通常在关闭Session时批量写入缓存。文章还说明了缓存的使用场景及注意事项。
381 7
Mybatis一级缓存、二级缓存详讲
|
SQL 缓存 Java
|
11月前
|
缓存 Java 数据库连接
十、MyBatis的缓存
十、MyBatis的缓存
235 6
|
缓存 NoSQL Java
Mybatis学习:Mybatis缓存配置
MyBatis缓存配置包括一级缓存(事务级)、二级缓存(应用级)和三级缓存(如Redis,跨JVM)。一级缓存自动启用,二级缓存需在`mybatis-config.xml`中开启并配置映射文件或注解。集成Redis缓存时,需添加依赖、配置Redis参数并在映射文件中指定缓存类型。适用于查询为主的场景,减少增删改操作,适合单表操作且表间关联较少的业务。
249 6
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
505 4
|
缓存 Java 数据库连接
MyBatis缓存机制
MyBatis提供两级缓存机制:一级缓存(Local Cache)默认开启,作用范围为SqlSession,重复查询时直接从缓存读取;二级缓存(Second Level Cache)需手动开启,作用于Mapper级别,支持跨SqlSession共享数据,减少数据库访问,提升性能。
253 1