(七)Mybatis 缓存机制详解

Mybatis 缓存

MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 通过缓存来避免重复的 SQL 查询,提高查询性能。MyBatis 提供了两种缓存机制:一级缓存和二级缓存。

一级缓存(SqlSession 级别的缓存)

一级缓存是 MyBatis 默认开启的,它是一个基于 PerpetualCache 的 HashMap 本地缓存,其作用范围是一个 SqlSession。作用范围为一个 SqlSession 意味着在同一个 SqlSession 内进行查询时,不会重复执行相同的 SQL 语句,而是从缓存中获取。当 SqlSession 提交或回滚时,缓存会被清空。

  • 实现原理

MyBatis 的一级缓存是通过 Executor 类实现的。在执行 SQL 查询时,先根据 SQL 语句、参数等生成一个缓存的 key。然后在缓存中查找是否存在相应的数据。如果存在,则直接从缓存中获取;如果不存在,则执行 SQL 查询并将结果放入缓存中。

  • 局限性
  1. 作用范围仅限于一个 SqlSession,无法跨多个 SqlSession 共享。
  2. 在多个 SqlSession 中执行相同的 SQL 语句时,不能有效利用缓存,可能导致数据不一致。

一级缓存测试

基本代码实现和以前一样,这里为了方便,所以在以前代码基础上,进行测试:

  • UserMapper 接口内容如下:
public interface UserMapper {
    //查询所有用户
    List<User> getUserList();
    //根据ID查询用户信息
    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.hb.dao.UserMapper">
    <select id="getUserList" resultType="User">
        select * from mybatis.user;
    </select>

    <select id="getUserById" resultType="User">
        select * from mybatis.user where id = #{id}
    </select>
</mapper>
  • 测试类如下:
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(1);
        System.out.println(user2);

        sqlSession.close();
    }
}

结果我们可以看到:

缓存失效的情况:

1、当我们查询不同的内容时;

2、对数据库内容的增加、修改、删除,会改变原来的数据,必定刷新缓存,会导致缓存失效,对数据库进行二次访问。

这里对 UserMapper 接口和 UserMapper.xml进行修改,加上 update 的相关操作然后修改测试代码:

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

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.getUserById(1);
        System.out.println(user1);
        mapper.updateUserInfo(new User(4,"小孙","77777776"));
        sqlSession.commit();
        System.out.println("========================");
        User user2 = mapper.getUserById(1);
        System.out.println(user2);

        sqlSession.close();
    }

查看结果:

3、查询不同的的Mapper.xml

4、手动清理了缓存:在测试代码中的sqlSession生命周期内,执行了sqlSession.clearCache();

二级缓存(Mapper 级别的缓存)

二级缓存的作用范围是一个 Mapper,可以被多个 SqlSession 共享。二级缓存需要用户手动开启。开启方法是在 Mapper 的 XML 配置文件中添加 <cache /> 标签。

这个简单语句的效果如下:

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

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

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

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

实现原理

二级缓存是通过 Cache 接口实现的。MyBatis 提供了多种二级缓存的实现,如 PerpetualCache(基于 HashMap 实现)、FifoCache(基于先进先出策略实现)、SoftCache(基于软引用实现)等。

默认的清除策略是 LRU(最近最少使用:移除最长时间不被使用的对象)。

用户也可以自定义实现二级缓存。

当一个 SqlSession 查询数据时,会先从二级缓存中查找。如果二级缓存中不存在,则从一级缓存中查找。如果一级缓存中也不存在,则执行 SQL 查询,并将结果保存到一级缓存和二级缓存中。当一个 SqlSession 提交或回滚时,会将一级缓存中的数据同步到二级缓存中。

优势和注意事项

二级缓存的优势在于,它可以跨多个 SqlSession 共享数据,提高查询性能。但是,使用二级缓存时需要注意以下几点:

  1. 要保证缓存数据的线程安全。MyBatis 提供的实现都是线程安全的,但自定义实现时需要注意。
  2. 只有在 <cache /> 标签中配置的 Mapper 才会启用二级缓存。如果不想启用缓存,可以在 <select /> 标签中添加 useCache="false" 属性。
  3. 对于更新操作,MyBatis 会自动清空与更新相关的二级缓存。但在分布式环境下,需要注意缓存同步问题。可以考虑使用分布式缓存,如 Redis、Memcached 等。
  4. 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

总之,MyBatis 的缓存机制可以大幅提高查询性能,但在使用时需要注意作用范围、线程安全、缓存同步等问题。在实际开发中,应根据业务需求选择合适的缓存策略。

自定义缓存

MyBatis 是一款优秀的 ORM 框架,它提供了一些默认的缓存配置来提高查询性能。不过有时候我们需要根据具体的业务需求来自定义缓存。

MyBatis 支持自定义缓存,可以通过实现 Cache 接口来实现自定义缓存。Cache 接口中定义了一些方法,包括 putObject、getObject、removeObject、clear 等,这些方法用于实现缓存的增加、查询、删除和清空等操作。

下面自定义一个缓存:

public class MyCache implements Cache {
    private Map<Object, Object> cache = new HashMap<Object, Object>();
    private String id;

    public MyCache(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }

    @Override
    public void clear() {
        cache.clear();
    }

    @Override
    public int getSize() {
        return cache.size();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        // 返回一个读写锁,用于并发控制
        return null;
    }
}

在这个示例中,我们实现了 Cache 接口,并且使用 HashMap 作为缓存的存储结构。在 putObject 方法中,将 key-value 对放入缓存中;在 getObject 方法中,根据 key 获取缓存中的 value;在 removeObject 方法中,根据 key 将缓存中的 key-value 对移除;在 clear 方法中,清空缓存。这些操作都是非常简单的。

需要注意的是,上面的示例中的 getReadWriteLock 方法返回了 null,这意味着我们没有提供并发控制,这个自定义缓存是线程不安全的。如果需要实现线程安全,可以返回一个读写锁,用于并发控制。

自定义缓存完成后,我们需要在 MyBatis 的配置文件中配置自定义缓存。假设我们的自定义缓存类是 MyCache,我们可以在 MyBatis 配置文件中增加配置:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

其中,type 属性指定了自定义缓存的类名。

这样,我们就完成了自定义缓存的操作。需要注意的是,自定义缓存可能会影响数据的一致性,因此在使用自定义缓存时需要谨慎考虑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HB0o0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值