美文网首页
104、【JavaEE】【Mybatis】缓存

104、【JavaEE】【Mybatis】缓存

作者: yscyber | 来源:发表于2021-10-20 02:01 被阅读0次

1、概述

  • 通过使用缓存一些数据(经常使用但是很少更新的),减少对数据库的访问,进而提高系统的效率。

  • MyBatis 等多数持久化框架都提供了缓存策略。

  • MyBatis 的缓存分为一级缓存、二级缓存。

2、一级缓存

  • 一级缓存是 MyBatis 默认开启的缓存机制。

  • 一级缓存是 SqlSession 级别的缓存,意思是一个SqlSession对象会对应有一个缓存空间,同一个SqlSession对象使用同一个缓存空间。

  • 参数SQL 语句完全一样的情况下,我们使用同一个SqlSession对象调用一个 Mapper 中的查询方法,只在第一次查询执行一次 SQL,因为使用SqlSession第一次查询后,MyBatis 会将其放在一级缓存中,以后再查询的时候,如果没有声明需要刷新(没有调用过SqlSessionclearCache()commit()close()等方法),并且缓存没有超时的情况下,SqlSession都会取当前缓存中的数据,而不会再次发送 SQL 语句到数据库。

  • 执行SqlSession的C(增加)U(更新)D(删除)操作,或者调
    clearCache()commit()close()方法,都会清空一级缓存。

  • 注意,必须是由 MyBatis 执行 CUD 操作或者执行当前SqlSession对象的clearCache()commit()close()方法,才会清空一级缓存;直接对数据库的操作(在数据库命令行客户端等执行 SQL)是无法清空一级缓存的。总之,一级缓存是 MyBatis 提供的,清空必须通过 MyBatis 实现(二级也是这个道理)

  • 在每一次查询后,自动清除一级缓存,在映射配置文件中配置:

<select ····· flushCache="true">
    ······
</select>

2、二级缓存

  • 二级缓存,默认不开启,需要配置。

  • 二级缓存,基本上不使用,因为容易造成“脏读”。会有一些更好的技术手段来实现缓存的作用。

  • 二级缓存是一种跨 SqlSession 的缓存,是 Namespace 级别或 Mapper 级别的缓存。意思是,即使是不同的SqlSession对象,只要使用的是同一个 Mapper,使用的将是同一个二级缓存空间。

MyBatis-15
  • 二级缓存的清空
MyBatis-16
  • 开启二级缓存:

1、相关的 POJO 需要实现序列化接口,即实现java.io.Serializable接口。

2、在核心配置文件中配置<setting name="cacheEnabled" value="true"/>(默认值为true,所以不显式配置也行)。cacheEnabled的含义是“全局性地开启或关闭所有映射器配置文件中已配置的任何缓存”,意思是cacheEnabledfalse,映射配置文件中的<cache/>配置也将无效。

3、在映射配置文件中配置<cache/>

4、在<select>标签中,使用useCache=true,表示当前查询将会使用二级缓存;否则该查询禁用二级缓存。

3、一级缓存与二级缓存的关系

  • 二级缓存开启的情况下,查询顺序为:二级缓存 -> 一级缓存 -> 数据库,即二级缓存中没有的到一级缓存中去查,一级缓存中仍没有再到数据库中查询。

  • 二级缓存开启的情况下,当数据是从数据库中读取出来的,先放入一级缓存,当执行了SqlSessionclose()commit()方法后,一级缓存中的内容会存到二级缓存中。

4、案例

4.1、二级缓存造成“脏读”

  • 以“用户-订单”为例,“一个用户对应多个订单”。在“查询用户的时候同时查询出所有订单”,对“查询用户”语句使用二级缓存。
    先查询出一个用户信息,然后对订单进行更新,然后再次查询该用户信息。由于开启了“用户 Mapper”的二级缓存,而更新是“订单 Mapper”负责,所以“更新订单”的时候是无法清除“用户 Mapper”的二级缓存的,最终,再次查询该用户,仍是从二级缓存中直接获取,而不是从数据库获取。典型“脏读”。
-- 用户表
CREATE TABLE lg_user (
user_id INT,
user_nickname VARCHAR(20) NOT NULL,
user_phone_number VARCHAR(20) NOT NULL,
CONSTRAINT PRIMARY KEY pk_lg_user(user_id)
);

-- 订单表
CREATE TABLE lg_order (
order_id INT,
order_account DECIMAL(8,2) NOT NULL,
order_user_id INT NOT NULL,
CONSTRAINT PRIMARY KEY pk_lg_order(order_id)
);
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

import java.math.BigDecimal;

@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class Order implements Serializable {

    private static final long serialVersionUID = 7010281991983231524L;

    private Integer id;

    private BigDecimal account;

}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;
import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString()
public class User implements Serializable {

    private static final long serialVersionUID = -6402364968509046679L;

    private Integer id;

    private String nickname;

    private String phoneNumber;

    private List<Order> orderList;

}
import com.yscyber.mybatis.five.pojo.Order;

import java.util.List;

public interface OrderRepo {

    List<Order> listOrdersByUserId(Integer userId);

    int insertOrder(Order order);

}
import com.yscyber.mybatis.five.pojo.User;

public interface UserRepo {

    User getUserById(Integer id);

}
<?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.yscyber.mybatis.five.repo.OrderRepo">

    <resultMap id="orderResultMap" type="Order">
        <id property="id" column="order_id"/>
        <result property="account" column="order_account"/>
    </resultMap>

    <select id="listOrdersByUserId" parameterType="int" resultMap="orderResultMap">
        SELECT order_id, order_account
        FROM lg_order
        WHERE order_user_id=#{userId}
    </select>

    <insert id="insertOrder" parameterType="Order">
        INSERT INTO lg_order(order_id, order_account, order_user_id)
        VALUES (#{id}, #{account}, 1)
    </insert>

</mapper>
<?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.yscyber.mybatis.five.repo.UserRepo">

    <cache/>

    <resultMap id="userResultMap" type="User">
        <id property="id" column="user_id"/>
        <result property="nickname" column="user_nickname"/>
        <result property="phoneNumber" column="user_phone_number"/>

        <!--
            将查询出的 column="user_id" 作为 listOrdersByUserId 参数
         -->
        <collection property="orderList" ofType="Order" select="com.yscyber.mybatis.five.repo.OrderRepo.listOrdersByUserId" column="user_id">
            <id property="id" column="order_id"/>
            <result property="account" column="order_account"/>
        </collection>
    </resultMap>
    
    <select id="getUserById" parameterType="int" resultMap="userResultMap" useCache="true">
        SELECT user_id, user_nickname, user_phone_number
        FROM lg_user
        WHERE user_id=#{id}
    </select>

</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="jdbc.properties"/>
    
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        
        <setting name="cacheEnabled" value="true"/>

        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
        <setting name="lazyLoadTriggerMethods" value=""/>
    </settings>

    <typeAliases>
        <package name="com.yscyber.mybatis.five.pojo"/>
    </typeAliases>
    
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>

    <environments default="dev">
        <!-- 配置数据源 -->
        <environment id="dev">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc_driver}"/>
                <property name="url" value="${jdbc_url}"/>
                <property name="username" value="${jdbc_username}"/>
                <property name="password" value="${jdbc_password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.yscyber.mybatis.five.repo"/>
    </mappers>
    
</configuration>
    @Test
    public void test1() throws IOException {
        InputStream ris = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ris);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserRepo userRepo = sqlSession.getMapper(UserRepo.class);
        OrderRepo orderRepo = sqlSession.getMapper(OrderRepo.class);

        User user1 = userRepo.getUserById(1); // UserRepo 查询
        System.out.println(user1);

        Order order = new Order();
        order.setId(4);
        order.setAccount(new BigDecimal(23.00));
        orderRepo.insertOrder(order); // OrderRepo 更新
        sqlSession.commit(); // 这里的 commit 方法无法清空 UserRepo 的二级缓存,只能清空该 SqlSession 对象的一级缓存以及 OrderRepo 的二级缓存(此处 OrderRepo 也没有开启二级缓存)

        User user2 = userRepo.getUserById(1);
        System.out.println(user2);

        sqlSession.close();
    }

最后,因为 UserRepo 的二级缓存,读取出的“订单”不是更新后的“订单”,还是第一次查询出的“订单”,造成“脏读”。

相关文章

网友评论

      本文标题:104、【JavaEE】【Mybatis】缓存

      本文链接:https://www.haomeiwen.com/subject/hoewoltx.html