Java教程

(七)Mybatis-缓存

本文主要是介绍(七)Mybatis-缓存,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

(七)Mybatis-缓存

一、简介

问题:查询=》连接数据库=》消耗资源!

解决方案:

  • 一次查询的结果,给他暂存在一个可以直接取到的地方=》内存:缓存。
  • 我们再次查询相同数据的时候,直接走缓存,就不去数据库查了。
  1. 什么是缓存【cache】
    • 存在内存中的临时数据。
    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据文件)查询,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  2. 为什么使用缓存?
    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  3. 什么样的数据能使用缓存?
    • 经常查询并且不经常改变的数据。
    • 比如菜单数据

二、Mybatis缓存

  • mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大地提升查询效率
  • mybatis系统中默认定义了2级缓存:一级缓存和二级缓存。
    • 默认情况下啊,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。namespace又和一个接口绑定,一个接口mapper对应一个表或一个业务,所以是一张表级别的缓存,整个mapper文件里的所有方法共享。
    • 为了提供扩展性,mybatis定义了缓存接口cache,我们可以通过cache接口来定义二级缓存。

三、一级缓存

在一次sqlSession会话中有效,如下从开启到关闭sqlSession

 @Test
    public void testGetBlogList() {
        try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            List<Blog> blogList = mapper.getBlogList();
            for (Blog blog : blogList) {
                System.out.println(blog);
            }
        }
    }

一级缓存默认开启,我们只需要测试即可

3.1 测试步骤

1 开启日志

在核心配置文件mybatis-config.xml

<settings>
<!--    <setting name="logImpl" value="STDOUT_LOGGING"/>-->
    <setting name="logImpl" value="LOG4J"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

2 编写测试实体pojo类

package com.happy.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
   private int id;
   private String name;
   private String pwd;
}

3 编写mapper接口

package com.happy.dao;

import com.happy.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {
    @Select("select * from user")
    List<User> getUserList();

    //规范最好给参数取名字
    User getUserById(@Param("id") int id);
}

4 编写mapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.happy.dao.UserMapper">

    <select id="getUserById" parameterType="int" resultType="user">
        select *
        from user
        where id = #{id}
    </select>

</mapper>

5 测试使用

测试步骤
  • 开启日志
  • 测试在一个session内查询两次相同记录
  • 查看日志输出
package com.happy.dao;

import com.happy.pojo.User;
import com.happy.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

    @Test
    public void testGetUserList() {
        try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            for (User user : userList) {
                System.out.println(user);
            }
        }
    }


    @Test
    public void testGetUserById() {
        try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            System.out.println("============第1次查询=============");
            User user1 = mapper.getUserById(3);
            System.out.println(user1);

            System.out.println("============第2次查询=============");
            User user2 = mapper.getUserById(3);
            System.out.println(user2);
        }
    }
}

缓存失效情况
  1. 查询不同的东西

  2. 增删该操作DML操作,可能会改变原来的数据,所以必定会刷新缓存。

    即在两次查询之间,加入修改操作后(即使是修改其他行数据),缓存也会刷新。

  3. 查询不同的mapper.xml

  4. 手动清楚缓存。

6 小结:

  • 一级缓存默认是开启的,只在一次sqlSession中有效
  • 也就是拿到连接和关闭连接这个区间段。
  • sqlSession的一级缓存就是一个map,下次来会去查这个map。

四、二级缓存

4.1 二级缓存产生的原因

默认情况下,只启用了本地的会话缓存即一级缓存,它仅仅对一个会话中的数据进行缓存

二级缓存产生的原因:

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。
  • 当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被再次保存到二级缓存中。
  • 新的会话查询信息,就可以从二级缓存中获取内容;
  • 不同的mapper查出的数据会放在自己对应的缓存(map)中。

4.2 二级缓存介绍

要启用全局的二级缓存,只需要在你的sql映射文件中添加一行:

<cache/>

二级缓存只作用于cache标签所在的映射文件中的语句,如果你混合使用Java API和XML映射文件,在共用接口中的语句将不会被默认缓存、你需要使用@CacheNamespaceRef注解指定缓存作用域。

这些属性可以通过cache元素的属性来修改,比如:

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

上面这个更高级的配置策略:

  • 创建了一个FIFO缓存,

  • 每隔60秒刷新,

  • 最多可以存储结果对象或列表512个应用

  • 而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同的线程中调用者产生冲突。

可用的清除策略有:

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

4.3 开启步骤

1 在核心配置文件显示开启全局二级缓存

虽然默认为true

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

2 在mapper文件里开启缓存

在要使用二级缓存的mapper.xml文件里开启,也可以自定义一些参数

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

3 测试使用

@Test
    public void testGetUserByIdByCache2() {
        SqlSession sqlSession1=null;
        SqlSession sqlSession2=null;
        try {
            sqlSession1 = MybatisUtils.getSqlSession();
            sqlSession2 = MybatisUtils.getSqlSession();
            UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
            UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
            System.out.println("============第1次查询=============");
            User user1 = mapper1.getUserById(1);
            System.out.println(user1);

            System.out.println("============第2次查询=============");
            User user2 = mapper1.getUserById(1);
            System.out.println(user2);

//            注意这里在其他sqlsession使用前必须关闭,如果不关闭sqlsession,不会把对象引用放到二级缓存共享给其他sqlsession
            sqlSession1.commit();
//            sqlSession1.close();
            System.out.println("============第3次查询,使用sqlsession2=============");
            User user3 = mapper2.getUserById(1);
            System.out.println(user3);

            System.out.println("============判断两个对象是否相同=============");
            System.out.println(user1 == user3);
        }
        catch (Exception e){

        }finally {
            sqlSession1.close();
            sqlSession2.close();
        }
    }

4 小结事项:

注意:

readOnly="true"/>
  • 如果readOnly设置为true,则不需要让user类实现Serializable接口,否则需要。
  • 如果readOnly设置为true,则两次从缓存中取出对象为一个,否则不是一个。
  • 注意这里在其他sqlsession使用前必须关闭或者提交,如果不关闭sqlsession,不会把对象引用放到二级缓存共享给其他sqlsession。

五、自定义缓存(二级缓存)

实现以下接口

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

mybatis已经实现的缓存策略如下:默认使用LRU,清除最不常使用。

六、ehcache(第三方缓存)

  • ehcache是一个纯java的进程内缓存框架,具有快速、精干等特点,是hibernate中默认的cache provider。
  • 是一种广泛使用的开源java分布式缓存,主要面向通用缓存。

要在程序中使用ehcache

6.1 使用步骤

1 引入依赖

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

2 集成第三方缓存

除了上述自定义缓存方式,可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。

<!--    开启二级缓存,在这个mapper文件中有效-->
    <cache
            eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true"
            type="org.mybatis.caches.ehcache.EhcacheCache"
        />

在mapper中指定使用我们的ehcache缓存实现。

3 将ehcache的配置文件ehcache.xml放在resources下

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!-- 配置缓存文件的路劲
        java.io.tmpdir,表示临时文件夹,windows表示在C:\Documents and Settings\Administrator\Local Setting\Temp
     -->
    <diskStore path="../temp/ehcache"/>

    <!-- 设定缓存的默认数据过期策略 -->
    <!--
        name:缓存名称
        maxElementsInMemory:缓存最大个数
        eternal:对象是否永久有效,一但设置了,timeout将不起作用
        timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒),仅当eternal=false对象不是永久有效时使用
        timeToLiveSeconds:设置对象在失效前允许存活时间,最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用
        overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中
        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB
        maxElementsOnDisk:硬盘最大缓存个数
        diskPersistent: 是否缓存虚拟机重启期数据
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒
        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU。
            最近使用(LRU)"策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU
        clearOnFlush:内存数量最大时是否清除
    -->
    <defaultCache
            maxElementsInMemory="1000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="300"
            timeToLiveSeconds="600"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"/>

    <cache name="userCache"
           maxElementsInMemory="1000"
           eternal="false"
           overflowToDisk="true"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
           memoryStoreEvictionPolicy="LRU"/>

</ehcache>

4 测试使用

和mybatis自带的二级缓存没有区别,只是多了一些缓存持久化存放到本地。

七、Mybatis的一级缓存和二级缓存执行顺序

在这里插入图片描述

1、先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库

2、如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取

3、一般不会关闭一级缓存

4、二级缓存默认不开启

5、如果二级缓存关闭,直接判断一级缓存是否有数据,如果没有就查数据库

6、如果二级缓存开启,先判断二级缓存有没有数据,如果有就直接返回;如果没有,就查询一级缓存,如果有就返回,没有就查询数据库

综上:先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此;缓存执行顺序是:二级缓存–>一级缓存–>数据库

在这里插入图片描述

八、其他缓存

工作中一般使用redis作为数据库缓存,而不使用自定义缓存。

这篇关于(七)Mybatis-缓存的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!