关于Spring事务的原理,以及在事务内开启线程,连接池耗尽问题.
主要以结果为导向解释Spring 事务原理,连接池的消耗,以及事务内开启事务线程要注意的问题.
Spring 事务原理这里不多说,网上一搜一大堆,也就是基于AOP配合ThreadLocal实现.
这里强调一下Spring Aop 以及Spring 注解式注入在非Spring容器管理的类中是无效的.
因为Spring Aop是在运行时实现字节码增强,字节码增强有多种实现方法,请自行了解,原生AspectJ是编译时织入,但是需要特定的编译器.语法并没有Spring Aop好理解.
先看下Spring的 事务传播行为类型
事务传播行为类型 |
说明 |
PROPAGATION_REQUIRED |
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。 |
PROPAGATION_SUPPORTS |
支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY |
使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW |
新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED |
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER |
以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED |
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与 PROPAGATION_REQUIRED类似的操作。 |
打开日记debug模式,留意控制台输出
以下测试为了可读性以及更容易理解全是基于Spring注解式事务,而没有配置声明式事务.
测试1:
可以看见只创建了一个SqlSession以及一个事务,在方法内所有操作都使用同一个连接,同一个事务
@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
@Transactional(propagation = Propagation.REQUIRED)
public void testThreadTx(){
//此方法没有事务(当前方法是 Propagation.REQUIRED)
Quotation quotation = quotationService.findEntityById(new String("1"));
//此方法没有事务(当前方法是 Propagation.REQUIRED)
quotationService.updateEntity(quotation);
}
//查看控制台输出(红字关键部分,第三个查询是更新方法内部需要先查询一次再更新,可以无视)
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
ansaction(line/:54) -JDBC Connection [1068277098(com.mysql.jdbc.JDBC4Connection@5d92bace)] will be managed by Spring
otationMapper.findEntityById(line/:54) -==> Preparing: SELECT * FROM table WHERE id = 1
otationMapper.findEntityById(line/:54) -==> Parameters:
otationMapper.findEntityById(line/:54) -<== Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4] from current transaction
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4] from current transaction
otationMapper.updateEntity(line/:54) -==> Preparing: update ….. where id = 1
otationMapper.updateEntity(line/:54) -==> Parameters:
otationMapper.updateEntity(line/:54) -<== Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
.impl.UserOperationLogServiceImpl(line/:41) -请求所用时间:207
.impl.UserOperationLogServiceImpl(line/:42) -请求结束*******************************************************************************
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@230376b4]
测试2:不使用事务
可以看出在非事务操作数据库,会使用多个连接,非常不环保,这里给稍微多线程插入埋下一个陷阱
@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
// @Transactional(propagation = Propagation.REQUIRED)
public void testThreadTx(){
Quotation quotation = quotationService.findEntityById(new String("1"));
quotationService.updateEntity(quotation);
}
//查看控制台输出(红字关键部分,第三个查询是更新方法内部需要先查询一次再更新,可以无视)
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f7b94f] was not registered for synchronization because synchronization
ansaction(line/:54) -JDBC Connection [352410768(com.mysql.jdbc.JDBC4Connection@c63fcb6)] will not be managed by Spring
otationMapper.findEntityById(line/:54) -==> Preparing: SELECT * FROM table WHERE id = 1
otationMapper.findEntityById(line/:54) -==> Parameters:
otationMapper.findEntityById(line/:54) -<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7f7b94f]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a41785a] was not registered for synchronization because synchronization
ansaction(line/:54) -JDBC Connection [1615108970(com.mysql.jdbc.JDBC4Connection@38377d86)] will not be managed by Spring
otationMapper.findEntityById(line/:54) -==> Preparing: SELECT * FROM table WHERE id = 1
otationMapper.findEntityById(line/:54) -==> Parameters:
otationMapper.findEntityById(line/:54) -<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a41785a]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@181e5a22] was not registered for synchronization because synchronization
ansaction(line/:54) -JDBC Connection [2096339748(com.mysql.jdbc.JDBC4Connection@5d4e9892)] will not be managed by Spring
otationMapper.updateEntity(line/:54) -==> Preparing: update …. where id = 1
otationMapper.updateEntity(line/:54) -==> Parameters:
otationMapper.updateEntity(line/:54) -<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@181e5a22]
.impl.UserOperationLogServiceImpl(line/:41) -请求所用时间:614
.impl.UserOperationLogServiceImpl(line/:42) -请求结束*******************************************************************************
测试3:
@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
@Transactional(propagation = Propagation.REQUIRED)
public void testThreadTx(){
final ExecutorService executorService = Executors.newFixedThreadPool(3);
Quotation quotation = quotationService.findEntityById(new String("1"));
quotationService.updateEntity(quotation);
List<Future<Integer>> futures = new ArrayList<Future<Integer>>(3);
for(int i=0;i<3;i++){
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Quotation quotation = quotationService.findEntityById(new String("1"));
quotationService.updateEntity(quotation);
return null;
}
};
futures.add(executorService.submit(task));
}
executorService.shutdown();
}
//查看控制台输出(红字关键部分,第三个查询是更新方法内部需要先查询一次再更新,可以无视)
为了节篇幅,这里不贴出控制台数据
大概就是输出了10个Creating a new SqlSession(大概有些同学使用了多线程,把线程池耗完了也没弄明白原因)
外层方法启动一个,内部3个线程,每个线程3个.一共是使用了10个连接.
为什么?这涉及到ThreadLocal以及线程私有栈的概念.如果Spring 事务使用InhertableThreadLocal就可以把连接传到子线程,但是为什么Spring不那么干呢?因为这样毫无意义,如果把同一个连接传到子线程,那就是SQL操作会串行执行,那何必还多线程呢?
有关于ThreadLocal,InhertableThreadLocal配合线程池的一些陷阱
请看我另一篇文章:
ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑
测试4:
既然使用同一个事务,不能实现并发操作,那么只能折中,在每一个线程开启一个事务,减少创建更多的连接,执行完毕以后可以返回操作成功失败结果,反馈给用户
@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
// @Transactional(propagation = Propagation.REQUIRED)
public void testThreadTx(){
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Future<Integer>> futures = new ArrayList<Future<Integer>>(3);
for(int i=0;i<3;i++){
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
quotationService.doSomeThing();
return null;
}
};
futures.add(executorService.submit(task));
}
executorService.shutdown();
} //封装一下
@Override
@Transactional(propagation =Propagation.REQUIRED)
public void doSomeThing(){
Quotation quotation = this.findEntityById(new String("1"));
this.updateEntity(quotation);
}
//查看控制台输出,只会创建3个连接,为节省篇幅,这里不贴出控制台所有数据
Creating a new SqlSession
Creating a new SqlSession
Creating a new SqlSession
最后小技巧PROPAGATION_NOT_SUPPORTED(仅仅为了让Spring能获取ThreadLocal的connection),如果不使用事务,但是同一个方法多个对数据库操作,那么使用这个传播行为可以减少消耗数据库连接
@RequestMapping(value="/testThreadTx",method = RequestMethod.GET)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testThreadTx(){
Quotation quotation = quotationService.findEntityById(new String("1"));
quotation.setStatus(ClassDataManager.STATE_N);
quotationService.updateEntity(quotation);
}
//这样只会创建一个SqlSession
关于Spring事务的原理,以及在事务内开启线程,连接池耗尽问题.的更多相关文章
- spring 5.x 系列第6篇 —— 整合 mybatis + druid 连接池 (代码配置方式)
源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...
- spring 5.x 系列第5篇 —— 整合 mybatis + druid 连接池 (xml配置方式)
源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...
- Spring Boot (三): ORM 框架 JPA 与连接池 Hikari
前面两篇文章我们介绍了如何快速创建一个 Spring Boot 工程<Spring Boot(一):快速开始>和在 Spring Boot 中如何使用模版引擎 Thymeleaf 渲染一个 ...
- Mysql 事务及其原理
Mysql 事务及其原理 什么是事务 什么是事务?事务是作为单个逻辑工作单元执行的一系列操作,通俗易懂的说就是一组原子性的 SQL 查询.Mysql 中事务的支持在存储引擎层,MyISAM 存储引擎不 ...
- 个人MySQL的事务特性原理学习笔记总结
目录 个人MySQL的事务特性原理笔记总结 一.基础概念 2. 事务控制语句 3. 事务特性 二.原子性 1. 原子性定义 2. 实现 三.持久性 1. 定义 2. 实现 3. redo log存在的 ...
- 深入理解Spring Boot数据源与连接池原理
Create by yster@foxmail.com 2018-8-2 一:开始 在使用Spring Boot数据源之前,我们一般会导入相关依赖.其中数据源核心依赖就是spring‐boot‐s ...
- 连接SQLServer时,因启用连接池导致孤立事务的原因分析和解决办法
本文出处:http://www.cnblogs.com/wy123/p/6110349.html 之前遇到过这么一种情况: 连接数据库的部分Session会出现不定时的阻塞,这种阻塞时长时短,有时候持 ...
- C3P0连接池在hibernate和spring中的配置
首先为什么要使用连接池及为什么要选择C3P0连接池,这里就不多说了,目前C3P0连接池还是比较方便.比较稳定的连接池,能与spring.hibernate等开源框架进行整合. 一.hibernate中 ...
- Spring中常用的连接池配置
首先,我们准备Jdbc属性文件 jdbc.properties,用于保存连接数据库的信息,利于我们在配置文件中的使用 jdbc.driver=com.mysql.jdbc.Driver jdbc.ur ...
随机推荐
- 怎么查看mysql的安装目录
如果忘记了MySQL的安装目录,怎么快速找到呢?方法或许很多,作者觉得这种最方便了 环境:windows+mysql+navicat 方法:进入mysql命令行输入:show variables li ...
- android activity传递实体类对象
通过实现Parcelable接口序列化对象的步骤: 1.实现Parcelable接口.2.并且实现Parcelable接口的public void writeToParcel(Parcel dest, ...
- Asp.net core 2.0.1 Razor 的使用学习笔记(四)
ASP.net core 2.0.1 中 asp.net identity 2.0.1 的基本使用(三)—用户注册 一.修改用户注册 1.打开Pages文件夹>Account>Regist ...
- VisionPro笔记(1):动态创建控件
VisionPro学习笔记(1):动态创建控件 有的时候可能需要在程序中动态创建控件,VisionPro实例中提供了一例动态创建Blob控件的方法.当然,动态创建过多的控件会极大的消耗系统的资源,建 ...
- [PHP]接口请求校验的原理
具体的校验步骤可以自定义,下面是比较直观的一种形式: 1. 客户端:请求参数带上时间,进行首字母排序,连接私钥后,取得加密结果: 客户端请求时带上这个加密结果作为sign参数. 2. 服务端:对sig ...
- JAVA泛型使用方法总结
1. 基本概念: (1)什么是泛型? 泛型,即"参数化类型".即将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用或 ...
- Go笔记-垃圾回收集和SetFinalizer
[垃圾回收] 1- Go的开发者也不用写代码来释放程序中不再使用的变量和结构占用内存,Go中有独立的进程,垃圾回收器(GC),处理这些事情.它会搜索不再使用的变量然后释放它们. 2- ...
- Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器
本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...
- oneNote总结
22.添加附加文件删除后,文件大小没有发生改变的(优化文件和清空回收站)
- 软件开发:网站&视频&书籍推荐(不断更新)
利用书籍进行系统学习,凭借博客/新闻等资料开阔眼界,辅之以代码及项目实战,并勤加以总结,方可进步. 常用网站: Leetcode刷题:https://leetcode.com/ ,练习数据结构和算法必 ...