SqlSession 和 SqlSessionTemplate 简单使用及注意事项
1、SqlSession 简单使用
先简单说下 SqlSession 是什么?SqlSession 是对 Connection 的包装,简化对数据库操作。所以你获取到一个 SqlSession 就相当于获取到一个数据库连接,就可以对数据库进行操作。
SqlSession API 如下图示:

配置好数据,直接通过 SqlSessionFactory 工厂获取 SqlSession 示例,代码如下:
public class MyBatisCacheTest {private static SqlSessionFactory sqlSessionFactory;private static Configuration configuration;private static JdbcTransaction jdbcTransaction;private static Connection connection;private static MappedStatement mappedStatement;private static SqlSession sqlSession;static {try {InputStream inputStream = MyBatisCacheTest.class.getResourceAsStream("/mybatis-config.xml");sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);configuration = sqlSessionFactory.getConfiguration();configuration.setCacheEnabled(true);connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/gwmdb?useSSL=false&allowPublicKeyRetrieval=true", "root", "itsme999");jdbcTransaction = new JdbcTransaction(connection);String statement = "org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson";mappedStatement = configuration.getMappedStatement( statement);// 注意这里设置了自动提交sqlSession = sqlSessionFactory.openSession(true);} catch (Exception e) {e.printStackTrace();}}}
2、SqlSession 缓存使用
SqlSession 获取到后开始演示下它的缓存使用。代码如下:
public static void main(String[] args) throws Exception {PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);Person person = mapper.getPerson(1);Person person1 = mapper.getPerson(1);System.out.println("person==person1 = " + (person == person1));}
最终结果输出为 true,因为在 SqlSession 里面是有缓存的,默认一级缓存开启,二级缓存不开启,这里暂时不讲二级缓存,想了解请看二级缓存使用篇。
但是在使用这个一级缓存时,需要注意,在多线程环境下面,会出现数据安全问题,多线程并发操作代码如下:
public static void main(String[] args) throws Exception {for (int i = 0; i < COUNT; i++) {new Thread(() -> {// 准备好 10 个线程try {cdl.await();} catch (Exception e) {e.printStackTrace();}// 随便调用其中一个查询方法PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);Person person = mapper.getPerson(1);System.out.println("person = " + person);}).start();cdl.countDown();}}
抛出异常如下:
### Cause: java.lang.ClassCastException: org.apache.ibatis.executor.ExecutionPlaceholder cannot be cast to java.util.Listat org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:155)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)at org.apache.ibatis.gwmtest.MyBatisCacheTest.lambda$main$0(MyBatisCacheTest.java:77)at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.ClassCastException: org.apache.ibatis.executor.ExecutionPlaceholder cannot be cast to java.util.Listat org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:163)at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:137)at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:90)at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:153)... 5 more
具体原因是为什么呢?因为在多线程环境下面,共用同一个 SqlSession 导致的,具体原因看源码,SqlSession 底层调用 Executor,在 MyBatis 中它们是一对一关系。
在 MyBatis 中有分三个基本执行器:
- SimpleExecutor:每次数据库操作都需要重新编译 SQL 语句,然后开始操作数据库
- ResuExecutor (推荐):只有第一次访问数据库会编译 SQL 语句,后面不会重新编译,提高效率,然后操作数据库
- BatchExecutor:当需要批量操作数据库时,进行打包分批访问数据库
除了上面三个基本 Executor 之外,因为还有一些公共的操作,所以向上衍生出一个 BaseExecutor,比如最基本的一级缓存就是在这个执行器做的,因为一级缓存是本地缓存不能跨线程使用,所以又继续向上衍生出 CachingExecutor,二级缓存就是在这里做的,这里可以定义一些缓存比如:Redis、MongoDB 等等。
看到 SqlSession 操作一级缓存的地方(BaseExecutor 类中),源码如下:
@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// ...Object object = localCache.getObject(key);List<E> list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}return list;}private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {// ...localCache.putObject(key, EXECUTION_PLACEHOLDER);List<E> list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);localCache.putObject(key, list);return list;}
假设现在两个线程并发调用 mapper.getPerson(1) ,最终都要拿到 SqlSession 实例去操作数据库。而 SqlSession 和 Executor 是一对一关系,SqlSession 最终会给到 BaseExecutor 处理,最终调用上面的源码 query() 方法。
而上面的源码你只需要关注两个地方:存和取缓存。存缓存的地方注意细节,MyBatis 会先往一级缓存中保存一个占位符 EXECUTION_PLACEHOLDER,具体作用是为了能够解决子查询中循环依赖问题,不展开叙述。注意这里保存的是占位符。假设现在线程1过来恰好往一级缓存中保存完这个占位符,但是线程1此时没来得及往下执行,CPU 执行权被线程2抢走,那么现在线程2过来执行 query() 方法,因为是同一个 SqlSession,所以 cacheKey 是一模一样的,线程2会去一级缓存中取值,此时线程2取出来的肯定是线程1之前在里面保存的占位符。线程1拿到这个占位符之后,开始执行类型转换,也就是对应这句代码:(List) localCache.getObject(key),你觉得此时泛型转换能成功么?肯定不能,所以直接抛出异常。
解决办法是什么?源码不太好改,只能从使用层面进行改进,主要是因为缓存 key 是一样的,线程1从缓存中可以取出一个占位符,那么让缓存 key 不一样不就行了么?最快最简单的让缓存 key 不一样就是换一个 SqlSession。用不同的会话去操作数据库是不会出现这样的问题。所以最终改进的代码如下:
public static void main(String[] args) throws Exception {for (int i = 0; i < COUNT; i++) {new Thread(() -> {// 准备好 10 个线程try {cdl.await();} catch (Exception e) {e.printStackTrace();}// 调用查询方法sqlSession = sqlSessionFactory.openSession(true);PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);Person person = mapper.getPerson(1);System.out.println("person = " + person);}).start();cdl.countDown();}}
就是每次都重新生成一个 SqlSession 实例。其实底层也换了一个 Connection 实例。这个就是我们常说的线程安全问题是 SqlSession 的一个实现 DefaultSqlSession,MyBatis 作者也对此类加以Note that this class is not Thread-Safe的注释。
或者换个理解 SqlSesion 线程不安全,SqlSesion 是 Mybatis 中的会话单元,对于 Mybatis 中而言,一个会话对应一个 SqlSession,也对应一个JDBC中的 Connection。多个线程同时操作 Connection,A线程执行完 SQL,还想再执行点其他的,但是B线程对这个 Connection 进行commit 操作,导致A线程一脸懵逼。
2、SqlSessionTemplate 简单使用
上面 SqlSession 存在这样的安全问题,Spring 在继承它的时候,做了改进,在 SqlSession 上继续封装一层,具体是通过动态代理做的。SqlSessionTemplate 在每次调用 API 时都会重新给你创建 SqlSession 实例。这样就能保证每次都在不同的 SqlSession 会话中操作数据库,比较安全。
下面开始演示个问题,代码如下:
public static void main(String[] args) {PaymentMapper paymentMapper = context.getBean(PaymentMapper.class);Payment payment = paymentMapper.queryAccount(1);Payment payment1 = paymentMapper.queryAccount(1);System.out.println("payment1 == payment = " + (payment1 == payment));}
最终输出结果为:false,和之前测试的结果不一样。SqlSession 不是有一级缓存嘛,为什么这里结果是 false。为什么?是因为 Spring 对 SqlSession 对象做了一层优化。之前说过同一个 SqlSession 在多线程环境下会出现安全问题,所以 Spring 在你每次操作 API 时都会重新创建新的 SqlSession 实例。所以 SqlSession 都是不一样的,就不用再去谈什么缓存。除非你是同一个 SqlSession 才有缓存之说。
那么怎么让一级缓存生效呢?可以开启事务,保证这些操作都在同一个事务下。改进代码如下:
public static void main(String[] args) {DataSourceTransactionManager tx = (DataSourceTransactionManager)context.getBean(TransactionManager.class);TransactionStatus transaction = tx.getTransaction(TransactionDefinition.withDefaults());PaymentMapper paymentMapper = context.getBean(PaymentMapper.class);Payment payment = paymentMapper.queryAccount(1);Payment payment1 = paymentMapper.queryAccount(1);System.out.println("payment1 == payment = " + (payment1 == payment));tx.commit(transaction);}
最终结果为:true,进入 SqlSessionTemplate 核心源码如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);SqlSession session = sessionHolder(executorType, holder);if (session != null) {return session;}LOGGER.debug(() -> "Creating a new SqlSession");session = sessionFactory.openSession(executorType);registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}
可以看到是从 TransactionSynchronizationManager 事务管理器中获取到一个 SqlSession 实例。如果没有开启事务,这个 TransactionSynchronizationManager 中获取不到,就会走下面的 openSession() 创建新的实例。
在看到 getResource() 方法,核心源码如下:
@Nullablepublic static Object getResource(Object key) {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Object value = doGetResource(actualKey);if (value != null && logger.isTraceEnabled()) {logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +Thread.currentThread().getName() + "]");}return value;}@Nullableprivate static Object doGetResource(Object actualKey) {Map<Object, Object> map = resources.get();if (map == null) {return null;}Object value = map.get(actualKey);// Transparently remove ResourceHolder that was marked as void...if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {map.remove(actualKey);// Remove entire ThreadLocal if empty...if (map.isEmpty()) {resources.remove();}value = null;}return value;}
最终看到变量 resources 源码如下:
public abstract class TransactionSynchronizationManager {private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");
}
发现竟然是一个 ThreadLocal 变量,这是每个线程私有的东西,人手一份,互不影响,当你开启事务之后,这个变量就已经保存好一个 SqlSession 连接,所以每次调用 API 时获取到的都是同一个 SqlSession 对象,是同一个会话,那么一级缓存就会开始生效。如果你没有开启事务,就会通过 SqlSessionFactory 工厂调用 openSession() 方法打开 SqlSession 会话,但是此时 SqlSessionTemplate 每次都会通过 SqlSessionFactory 打开一个新的 SqlSession,这样就不存在说啥一级缓存了都,完全两个 SqlSession。
相关文章:
SqlSession 和 SqlSessionTemplate 简单使用及注意事项
1、SqlSession 简单使用 先简单说下 SqlSession 是什么?SqlSession 是对 Connection 的包装,简化对数据库操作。所以你获取到一个 SqlSession 就相当于获取到一个数据库连接,就可以对数据库进行操作。 SqlSession API 如下图示:…...
1. QSaveFile和QFile的简单使用
1. 说明 QSaveFile和QFile两个类都是用来操作文件的,区别在于QSaveFile在对文件进行写入时有一种保护机制,再写入出错时,不会对源文件中的内容进行操作。该类在执行写操作时,会先将内容写入到一个临时文件中,如果没有…...
工业4.0是如何优化垃圾处理行业的
如今,工业4.0正在影响着制造业和物流等行业,其发展潜力在未来还有望进一步扩大。一些全球领先的垃圾处理公司已经开始在水处理和废物回收等领域应用工业4.0。工业4.0的创新给这个领域带来了一些必要的改进。随着环境危机的加剧,垃圾处理行业面…...
vue 动画(transition)
一、 实现原理 在插入、更新、移除 DOM 元素时,在合适的时候给元素添加样式类名,配合 CSS 样式使用,实现动画效果。 通俗来讲,就是将要进行动画操作的 DOM 元素用 transition 标签包裹起来。在此html元素运动前,运动…...
Python 爬虫工程师面试经验分享,金三银四
🙃 作为一个 Python 爬虫工程师,我可以分享一些我在面试中的经验和建议。 首先一点是在面试中要表现自信、友好、乐于合作,同时对公司的业务和文化也要有一定的了解和兴趣,这些也是公司在招聘中看重的因素。 文章目录🕛…...
MySQL实战篇-MySQL 降配导致的实例宕机
问题描述 由于近期对服务器进行了降配,该mysql数据库会进行批量写入操作,直接导致实例宕机 查看错误日志: 2021-02-02T09:09:23.557505Z 0 [Note] InnoDB: page_cleaner: 1000ms intended loop took 16791ms. The settings might not be optimal. (fl…...
时隔多年,这次我终于把动态代理的源码翻了个地儿朝天
本文内容整理自 博学谷狂野架构师 动态代理简介 Proxy模式是常用的设计模式,其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。 用户可以更加结构图࿰…...
数据分析-深度学习 Tensorflow Day6
我们需要解决的问题:1: 什么是bp 神经网络?2:理解bp神经网络需要哪些数学知识?3:梯度下降的原理4: 激活函数5:bp的推导。1.什么是bp网络?引用百度知道回复:“我们最常用的…...
leaflet 设置多个marker,导出为一个geojson文件(066)
第066个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中使用L.marker设置多个markers, 通过数据重组,导出为geojson文件。 这里面 ayer instanceof L.Marker 是一个很重要的判断条件,可以灵活地去运用。 直接复制下面的 vue+openlayers源代码,操作2分钟即可…...
企业与第三方供应商合作时,会存在哪些安全风险?
随着现代社会的发展,企业供应链、产业供应链已日渐成熟。其中,供应商与企业的关系也由最初的纯粹买卖关系发展成了合作伙伴关系。在整个供应链体系中,供应商与其受众承担着供应链中环环相扣的责任,可以说,企业安全的薄…...
技术源自洛克希德·马丁,光场XR眼镜FYR解析
专注于医疗场景的一家XR眼镜厂商FYR(全称:FYR Medical)近期亮相,并宣布完成了260万美元A轮融资,本轮融资由NuVasive领投,资金将用于开发世界上第一个XR光场“放大镜”类产品。据青亭网了解,NuVa…...
剑指 Offer 10- II. 青蛙跳台阶问题(LeetCode 70. 爬楼梯)(动态规划打表)
题目: 链接:剑指 Offer 10- II. 青蛙跳台阶问题;LeetCode 70. 爬楼梯 难度:简单 相关博文:剑指 Offer 10- I. 斐波那契数列(动态规划打表) 一只青蛙一次可以跳上1级台阶,也可以跳上…...
webpack(高级)--文件的压缩Terser(js/css/html) Tree Shaking
webpack Terser Terser是一个javascript的解释(Parser),Mangler(绞肉机) /Compressor(压缩机)的工具集 早期我们会使用uglify-js来压缩,丑化我们的javascript代码 但是目前已经不在维护 并且不支持ES6语法 Terser是从uglify-es fork 过来的 也就是说 Terser可以帮…...
做软文发布需要注意哪些细节?
软文发布是一种有效的网络营销和推广活动,它以媒体等形式把产品信息植入到软文报道或新闻中,进行心理暗示和引导销售,进行正面宣传以及促进销售的新型网络营销方式,它不但能够有效地推行产品宣传、也能有效地提高网络曝光率&#…...
【Python】一篇文章读懂yield基本用法
这一次,田辛老师想通俗易懂地解释一下Python中的yield功能。 本文要说明以下四个问题: yield是什么什么是迭代器和生成器yield的基本用法如何使用yield from 用真正简单的方法讲解yield并不容易。 我想,就算你不懂yield语句,也…...
Docker getting started
系列文章目录 Docker 概述 Docker getting started 文章目录系列文章目录前言一、容器及镜像的概念二、容器化一个应用三、更新应用四、分享应用五、持久化数据存储volume mount 和 bind mount比较Container volumesbind mounts六、跨多容器的应用七、Docker 其它八、Docker 图…...
【Uniapp使用遇到问题合集】
Uniapp使用遇到问题合集问题一跳转页面后无法进行滑动/滚动的操作描述解决方法问题一 跳转页面后无法进行滑动/滚动的操作 描述 如题,实际操作是我在uniapp自带的组件uni-popup弹出层中加入了一个点击事件,点击后可跳转到指定的页面 但实际运行中出现了跳转过后页面过长时无…...
宝塔面板破解最新教程
宝塔,让运维简单高效。面板支持Linux与Windows系统。一键配置:LAMP/LNMP、网站、数据库、FTP、SSL,通过Web端轻松管理服务器。今天考高分网就简单说一下BT宝塔面板专业版最新破解教程。 网地址:https://www.bt.cn/ 网上的破解版一般分为两种,一种是直接…...
基于zookeeper的Hadoop集群搭建详细步骤
目录 一、一些基本概念 二、集群配置图 三、Hadoop高可用集群配置步骤 1.在第一台虚拟机解压hadoop-3.1.3.tar.gz到/opt/soft/目录 2.修改文件名、属主和属组 3.配置windows四台虚拟机的ip映射 4.修改hadoop配置文件 (1)hadoop-env.sh (2)workers (3)crore-site.xml …...
职称有哪些意义?如何提升职称?
每年我们会看到很多人都会努力地提升自己的职称,那么为什么大家都想要晋升职称?在这里余老师说说他的作用,您可以参考一下。 一、个人金钱方面的提升 工资。职称直接关联的就是涨工资了。正常情况下,职称和工资是一一对应的了,…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
