MyBatis中三级缓存的理解

  • Post author:
  • Post category:其他




前言

缓存的作用:不加缓存 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'}
    }


项目结构:


项目结构



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,相同参数

相同参数



相同mapper,不同参数

不同参数



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();
    }



redis缓存数据

查询缓存



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

缓存



版权声明:本文为qq_37400096原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。