ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Mybatis入门-07-缓存

2020-09-21 20:00:24  阅读:192  来源: 互联网

标签:缓存 07 id println User Mybatis public out


一、前言

关于缓存,官方文档有一些提及,不是很详细,但足够入门。

版本相关:

  • MySQL 8.0.19
  • MyBatis 3.5.5,注意:本内容需要开启日志

参考视频:【狂神说Java】Mybatis最新完整教程IDEA版

二、简介

2.1 缓存

  1. 什么是缓存[Cache]?

    • 存在内存中的临时数据
    • 将用户基础查询的数据放在内存中,用户去查询数据就不用从硬盘上(关系型数据库文件)查询,从缓存中查询,从而提高效率,用于解决高并发系统的性能问题
  2. 为什么使用缓存?

    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据能使用缓存?

    • 经常查询且不经常改变的数据

2.2 MyBatis缓存

  • MyBatis包含一个非常强大的缓存特性

  • MyBatis定义了两级缓存:一级缓存二级缓存

    默认情况下,一级缓存开启,即SqlSession级别的缓存,也成为本地缓存

  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存

  • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过Cache接口去自定义二级缓存。

三、准备工作

3.1 数据库

建表,之前我在mybatis这个数据库里建好了表,现在再拿出来:

create table if not exists `User`(
    `id` INT(20) not null primary key,
    `name` VARCHAR(20) default null,
    `pwd` varchar(20) default null
)ENGINE=INNODB default CHARSET = UTF8;

insert into `User`(`id`,`name`,`pwd`)
values (1,'admin','123456'),
       (2,'Jax','123456'),
       (3,'Jinx','123455'),
       (4,'Query','123456'),
       (5,'biubiu','123456');

3.2 配置文件

关于我mybatis配置、日志配置的文件,请自行配置,下面仅供参考:

路径:

image-20200921102632828

代码

db.properties,一些mybatis-config.xml用到的连接数据库的信息:

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC
username = root
password = qq123456

mybatis-config.xml:

注意:

  • 使用了SLF4J+log4J,如果想使用的话请加入依赖:

            <!--使用slf4j 作为日志门面-->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.7.25</version>
            </dependency>
            <!--使用 log4j2 的适配器进行绑定-->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-slf4j-impl</artifactId>
                <version>2.12.1</version>
                <scope>test</scope>
            </dependency>
    
    
            <!--log4j2 日志门面-->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
                <version>2.12.1</version>
            </dependency>
            <!--log4j2 日志实现-->
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-core</artifactId>
                <version>2.12.1</version>
            </dependency>
    
    
  • <package name="com.duzhuan.pojo"/>可以看到使用了别名,以后pojo包里的实体类的名字会不加全限定名而在Mapper中出现

  • <mapper class="com.duzhuan.dao.UerMapper"/>,这是我注册的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="db.properties"></properties>

    <settings>
        <setting name="logImpl" value="SLF4J"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
    <typeAliases>
        <package name="com.duzhuan.pojo"/>
    </typeAliases>
    

    <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 class="com.duzhuan.dao.UerMapper"/>
    </mappers>
</configuration>

log4j2.xml,日志配置,具体可以看Mybatis入门-03-日志工厂,注意:本内容需要开启日志,因此不想使用下面复杂的日志也没有配置过日志的可以参照Mybatis入门-03-日志工厂里设置STDOUT_LOGGING

<?xml version="1.0" encoding="UTF-8"?>

<!--
    status="debug" 日志框架本身的级别
    configuration还有个属性是 monitorInterval = 5,自动加载配置文件的最小间隔时间,单位是秒
-->
<configuration status="debug">

    <!--
        集中配置属性进行管理,使用时通过:${}
    -->
    <properties>
        <property name="LOG_HOME">./logs</property>
    </properties>

    <!--日志处理器-->
    <!--先定义所有的appender -->
    <appenders>
        <!--这个输出控制台的配置 -->
        <Console name="Console" target="SYSTEM_OUT">
            <!--             控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
            <!--             这个都知道是输出日志的格式 -->
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </Console>

        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用 -->
        <!--append为TRUE表示消息增加到指定文件中,false表示消息覆盖指定的文件内容,默认值是true -->
        <File name="log" fileName="${LOG_HOME}/mybatis-log.log" append="false">
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!--
            添加过滤器ThresholdFilter,可以有选择的输出某个级别以上的类别
            onMatch="ACCEPT" onMismatch="DENY"意思是匹配就接受,否则直接拒绝
        -->
        <File name="ERROR" fileName="${LOG_HOME}/mybatis-error.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </File>

        <!--
            使用随机读写流的日志文件输出appender,性能提高
        -->
        <RandomAccessFile name="accessFile" fileName="${LOG_HOME}/mybatis-access.log">
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
        </RandomAccessFile>

        <!--
            这个会打印出所有的信息,每次大小超过size,
            则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,
            作为存档
         -->
        <RollingFile name="RollingFile" fileName="${LOG_HOME}/mybatis-web.log"
                     filePattern="logs/$${date:yyyy-MM}/web-%d{MM-dd-yyyy}-%i.log.gz">
            <PatternLayout pattern="%d{yyyy-MM-dd 'at' HH:mm:ss z} [%-5level] %class{36} %L %M - %msg%xEx%n"/>
            <SizeBasedTriggeringPolicy size="2MB"/>
        </RollingFile>
    </appenders>


    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
    <loggers>
        <!--使用rootLogger配置   日志级别level="trace" -->
        <root level="trace">
            <!--制定日志使用的处理器-->
            <appender-ref ref="log"/>
            <appender-ref ref="ERROR" />
            <appender-ref ref="Console"/>
            <appender-ref ref="accessFile"/>
            <appender-ref ref="RollingFile"/>
        </root>
    </loggers>
</configuration>

3.3 实体类

路径

image-20200921103500023

代码

package com.duzhuan.pojo;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/21 10:34
 * @Version: 1.0
 * @since: jdk11
 */
public class User {
    int id;
    String name;
    String password;

    public User() {
    }

    public User(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

3.4 工具类

注意,这次工具类和之前的工具类有些许不同:

路径

image-20200921104435609

代码

package com.duzhuan.utils;

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 java.io.IOException;
import java.io.InputStream;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/21 10:39
 * @Version: 1.0
 * @since: jdk11
 */
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static{
        try {
            String config = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(config);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);
    }
}

如果是使用IDEA的话,可以比较清晰地看出来return sqlSessionFactory.openSession(true);openSession()中加true的作用。

image-20200921104651173

就是开启事务自动提交。

四、一级缓存

4.1 Mapper

路径

image-20200921105031645

代码

UserMapper:

package com.duzhuan.dao;

import com.duzhuan.pojo.User;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/21 10:47
 * @Version: 1.0
 * @since: jdk11
 */
public interface UserMapper {
    User getUserById(int id);
}

UserMapper.xml:

<?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.duzhuan.dao.UserMapper">

    <resultMap id="UserMap" type="User">
        <result property="password" column="pwd"/>
    </resultMap>

    <select id="getUserById" parameterType="int" resultMap="UserMap">
        select * from mybatis.user where `id` = #{id}
    </select>
</mapper>

4.2 测试样例

路径

image-20200921121409874

代码

package com.duzhuan.dao;

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

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/21 12:07
 * @Version: 1.0
 * @since: jdk11
 */
public class UserMapperTest {
    @Test
    public void getUserByIdTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.getUserById(1);
        System.out.println(user1);
        System.out.println("==========================================");
        User user2 = mapper.getUserById(2);
        System.out.println(user2);
        System.out.println("==========================================");
        User user1once = mapper.getUserById(1);
        System.out.println(user1once);

        sqlSession.close();
    }
}

查询了两次id为1的用户。

4.3 结果

image-20200921122145331

4.4 缓存失效的情况

官方文档中列了几个说明:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

也就是说:

  1. 查询不同的东西、不同的Mapper.xml,且之前没查询过,不会用到缓存
  2. 增删改操作,可能会改变原来的数据库,所以必定会刷新缓存
  3. 手动清理缓存

4.3 结果中已经展示了1,那么试一下第二点:

测试第二点:增删改

UserMapper中添加方法:

int updateUser(User user);

在UserMapper.xml中添加标签:

    <update id="updateUser" parameterType="User">
        update mybatis.user set `name` = #{name}, pwd = #{password} where id = #{id}
    </update>

测试样例添加:

@Test
public void updateUserTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = new User(6,"Uzi","555555");
    User user1 = mapper.getUserById(1);
    System.out.println(user1);
    System.out.println("==========================================");
    mapper.updateUser(user);
    System.out.println("==========================================");
    User user1once = mapper.getUserById(1);
    System.out.println(user1once);

    sqlSession.close();
}

结果:image-20200921131143947

可以看到,查询的是id=1的用户,改的是id=6的用户,但是缓存依旧失效了,即缓存刷新了。

增删改可能会改变原来的数据,所以会刷新缓存,这是为了保持数据库的一致性

测试第三点:手动清理缓存

添加测试样例:

@Test
public void getUserByIdTestAndClearCache(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user1 = mapper.getUserById(1);
    System.out.println(user1);
    System.out.println("==========================================");

    //清理缓存
    sqlSession.clearCache();

    System.out.println("==========================================");
    User user1once = mapper.getUserById(1);
    System.out.println(user1once);

    sqlSession.close();
}

结果:

image-20200921131832404

一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间(因为每个用户都会创建一个连接,所以一级缓存只有在一个用户不停地刷新一个页面有用)。

一级缓存就是一个Map。

五、二级缓存

  • 二级缓存也叫全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制:
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没有了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中。

官方文档中说:

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

开启缓存:

  1. 开启全局缓存。

    在配置文件中(本文中的mybatis-config.xml)的<settings>里有这么一个设置,虽然是默认开启的,但是一般为了可读性,我们会显式添加<setting name="cacheEnabled" value="true"/>

    设置名 描述 有效值 默认值
    cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true | false true

2.在UerMapper.xml(要使用二级缓存的Mapper)中添加<cache/>

​ 也可以自定义配置:

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

官方文档给的解释:

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

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

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

5.1 Mapper

在本文的测试上,使用:

<cache/>

还是使用:

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

都不会有太大差别,这里使用第二种,将其添加到要启用二级缓存的UserMapper.xml里。

5.2 测试样例

在UserMapperTest中添加:

@Test
public void getUserByIdTestAndTestCache(){
    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

    User userById1 = mapper1.getUserById(1);
    System.out.println(userById1);
    System.out.println("=================================================");
    User userById2 = mapper2.getUserById(1);
    System.out.println(userById2);

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

5.3 结果

先注释掉<cache/>看看不使用二级缓存的情况:

image-20200921141710188

可以看到,不仅是两条SQL,而且是两个连接。

然后将注释去掉,启用二级缓存:

image-20200921141949271

依旧是两个连接两条SQL。

其实在前面引用的官方文档中就有提示:

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

即,当一个连接关闭了,二级缓存才有作用。现在修改测试样例:

@Test
public void getUserByIdTestAndTestCache(){
    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

    User userById1 = mapper1.getUserById(1);
    System.out.println(userById1);
    System.out.println("=================================================");
    sqlSession1.close();
    
    User userById2 = mapper2.getUserById(1);
    System.out.println(userById2);

    sqlSession2.close();
}

注意观察顺序。

注释掉<cache/>时运行程序:

image-20200921142412059

取消注释后运行程序:

image-20200921142650371

同时在测试样例里加入一句System.out.println(userById1 == userById2);

image-20200921142830561

image-20200921142856597

显然,是相同的对象。

5.4 可能遇到的问题

报错:

Caused by: java.io.NotSerializableException: com.duzhuan.pojo.User

解决方法:0

使User实现序列化:

public class User implements Serializable{
.......
}

5.5 小结

  • 只要开启了二级缓存,在同一个命名空间(Mapper)下有效
  • 所有的数据都会先放在一级缓存中
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中

image-20200921195022756

标签:缓存,07,id,println,User,Mybatis,public,out
来源: https://www.cnblogs.com/duzhuan/p/13708115.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有