当前位置: 首页>后端>正文

第八章 缓存

target

掌握 mybatis的一级缓存是什么
掌握 mybatis一级缓存失效的几种情况
掌握 mybatis 二级缓存的机制和开启步骤
掌握 mybatis 缓存机制的执行流程

计算机操作系统中,有一个叫缓存机制的东西。大概的意思就是操作系统把CPU频繁需要用到的指令和数据暂时存在cache中,使的CPU交互直接是和cache进行,因为cache的速度极快。而不需要和速度相对较慢的内存之间进行,大大提高了效率和资源消耗。

mybatis中也存在缓存机制,分为一级缓存和二级缓存。

新建动态Web项目MyBatis-08,来演示本章内容。

① 导入jar

将以下jar包复制到lib目录下

log4j-1.2.17.jar
log4j-api-2.3.jar
log4j-core-2.3.jar
mybatis-3.4.5.jar
mysql-connector-java-8.0.15.jar

② 创建实体类

在com.mybatis.po包下新建一个Employee类:

public class Employee {

    private Integer id;
    private String name;
    private Integer gender;
    private String email;
}

③ 建表

在MySQL数据库中新建employee表:

CREATE TABLE employee(
id int PRIMARY key,
name VARCHAR(30),
gender int(2),
email VARCHAR(30)
);

④ 创建映射接口

在com.mybatis.mapper包下创建EmployeeMapper.java接口:

public interface EmployeeMapper {
  Employee selectEmployeeById(Integer id);
    int insertEmp(Employee emp);
    int updateEmpById(Employee emp);
}

⑤ 创建映射文件

在com.mybatis.mapper包下创建EmployeeMapper.xml映射文件:

namespace是映射接口的全类名。

<?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">
<mapper namespace="com.mybatis.mapper.EmployeeMapper">

    <!--根据id查询用户信息 -->
    <select id="selectEmployeeById" resultType="com.mybatis.po.Employee"
        parameterType="com.mybatis.po.Employee">
        select * from employee where id = #{id} 
    </select>

    
    <!-- 插入一条用户信息 -->
    <insert id="insertEmp" parameterType="com.mybatis.po.Employee">
        insert into employee(name,gender,email) 
        values (#{name},#{gender},#{email})
    </insert>
    
    <!-- 根据id修改一条用户信息 -->
    <update id="updateEmpById" parameterType="com.mybatis.po.Employee">
        update employee set name = #{name} , gender = #{gender} ,email = #{email} where id = #{id}
    </update>
</mapper>

⑥ 创建mybatis全局配置文件

在src目录下创建mybat-config.xml文件,并配置数据库信息和别名,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>
    <properties resource="dbconf.properties"></properties>
    <settings>
        <setting name="logImpl" value="LOG4J" />
    </settings>
    <typeAliases>
        <package name="com.mybatis.po" />
    </typeAliases>
    <!-- 配置mybatis运行环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用JDBC的事务管理 -->
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <!-- MySQL数据库驱动 -->
                <property name="driver" value="${mysql.driver}" />
                <!-- 连接数据库的URL -->
                <property name="url" value="${mysql.url}" />
                <property name="username" value="${mysql.username}" />
                <property name="password" value="${mysql.password}" />
            </dataSource>
        </environment>
    </environments>

    <!-- 将mapper文件加入到配置文件中 -->
    <mappers>
        <mapper resource="com/mybatis/mapper/EmployeeMapper.xml" />
    </mappers>
</configuration>

在src目录下创建dbconf.properties文件,用来加载数据库连接信息。

# MySQL environment
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=lixintao

# Oracle Environment
oracle.driver=com.jdbc.OracleDriver
oracle.url=jdbc:oracle:thin:@localhost:1521:orcl
oracle.username=root
oracle.password=lixintao

创建log4j.properties,配置日志相关:

# Global logging configuration
log4j.rootLogger=ERROR,stdout
# MyBatis logging configuration...
log4j.logger.com.mybatis=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

⑦ 创建测试类

在com.mybatis.test包下,新建测试类MybatisTest.java:

public class MybatisTest {

    SqlSession session = null;

    @Before
    public void init() {
        InputStream in = null;
        try {
            in = Resources.getResourceAsStream("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);
        session = sessionFactory.openSession();
    }
    
    
    @After
    public void destory() {
        if (session!=null) {
            session.close();
        }
    }
}

1. 一级缓存

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。同一个sqlSession之间的缓存数据区域(HashMap)是相互共享的。

1)什么是一级缓存

我们在一个 sqlSession 中,对 employee 表根据id进行两次查询,查看他们发出sql语句的情况。

@Test
public void testCache() {
  EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
  System.out.println("第一次查询id为1的雇员:");
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);
  System.out.println("第二次查询id为1的雇员:");
  Employee emp2 = employeeMapper.selectEmployeeById(1);
  System.out.println(emp2);
}

执行流程:


第八章 缓存,第1张

只发起一次SQL查询。

第一次查询,发出sql语句,并将查询的结果放入缓存中。第二次查询,由于是同一个sqlSession,会在缓存中查找查询结果,如果有,则直接从缓存中取出来,不和数据库进行交互。

2)一级缓存失效的几种情况

① sqlSession不同

@Test
public void testCache2() {
  EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);
  System.out.println("新建一个sqlSession:");
  SqlSession session2 = sessionFactory.openSession();
  EmployeeMapper employeeMapper2 = session2.getMapper(EmployeeMapper.class);
  Employee emp2 = employeeMapper2.selectEmployeeById(1);
  System.out.println(emp2);
}

执行流程:


第八章 缓存,第2张

由于是不同的sqlSession,一级缓存失效,所以执行两次SQL查询。

② sqlSession相同,查询条件不同

@Test
public void testCache3() {
  EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
  System.out.println("第一次查询id为1的雇员:");
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);
  System.out.println("第二次查询id为3的雇员:");
  Employee emp2 = employeeMapper.selectEmployeeById(3);
  System.out.println(emp2);
}

执行流程:


第八章 缓存,第3张

发现,执行了两次SQL查询。

③ sqlSession相同,两次查询之间有增删改操作

两次查询之间有增删改操作(即使没有commit),一级缓存同样失效。

或者直接执行一个commit操作(没有增删改操作),一级缓存也会失效。

@Test
public void testCache4() {
  EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
  System.out.println("第一次查询id为1的雇员:");
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);
  //        Employee newemp = new Employee(null, "wangwu", 0, "ww@qq.com");
  //        employeeMapper.insertEmp(newemp);
  session.commit();
  System.out.println("第二次查询id为1的雇员:");
  Employee emp2 = employeeMapper.selectEmployeeById(1);
  System.out.println(emp2);
}

执行流程:


第八章 缓存,第4张

④ sqlSession相同,手动清除一级缓存

@Test
public void testCache5() {
  EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
  System.out.println("第一次查询id为1的雇员:");
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);
  session.clearCache();//清空一级缓存
  System.out.println("第二次查询id为1的雇员:");
  Employee emp2 = employeeMapper.selectEmployeeById(1);
  System.out.println(emp2);
}

执行流程:


第八章 缓存,第5张

2. 二级缓存

二级缓存也叫全局缓存。二级缓存是基于namespace的缓存,一个namespace对应一个二级缓存。

1)工作机制

一个会话查询一条语句,这个会话会放在当前会话的一级缓存中。

如果会话关闭,一级缓存中的数据会保存到二级缓存中。

新的会话查询信息,会参照二级缓存。

2)开启步骤

① 实体类序列化

将Employee类进行序列化:

public class Employee implements Serializable{
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String name;
    private Integer gender;
    private String email;
}

② mybatis中开启二级缓存

在mybatis-config.xml中开启二级缓存:

<settings>
  <!--这个配置使全局的映射器(二级缓存)启用或禁用缓存-->
  <setting name="cacheEnabled" value="true" />
</settings>

③ 在映射文件中开启二级缓存

实际项目中会存在多个xml映射文件,在哪个中开启二级缓存,哪个就有效。

EmployeeMapper.xml:

<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024" />

参数介绍:

  • eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。

    • LRU,最近最少使用的,一处最长时间不用的对象
    • FIFO,先进先出,按对象进入缓存的顺序来移除他们
    • SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
    • WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。

    默认采用的是LRU,移除最长时间不用的对形象

  • flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。

  • size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。 这里配置的是1024个对象

  • readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存,他的默认值是false,不允许我们修改。

3)测试

@Test
public void testCache6() {
  SqlSession sqlSession = sessionFactory.openSession();
  EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
  System.out.println("第一次查询id为1的雇员:");
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);
  sqlSession.close();//关闭sqlSession会话

  SqlSession sqlSession2 = sessionFactory.openSession();
  EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
  System.out.println("第二次查询id为1的雇员:");
  Employee emp2 = employeeMapper2.selectEmployeeById(1);
  System.out.println(emp2);

}

执行流程:


第八章 缓存,第6张

3. 细节问题

  1. cacheEnable=true/false:

    只对二级缓存起作用,一级缓存一直开启的。

  2. 每个select标签里都有useCache=true

    只对二级缓存起作用,一级缓存一直开启的。

  3. 每个增删改标签都有flushCache

    当设置为true的时候,一级缓存和二级缓存都被清除。

  4. sqlSession.clearCache():

    只清除一级缓存,不会影响到二级缓存的使用。即:执行完clearCache后,在执行close,依旧会把一级缓存未清除之前的内容放到二级缓存。

4.执行流程

当一个新的会话(sqlSession)到来时,首先会去查询二级缓存,若二级缓存未命中,直接发出SQL语句。

@Test
public void testCache6() {
  SqlSession sqlSession = sessionFactory.openSession();
  EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
  System.out.println("第一次查询id为1的雇员:");
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);

  SqlSession sqlSession2 = sessionFactory.openSession();
  EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);
  System.out.println("第二次查询id为1的雇员:");
  Employee emp2 = employeeMapper2.selectEmployeeById(1);
  System.out.println(emp2);
}

执行流程:


第八章 缓存,第7张

当一个已经存在的会话里发出一个新的查询时,首先会去二级缓存查询,如果未命中,则会去一级缓存再次查询,若仍然未命中,直接发出SQL语句。

@Test
public void testCache() {
  EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
  System.out.println("第一次查询id为1的雇员:");
  Employee emp = employeeMapper.selectEmployeeById(1);
  System.out.println(emp);
  System.out.println("第二次查询id为1的雇员:");
  Employee emp2 = employeeMapper.selectEmployeeById(1);
  System.out.println(emp2);
}

执行流程:


第八章 缓存,第8张

5. mybatis整合Ehcache

三方缓存要和mybatis进行整合,必须实现org.apache.ibatis.cache.Cache接口。

1)导入jar:

Ehcache-core-2.6.8.jar  
mybatis-Ehcache-1.0.3.jar   
slf4j-api-1.7.5.jar

2)创建Ehcache配置文件

在src下创建Ehcache配置文件Ehcache.xml:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
  <diskStore path="F:\Ehcache"/>
  <defaultCache
                maxElementsInMemory="1000"
                maxElementsOnDisk="1000000"
                eternal="false"
                overflowToDisk="false"
                timeToIdleSeconds="100"
                timeToLiveSeconds="100"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
  </defaultCache>
</ehcache>

3)开启Ehcache

在mapper.xml文件中,

<mapper namespace="org.lanqiao.mapper.IStudentMapper">
  <cache type="org.mybatis.caches.ehcache.EhcacheCache">
    <property name="timeToIdleSeconds" value="3600"/>
    <property name="timeToLiveSeconds" value="3600"/>
    <!--同ehcache参数maxElementsInMemory -->
    <property name="maxEntriesLocalHeap" value="1000"/>
    <!--同ehcache参数maxElementsOnDisk -->
    <property name="maxEntriesLocalDisk" value="10000000"/>
    <property name="memoryStoreEvictionPolicy" value="LRU"/>
  </cache>
</mapper>   

https://www.xamrdz.com/backend/37c1926068.html

相关文章: