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,可以通过以下步骤开启二级缓存:
- 配置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>
- 配置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>
- 配置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
- 配置接口类:
你还可以通过在mapper接口上添加@CacheNamespace注解来启用并配置二级缓存。例如:
@Mapper
@CacheNamespace(implementation= MybatisPlusRedisCache.class,eviction=MybatisPlusRedisCache.class)
public interface InsuranceSettlementMapper extends BaseMapper<InsuranceSettlementEntity> {
}
- 配置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认为:对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。
-
传入的statementId。
-
查询时要求的结果集中的结果范围。
-
这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )。
-
传递给java.sql.Statement要设置的参数值。
示例原因见
https://blog.csdn.net/IPI715718/article/details/98642510