主要以结果为导向解释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事务的原理,以及在事务内开启线程,连接池耗尽问题.的更多相关文章

  1. spring 5.x 系列第6篇 —— 整合 mybatis + druid 连接池 (代码配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...

  2. spring 5.x 系列第5篇 —— 整合 mybatis + druid 连接池 (xml配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 项目目录结构 1.创建maven工程,除了Spring基本依赖外,还需要导 ...

  3. Spring Boot (三): ORM 框架 JPA 与连接池 Hikari

    前面两篇文章我们介绍了如何快速创建一个 Spring Boot 工程<Spring Boot(一):快速开始>和在 Spring Boot 中如何使用模版引擎 Thymeleaf 渲染一个 ...

  4. Mysql 事务及其原理

    Mysql 事务及其原理 什么是事务 什么是事务?事务是作为单个逻辑工作单元执行的一系列操作,通俗易懂的说就是一组原子性的 SQL 查询.Mysql 中事务的支持在存储引擎层,MyISAM 存储引擎不 ...

  5. 个人MySQL的事务特性原理学习笔记总结

    目录 个人MySQL的事务特性原理笔记总结 一.基础概念 2. 事务控制语句 3. 事务特性 二.原子性 1. 原子性定义 2. 实现 三.持久性 1. 定义 2. 实现 3. redo log存在的 ...

  6. 深入理解Spring Boot数据源与连接池原理

    ​ Create by yster@foxmail.com 2018-8-2 一:开始 在使用Spring Boot数据源之前,我们一般会导入相关依赖.其中数据源核心依赖就是spring‐boot‐s ...

  7. 连接SQLServer时,因启用连接池导致孤立事务的原因分析和解决办法

    本文出处:http://www.cnblogs.com/wy123/p/6110349.html 之前遇到过这么一种情况: 连接数据库的部分Session会出现不定时的阻塞,这种阻塞时长时短,有时候持 ...

  8. C3P0连接池在hibernate和spring中的配置

    首先为什么要使用连接池及为什么要选择C3P0连接池,这里就不多说了,目前C3P0连接池还是比较方便.比较稳定的连接池,能与spring.hibernate等开源框架进行整合. 一.hibernate中 ...

  9. Spring中常用的连接池配置

    首先,我们准备Jdbc属性文件 jdbc.properties,用于保存连接数据库的信息,利于我们在配置文件中的使用 jdbc.driver=com.mysql.jdbc.Driver jdbc.ur ...

随机推荐

  1. eclipse-java开发实用快捷键

    Expand All:ctrl+小键盘* Collapse All:ctrl+shift+小键盘/

  2. linux 安装icu库

     先下载源码包并解压 然后安装 cd /icu/source ./configure --prefix=/usr/local/icu gmake make install  

  3. 手机文件夹的emulated什么意思

    词典翻译是仿真,就是自带的存储卡, 手机的储存方式有两种,一种是手机内存 ,一种是SD卡内存.

  4. cobbler自动化安装系统

    笔者Q:972581034 交流群:605799367.有任何疑问可与笔者或加群交流 在很久很久以前,使用kickstart实现自动化安装的时候,我一直认为装系统是多么高大上的活,直到cobbler的 ...

  5. 树莓派小车By 树莓派爱好者ITJoker(通过python socket通信实现树莓派视频小车)(一)

    本文由树莓派爱好者ITJoker 编辑,转载请注明出处.本人也有新浪博客同样是树莓派爱好者ITJoker 所需材料:树莓派2B或者2B以上,L2985n驱动板,若干排线,电池及电池盒,usb无线网卡( ...

  6. mkdir与mkdirs的区别

    mkdir与mkdirs的区别 项目中需要在代码中读取或创建文件保存路径,用到了mkdir,查看还有个mkdirs方法,这里记录一下两者的区别. 1.关于两者的说明如下: boolean mkdir( ...

  7. LINUX文件定位

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  8. Django中url的生成过程详解

    在前面我们知道,Django启动之前会执行admin.py中的autodiscover()方法. def autodiscover(): autodiscover_modules('admin', r ...

  9. phpstorm中使用xdebug配置cli模式的调试

    这里略去xdebug的安装,安装很简单可以下载源码包,动态编译进去! 环境: Dev 服务器(IP:192.168.2.100),安装phpstorm,用来做开发任务! Server服务器(IP:19 ...

  10. Spring基础篇——bean的自动化装配

    上篇博文讲Spring的IOC容器时说道,虽然容器功能强大,但容器本身只是个空壳,需要我们主动放入装配对象,并告诉它对象之间的协作关系,然后容器才能按照我们的指示发挥它的魔力,完成装配bean的使命. ...