Mybatis的缓存机制

mybatis的查询过程

MyBatis 查询连接过程通常指的是 MyBatis 如何获取数据库连接的过程。MyBatis 通过 SqlSessionFactory 和 SqlSession 来管理数据库连接。

以下是一个简单的例子,展示如何使用 MyBatis 获取 SqlSession 从而进行数据库操作:

// 1. 获取 SqlSessionFactory 实例
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 
// 2. 获取 SqlSession 实例
SqlSession sqlSession = sqlSessionFactory.openSession();
 
// 3. 执行数据库操作
try {
    // 获取 Mapper 接口实例
    YourMapper mapper = sqlSession.getMapper(YourMapper.class);
    // 调用 Mapper 接口中的方法进行数据库操作
    YourResultType result = mapper.yourMethod(params);
    // 处理结果
} finally {
    // 确保释放资源
    sqlSession.close();
}

在这个例子中:

通过 SqlSessionFactoryBuilder 读取 MyBatis 配置文件 mybatis-config.xml 并构建 SqlSessionFactory 实例。
通过 SqlSessionFactory 获取 SqlSession 实例,一级缓存是SqlSession级别的缓存,它默认是开启的。通过 SqlSession 获取 Mapper 接口的代理实现,并执行数据库操作。操作完成后,确保正确关闭 SqlSession 释放资源。这个过程展示了 MyBatis 如何管理数据库连接和执行数据库操作。

二级缓存是Mapper级别的缓存,它可以被多个SqlSession共享,例如在xml文件 的<select>标签添加 flushCache=“true”, 禁用此查询的一级缓存

一级缓存的手动清理

方案一:通过SqlSessionUtils.getSqlSession(sqlSessionFactory).clearCache()方法刷新缓存
在mybatisplus 缓存中使用的是清除一级缓存

public interface YourMapper extends BaseMapper<YourEntity> {
    @Select("SELECT * FROM your_table WHERE condition = #{value}")
    @Options(flushCache = Options.FlushCachePolicy.TRUE)
    List<YourEntity> selectWithoutCache(@Param("value") String value);
}

或者在服务层

@Autowired
private SqlSession sqlSession;
 
public List<YourEntity> selectWithoutCache() {
    try {
        // 清除缓存
        sqlSession.clearCache();
        
        // 执行查询
        return yourMapper.selectList(/* 查询条件 */);
    } finally {
        // 可以选择在 finally 块中清除缓存,确保即使发生异常也能清除缓存
        sqlSession.clearCache();
    }
}

方案二:在mapper.xml对应的查找语句中添加flushCache=“true” ,注意,手动清理数据,缓存中的数据并不会立即提交到数据库中,需要手动提交才行。这块清理的为二级缓存。

Mybatis 二级缓存全详解教程

【Mybatis-Plus】Mybatis-Plus 二级缓存全详解

一,Mybatis-Plus介绍

MyBatis-Plus(简称MP)是一个基于 MyBatis 的增强工具,它简化了 MyBatis 的开发,并且提供了许多便利的功能,帮助开发者更高效地进行持久层的开发。

二,Mybatis-Plus缓存级别 一级缓存,二级缓存

MyBatis-Plus 与 MyBatis 一样,也依赖于 MyBatis 的缓存机制,包括一级缓存(本地缓存)和二级缓存(全局缓存)。

  • 一级缓存(本地缓存):

一级缓存是指在同一个 SqlSession 内部的缓存。它默认是开启的,且无法关闭。
当执行查询语句时,如果之前已经从数据库中查询过相同的结果,则会直接从缓存中获取结果,而不需要再次查询数据库。
一级缓存的作用范围是 SqlSession 级别的,也就是说,只有在同一个 SqlSession 中执行的查询操作才会共享同一个一级缓存。
当 SqlSession 关闭时,一级缓存将被清空,这意味着一级缓存的生命周期与 SqlSession 是一致的。

  • 二级缓存(全局缓存):

    • 二级缓存是指多个 SqlSession 之间共享的缓存,它的作用范围是 Mapper 级别的。
    • 二级缓存需要手动开启和配置,可以通过在 Mapper 接口或者 XML 配置文件中添加 标签来配置。
    • 当执行查询语句时,如果之前已经从数据库中查询过相同的结果,并且该结果被保存在二级缓存中,则会直接从缓存中获取结果,而不需要再次查询数据库。
    • 二级缓存的生命周期是全局的,在多个 SqlSession 中共享,但是它需要考虑缓存的一致性和并发访问的安全性。
    • 在更新数据时,需要手动清除或刷新二级缓存,以保证数据的正确性。

总的来说,一级缓存是 SqlSession 级别的缓存,而二级缓存是 Mapper 级别的缓存。一级缓存默认开启且无法关闭,而二级缓存需要手动配置开启。

Spring Boot 整合Mybatis 开启二级缓存

在这里插入图片描述

在Spring Boot中整合MyBatis,可以通过以下步骤开启二级缓存:

  1. 配置mybatis-config.xml文件:
    在mybatis-config.xml文件中,需要添加如下配置:
<configuration>
  <settings>
    <setting name="cacheEnabled" value="true"/> <!-- 开启二级缓存 -->
    <setting name="lazyLoadingEnabled" value="true"/> <!-- 开启延迟加载 -->
    <setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极的延迟加载 -->
  </settings>
  <typeAliases>
    ...
  </typeAliases>
  <mappers>
    ...
  </mappers>
</configuration>

  1. 配置mapper.xml文件:
    在mapper.xml文件中,需要添加如下配置:
<mapper namespace="com.example.myexcel.dao.InsuranceSettlementMapper">

    <resultMap type="com.example.myexcel.pojo.entity.InsuranceSettlementEntity" id="insuranceSettlementMap">
        <result property="insuranceName" column="insurance_name"/>
        <result property="credentialNo" column="credential_no"/>
        <result property="drugApplyId" column="drug_apply_id"/>
        <result property="totalAmount" column="total_amount"/>
    </resultMap>
    <cache-ref namespace="com.example.myexcel.dao.InsuranceSettlementMapper"/>

</mapper>

  1. 配置application.yml文件:
    在application.yml文件中,需要添加如下配置:
# 开启MyBatis二级缓存
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.myexcel.pojo.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志
    #缓存开启
    cache-enabled: true

  1. 配置接口类:
    你还可以通过在mapper接口上添加@CacheNamespace注解来启用并配置二级缓存。例如:
@Mapper
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)
public interface InsuranceSettlementMapper extends BaseMapper<InsuranceSettlementEntity> {
}

  1. 配置Redis:
    如果使用Redis作为二级缓存的实现,需要在application.yml文件中添加如下配置:
# Redis配置
  redis:
    database: 15
    host: 
    port: 6379
    password: 123456  # 密码(默认为空)
    timeout: 6000ms  # 连接超时时长(毫秒)
    lettuce:
      pool:
        max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms      # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 10      # 连接池中的最大空闲连接
        min-idle: 5       # 连接池中的最小空闲连接

@Test
    public void query(){

        log.info("*****************************************************************************************");
        List<InsuranceSettlementEntity> list = insuranceSettlementService.list();
        log.info("列表:{}",list);
    }

缓存查询列表测试结果:先查询缓存,发现没有数据后查询数据再存入缓存
在这里插入图片描述
缓存查询列表测试结果:先查询缓存,发现有数据直接返回数据
在这里插入图片描述

mybatisplus 禁用一级缓存在springboot中禁用 spring mybatis 一级缓存

https://blog.51cto.com/u_16213659/10995973

熟悉MyBatis的小伙伴都知道MyBatis默认开启一级缓存,当我们执行一条查询语句后,MyBatis会以我们查询的信息生成一个缓存key,查询的结果为value,存到一个map中,即存入一级缓存。

环境:Mybatis + Spring,MyBatis的一级缓存使用默认值,即开启一级缓存

测试1
在一个serviceImpl中连续调用三次dao层查询数据库

@GetMapping("/test")
public void test() throws JsonProcessingException {
    List<User> user = userService.getUser();
    List<User> user1 = userService.getUser();
    List<User> user2 = userService.getUser();
}

现象

日志显示,创建了三个sqlSession,查询了三次数据库

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7e8fe6ec] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8357a79] will not be managed by Spring
==>  Preparing: SELECT id,username,password FROM user
==> Parameters: 
<==    Columns: id, username, password
<==        Row: 1, 李显赫, 1343
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7e8fe6ec]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64cc7639] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8357a79] will not be managed by Spring
==>  Preparing: SELECT id,username,password FROM user
==> Parameters: 
<==    Columns: id, username, password
<==        Row: 1, 李显赫, 1343
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64cc7639]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@47ede809] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@8357a79] will not be managed by Spring
==>  Preparing: SELECT id,username,password FROM user
==> Parameters: 
<==    Columns: id, username, password
<==        Row: 1, 李显赫, 1343
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@47ede809]

结论

MyBatis的一级缓存没起作用

测试2

我们在serviceImpl的方法上加入 @Transactional

@GetMapping("/test2")
@Transactional
public void test() throws JsonProcessingException {
    List<User> user = userService.getUser();
    List<User> user1 = userService.getUser();
    List<User> user2 = userService.getUser();
}

现象2

日志显示,创建了一个sqlSession,查询了一次数据库

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d29be04]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7e9f4a27] will be managed by Spring
==>  Preparing: SELECT id,username,password FROM user
==> Parameters: 
<==    Columns: id, username, password
<==        Row: 1, 李显赫, 1343
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d29be04]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d29be04] from current transaction
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d29be04]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d29be04] from current transaction
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4d29be04]

结论2

MyBatis的一级缓存又起作用了

mybatis 一级缓存查询不到数据的示例

程序先插入数据至数据库,插入成功后推送至MQ,MQ消费到消息后,在根据推送的数据查询数据库。可以数据插入成功后,通过客户端可以查询到插入的记录,但是通过数据库程序查询却查询不到数据,就是这么奇怪。事后,每次程序在查询数据的时候Thread.sleep个几秒后就能查询到数据。

插入数据代码

 @Override
    public void addNodeInstance(ASGxNodeInstance nodeInstance) {
        //entity转换为DO
        ASGxNodeInstanceDO nodeInstanceDO = converter.entityConvertToDO(nodeInstance);
        baseMapper.insert(nodeInstanceDO);
    }

插入成功后的日志,可以查看到插入成功了

Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12dba123] from current transaction
==>  Preparing: INSERT INTO t_asg_node_instance ( ID, ASSIGNMENT_INSTANCE_ID, EXECUTOR_ID, STATE_CODE, NODE_INSTANCE_INDEX, PARENT, ORIGN_NODE_ID, CREATE_TIME, CREATE_BY, UPDATE_TIME, UPDATE_BY, DEL_FLAG ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 1827890636498534400(Long), 1827890634317496320(Long), 1695730122804490240(Long), 0(Integer), fa5ade49634f11ef843d005056a9110e(String), 1827890634338467840(Long), 1827890634636263424(Long), 2024-08-26T10:07:42.964(LocalDateTime), 99779001(String), 2024-08-26T10:07:42.964(LocalDateTime), 99779001(String), 0(String)
<==    Updates: 1

从MQ 接收到消息后,查询数据的日志

Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1700b5b0] from current transaction
==>  Preparing: SELECT ID,ASSIGNMENT_INSTANCE_ID,EXECUTOR_ID,STATE_CODE,NODE_INSTANCE_INDEX,PARENT,ORIGN_NODE_ID,CREATE_TIME,CREATE_BY,UPDATE_TIME,UPDATE_BY,DEL_FLAG,VERSION FROM t_asg_node_instance WHERE ID=? AND DEL_FLAG='0'
==> Parameters: 1827890636498534400(Long)
<==      Total: 0

可以通过日志看到插入数据的时候使用的是Fetched SqlSession 拿到一个sqlSession 然后创建插入事务的。Fetched SqlSession 是在事务阻塞的情况下拿到的缓存sqlsession ,执行成功后的事务数据并 不会立即提交到数据库,等到别的事务释放表锁后,在拿到表锁进行事务提交。数据库查询的时候拿去的也是一个Fetched SqlSession 缓存,因此当然也查询不到还未提交数据库的数据。所以这就造成我们在客户端可以查询到,但是程序每次查询不到。

优化如下


    @Autowired
    private SqlSessionFactory sqlSessionFactory ;

    @Override
    public void addNodeInstance(ASGxNodeInstance nodeInstance) {
        //entity转换为DO
        SqlSession sqlSession= null;
        try{
             sqlSession= sqlSessionFactory.openSession();
            ASGxNodeInstanceDO nodeInstanceDO = converter.entityConvertToDO(nodeInstance);
            sqlSession.getMapper(ASGxNodeInstanceMapper.class).insert(nodeInstanceDO);
            sqlSession.commit();
            sqlSession.clearCache();
        }catch(Exception e){
           log.error(e.getMessage(),e);
        }finally {
            if(sqlSession!=null){
                sqlSession.close();
            }
        }
    }

手动获取SqlSession ,获取之后,手动提交提交成功之后再清理SqlSession 缓存。这样SqlSession 中的数据会主动提交到提交到数据库。

更改后,可以看到插入的时候直接使用JDBC Connection 获取连接,没有使用sqlSession 的缓存,因此数据直接落库后才推送消息,可以查询到

插入日志如下

2024-08-27 16:11:16.538  INFO 18410 --- [nio-7015-exec-4] c.c.c.c.a.e.a.ASGxWorkFlowExecutor       : workFlow create after: {"processInstanceId":"ee8ac039644b11ef843d005056a9110e","dealMsg":"创建流程实例成功","pageSize":0,"totalCount":0,"pageNum":0,"dealFlag":true} time = 1206
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1b6980e9] will be managed by Spring
==>  Preparing: INSERT INTO t_asg_node_instance ( ID, ASSIGNMENT_INSTANCE_ID, EXECUTOR_ID, STATE_CODE, NODE_INSTANCE_INDEX, PARENT, ORIGN_NODE_ID, CREATE_TIME, CREATE_BY, UPDATE_TIME, UPDATE_BY, DEL_FLAG ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: 1828344517124927488(Long), 1828344510648922112(Long), 1695730122804490240(Long), 0(Integer), ee8ac039644b11ef843d005056a9110e(String), 1828344511294844928(Long), 1828344511835910144(Long), 2024-08-27T16:11:16.539(LocalDateTime), 99779001(String), 2024-08-27T16:11:16.539(LocalDateTime), 99779001(String), 0(String)
<==    Updates: 1
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e1b42ce]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e1b42ce]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e1b42ce]

查询

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1e27e5ed]
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@1b6980e9] will be managed by Spring
==>  Preparing: SELECT ID,EXEC_CODE,PROCESS_ID,FLOW_ID,TASKINDEX_ID,LOGIC_FLAG,CREATE_TIME,CREATE_BY,UPDATE_TIME,UPDATE_BY,DEL_FLAG,VERSION FROM t_asg_executor_header WHERE ID=? AND DEL_FLAG='0'
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@311d9e63] will be managed by Spring
==>  Preparing: SELECT ID,ASSIGNMENT_INSTANCE_ID,EXECUTOR_ID,STATE_CODE,NODE_INSTANCE_INDEX,PARENT,ORIGN_NODE_ID,CREATE_TIME,CREATE_BY,UPDATE_TIME,UPDATE_BY,DEL_FLAG,VERSION FROM t_asg_node_instance WHERE DEL_FLAG='0' AND (NODE_INSTANCE_INDEX = ?)
==> Parameters: 1695730122804490240(Long)
==> Parameters: ee8ac039644b11ef843d005056a9110e(String)
<==    Columns: ID, ASSIGNMENT_INSTANCE_ID, EXECUTOR_ID, STATE_CODE, NODE_INSTANCE_INDEX, PARENT, ORIGN_NODE_ID, CREATE_TIME, CREATE_BY, UPDATE_TIME, UPDATE_BY, DEL_FLAG, VERSION
<==        Row: 1828344517124927488, 1828344510648922112, 1695730122804490240, 0, ee8ac039644b11ef843d005056a9110e, 1828344511294844928, 1828344511835910144, 2024-08-27 16:11:17, 99779001, 2024-08-27 16:11:17, 99779001, 0, 0
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@35eaf65c]

mybatis 一级缓存和事务之间的关系

结论:个人认为在开发中即使是查询service也应该加入事务,尤其是对于一个service中存在多个查询操作,优点是当存在着相同的查询并且两次查询之间不存在增删改操作时,可以使用到缓存,即使是不相同的查询,也会减少SqlSession实例的创建和销毁,能够提升效率,减少时间的浪费。

https://blog.csdn.net/IPI715718/article/details/98642510

Mybatis 默认开启一级缓存,其一级缓存是SqlSession级别的,sqlSession级别的缓存,意味着伴随着sqlSession的生死。

一级缓存的作用:当使用同一个sqlSession对数据库做相同的查询时,第一次查询的结果会放入缓存,在缓存中是以Map的形式存放的,当后面相同的查询到来时就会去缓存中取数据,而不再查询数据库

注意:这里的后面相同的查询到来时就会去缓存中取数据是有条件的,条件就是在第一次查询后和后几次的相同查询之间没有增删改操作,否则缓存中的数据会被清除,后来的相同查询会因在缓存中找不到缓存结果而去数据库中查询。

mybatis认为:对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

  1. 传入的statementId。

  2. 查询时要求的结果集中的结果范围。

  3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )。

  4. 传递给java.sql.Statement要设置的参数值。
    示例原因见
    https://blog.csdn.net/IPI715718/article/details/98642510

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值