• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

Recommended Posts

(七)Mybatis-缓存

一、简介

问题:Query="Connect Database="消耗资源!

解决方案:

一个查询的结果临时存储在一个他可以直接获取的地方=内存:缓存。

当我们再次查询相同的数据时,我们直接进入缓存,而不是进入数据库。

什么是缓存[cache]

内存中的临时数据。

通过将用户经常查询的数据放在缓存(内存)中,用户可以从缓存而不是磁盘(关系数据文件)中查询数据,从而提高查询效率,解决高并发系统的性能问题。

为什么使用缓存?

减少与数据库的交互次数,降低系统开销,提高系统效率。

什么样的数据可以缓存?

经常查询但不经常更改的数据。

例如菜单数据。

二、Mybatis缓存

Mybatis包含了一个非常强大的查询缓存特性,可以非常方便的定制和配置缓存,缓存可以大大提高查询效率。

mybatis系统默认定义了两级缓存:一级缓存和二级缓存。

默认情况下,仅打开一级缓存。(SqlSession级缓存,也称为本地缓存)

二级缓存需要手动打开和配置,它基于命名空间级缓存。命名空间绑定了一个接口,一个接口映射器对应一个表或者一个业务,所以是表级缓存,整个映射器文件中的所有方法都是共享的。

为了提供扩展性,mybatis定义了缓存接口cache,我们可以通过缓存接口定义L2缓存。

三、一级缓存

在sqlSession会话中有效,如下所示:从开始到结束sqlSession

@测试

public void testGetBlogList() {

try(SQL session SQL session=mybatisutils . getsql session()){

blog mapper mapper=SQL session . get mapper(blog mapper . class);

list blog blogList=mapper . getbloglist();

对于(博客博客:博客列表){

System.out.println(博客);

}

}

}

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

3.1 测试步骤

1 开启日志

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

设置

!-设置名称='logImpl '值='STDOUT_LOGGING'/-

设置名称='logImpl '值='LOG4J'/

设置name=' mapUnderscoreToCamelCase ' value=' true '/

/设置

2 编写测试实体pojo类

包com . happy . POJO;

进口龙目岛。AllArgsConstructor

进口龙目岛。数据;

进口龙目岛。NoArgsConstructor

@数据

@NoArgsConstructor

@AllArgsConstructor

公共类用户{

private int id

私有字符串名称;

私有字符串pwd

}

3 编写mapper接口

包com . happy . Dao;

导入com . happy . POJO . user;

导入org . Apache . ibatis . annotations . param;

导入组织

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

wkizxh4uhnm4067.ico

缓存失效情况
  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,清除最不常使用。

x25b3s1wsup4068.ico

六、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自带的二级缓存没有区别,只是多了一些缓存持久化存放到本地。

rz44kwtg1144069.ico

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

在这里插入图片描述

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

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

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

4、二级缓存默认不开启

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

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

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

在这里插入图片描述

八、其他缓存

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

Link to comment
Share on other sites