主要以结果为导向解释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. libev学习笔记

    转 libev的使用--结合Socket编程 作者:cxy450019566 之前自己学过一些libev编程的基础,这次写压测刚好用上了,才算真正动手写了些东西,在这里做一些总结.写这篇文章是为了用浅 ...

  2. JSP -java service pages

    jsp的运行机制: 1客户端(请求jsp页面)------->JSP页面(转换为java源文件)------->Selvet文件(.java) (编译成相应的字节码文件也就是后缀名是.cl ...

  3. java中自定义异常类

    hello,大家好,今天跟大家分享一下java中如何自定义异常,以后如果有新的心得,再添加,欢迎前辈指导... 首先,上Api,看一下异常和错误的父类: 然后,现在假设我有个循环(i=0;i<1 ...

  4. python3操作socketserver

    socketserver是标准库中的一个高级模块,用于网络客户端与服务器的实现.模块中,已经实现了一些可供使用的类. 在python3中,导入socketserver模块使用的命令: import s ...

  5. spring中ref属性与<ref/>标签

    在bean的内部引用另一个bean对象: 使用ref标签对其进行引用: <ref bean="viewResolver2"/> <bean id="vi ...

  6. apache配置详解

    可参考:Apache 的 httpd.conf 详解 ServerTokens OS 此指令控制了Server回送给客户端的回应头域是否包含关于服务器OS类型和编译进的模块描述信息.服务器会发送:Se ...

  7. POJ1509 Glass Beads [后缀自动机]

    题意: 给一个字符串S,每次可以将它的第一个字符移到最后面,求这样能得到的字典序最小的字符串.输出开始下标 练习SAM第一题! SS构造SAM,然后从开始尽量走最小走n步就可以啦 什么?开始位置?!R ...

  8. IntelliJ IDEA使用心得之基础篇

    今天和大家分享一个非常好用的Java开发工具-IntelliJ IDEA. 下载地址:https://www.jetbrains.com/idea/ 目录: 1)IntelliJ IDEA使用心得之基 ...

  9. 一步一步从原理跟我学邮件收取及发送 12.telnet命令行收一封信pop3

    本系列上一篇文章中我们就说到了,这一次我们要说 pop3 收信了.虽然我觉得应该先说完 mime 格式,不过估计大家已经不耐烦了 -- 怎么老在说发送啊?我们要看收取!    好吧,来啦,来啦!收取邮 ...

  10. ssh: Could not resolve hostname git.*****-inc.com : Temporary failure in name resolution fatal: The remote end hung up unexpectedly

    问题出现的情景:使用git pull拉取开发的代码到测试服务器,报错: ssh: Could not resolve hostname git.****-inc.com : Temporary fai ...