【面试题】【Mybatis】面试题3、mybatis如何实现二级缓存

一、问题

1.1、环境
电脑环境:Windows 10;
开发工具:IntelliJ IDEA;
数据库环境:MySQL 11.0.10;
JDK环境: Jdk1.8;
Maven环境:Maven3.5.0;
1.2、问题
如何使用Mybatis框架实现二级缓存?

二、解答

2.1、基本知识;
1.一级缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper ,并且可自定义存储源,如 Ehcache

2.2、实战
首先我们搭建一个简单的项目:
①、实体类:
如果要实现二级缓存,必须要实现序列化,即实现实现Serializable接口,为什么要实现序列化接口?我的理解是,Mybatis如何能判断你这个User实体类的查询和另一个User实体类查询时不一样的?序列化就像身份证一样,是标记一个对象是否相同的一个唯一验证码;并且,我们再调用外部接口或者传输过程中,都需要序列化,也是为了将一个立体的实体类,在保证对象结构和对象数据的基础上,做一个线性,二进制化的一个处理;就像一个方阵要快速通过一个窄桥,宽度只能满足一个人通过,大家按照一定的规则(从前到后,从左到右)依次快速通过,通过窄桥后,再恢复为原来的方阵(反序列化);总而言之就是:唯一标记和快速传输;

package com.example.student.domain;


import java.io.Serializable;

public class User  implements Serializable{
    private int id;
    private int age;
    private String userName;
    private String userAddress;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getUserAddress() {
        return userAddress;
    }
    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString()
    {
        final StringBuilder builder = new StringBuilder();
        builder.append("id=").append(id).append(",");
        builder.append("age=").append(age).append(",");
        builder.append("userName=").append(userName).append(",");
        builder.append("userAddress=").append(userAddress).append(".");

        return builder.toString();

    }
}


②、mapper.xml文件
mapper.xml文件很简单,这里我们再select标签上新加了一个cache标签,用来配置缓存配置;eviction表示缓存回收的策略;flushInterval表示清理间隔,满了多少条之后清空缓存;这里如果设置1,则第2次查询都会新开一个sqlSession;你也可以设置大一些;例如60000等;

映射语句文件中的所有select 语句将会被缓存。
映射语句文件中的所有insert,update 和delete 语句会刷新缓存。
缓存会使用Least Recently Used(LRU,最近最少使用的)算法来收回。
根据时间表(比如no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的1024 个引用。
缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 这里namespace必须是PostsMapper接口的路径,不然要运行的时候要报错 “is not known to the MapperRegistry”-->
<mapper namespace="com.example.student.mapper.UserMapper">
    <!-- 这儿的resultType是配置在mybatis-config.xml中得别名 -->

    <!--<cache/>-->

<!--  回收策略:
    LRU – 最近最少使用的:移除最长时间不被使用的对象。
    FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。-->
    <!--<cache eviction="FIFO" flushInterval="1" size="512" readOnly="true" />-->
    <select id="getUserById" parameterType="int" resultType="com.example.student.domain.User">
        select * from user where id=#{id}
    </select>
</mapper>

③、mapper.java类

package com.example.student.mapper;

import com.example.student.domain.User;

//通过面向接口的mybatis编程方式,需要保证方法名和配置文件中的id名称一致
public interface UserMapper {
    public User getUserById(int id);
}

④、测试类
注意导入的包的版本和路径;

import com.alibaba.fastjson.JSON;
import com.example.student.domain.User;
import com.example.student.mapper.UserMapper;
import junit.framework.TestCase;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;

public class TestClass extends TestCase {

        Logger logger = LoggerFactory.getLogger(TestClass.class);
        private static SqlSessionFactory sqlSessionFactoy;

        static{
//            获取mybatis配置文件的方式:
//            String resource = "org/mybatis/builder/mybatis-config.xml";
//            InputStream inputStream = Resources.getResourceAsStream(resource);
//            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//            SqlSessionFactory factory = builder.build(inputStream);
            String resource = "mybatis.config.xml";
            try {
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactoy = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Test
        public void testgetUserById() {

//            默认的 openSession()方法没有参数,它会创建有如下特性的 SqlSession:
//            1.会开启一个事务(并且不自动提交)。
//            2.将从由当前环境配置的 DataSource 实例中获取 Connection 对象。
//            3.事务隔离级别将会使用驱动或数据源的默认设置。
//            4.预处理语句不会被复用,也不会批量处理更新。

            //这里开启两个SqlSession来模拟两次请求;
            SqlSession sqlSession = sqlSessionFactoy.openSession();
            SqlSession sqlSession2 = sqlSessionFactoy.openSession();
            try{
                UserMapper mapper = sqlSession.getMapper(UserMapper.class);
                UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

                User u1=mapper.getUserById(1);
//                事务控制方法;
//                void commit()
//                void commit(boolean force)
//                void rollback()
//                void rollback(boolean force)
                sqlSession.commit();
                logger.debug(JSON.toJSONString(u1));

                User u2=mapper2.getUserById(1);
                sqlSession2.commit();
                logger.debug(JSON.toJSONString(u2));

            } finally {
                sqlSession.close();
            }
        }
    }

⑤、建表语句(MySQL):

drop table if exists user;


create table user(
    id int primary key auto_increment,
    username varchar(50) unique,
    password varchar(100),
    useraddress varchar(50)
);

select * from user ;
insert into user values(1,"123","123","beijing");
insert into user values(2,"46","456","shanghai");

⑥配置文件
配置mybatis.config.xml文件;这里要放开settings标签,让缓存生效;你可以对比放开前后日志打印的情况,是否开启了两个sqlSession;还是调用2次查询,但是只开启依次sqlSession;下面还要注意一点,现在mysql的Driver废弃了com.mysql.jdbc.Driver,最好改用com.mysql.cj.jdbc.Driver;并且url后面设置服务器的时区serverTimezone;而且url里【&】符号要用转移字符代替,即【&amp;】;别忘了修改本地MySQL数据库的连接信息;关联的实体类的路径,关联的mapper.xml的路径;一般mapper.xml文件都放在resources下面;我这里放在resources下面的mapper文件夹里的;

<?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--必须要配置可缓存-->
<!--    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>-->
    <typeAliases>
        <typeAlias alias="User" type="com.example.student.domain.User" />
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/sys?characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/UserMapper.xml" />
    </mappers>

</configuration>

测试:

11:45:00.844 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:45:00.994 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.11:45:01.099 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
11:45:01.105 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
11:45:02.716 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1496949625.
11:45:02.716 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.718 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==>  Preparing: select * from user where id=? 
11:45:02.746 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
11:45:02.769 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <==      Total: 1
11:45:02.831 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
11:45:02.831 [main] DEBUG com.example.student.mapper.UserMapper - Cache Hit Ratio [com.example.student.mapper.UserMapper]: 0.0
11:45:02.831 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
11:45:02.841 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1665197552.
11:45:02.841 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6340e5f0]
11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==>  Preparing: select * from user where id=? 
11:45:02.842 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - ==> Parameters: 1(Integer)
11:45:02.843 [main] DEBUG com.example.student.mapper.UserMapper.getUserById - <==      Total: 1
11:45:02.843 [main] DEBUG TestClass - {"age":0,"id":1,"userAddress":"beijing","userName":"guowuxin"}
11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.843 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5939a379]
11:45:02.843 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1496949625 to pool.

Process finished with exit code 0

截图:
在这里插入图片描述

如上图所示的报文里,就开启了请求查询的两个事务;

本篇说明内容完毕;

三、总结

1、实体类序列化;
2、mybatis.config.xml(或者你自己起的别的名称)配置文件里导入settings标签,配置setting标签,属性name为cacheEnabled,value值为true;

    <!--必须要配置可缓存-->
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

3、具体的mapper.xml文件标签上配置cache标签;配置属性eviction为FIFO(先入先出),就是优先移除最早的缓存;flushInterval间隔可以自定义设置,设置为1则每次查询过后都会清空缓存;readOnly看你业务是否需要只读与否;

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

参考博客:
1、JAVABEAN为什么需要序列化
2、mybatis 介绍cache和使用cache
3、mybatis 缓存(cache)的使用
4、mybatis入门教程(九)------mybatis缓存
5、mybatis中文文档

欢迎关注我的
CSDN博客: https://blog.csdn.net/River_Continent
微信公众号:幕桥社区
在这里插入图片描述
知乎:张牧野, https://www.zhihu.com/people/zhang-mu-ye-37-76/activities
简书: https://www.jianshu.com/u/02c0096cbfd3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章