主要以结果为导向解释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. HotSpot 虚拟机的算法实现

  2. Linux指令--cd,pwd

    本文参照博客做了修改和总结,感谢作者的分享.http://www.cnblogs.com/peida/archive/2012/10/24/2736501.html cd 命令可以说是Linux中最基 ...

  3. org.apache.catalina.util.DefaultAnnotationProcessor cannot be cast to org.apache.AnnotationProcessor

    这几天来公司,公司的SVN坏掉了,然后项目还比较大,是一个分布式的,然后同事就把项目发我了.我在myeclipse里面导入项目了,把相应的jar包也建了个人的library导入了项目,现在项目不报错了 ...

  4. linkin大话设计模式--代理模式

    代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象的时候,客户端并不关心是否可以准确的得到这个对象,他只要一个能够提供该功能的对象而已,此时我们就可以返回该对象的代理.总而言之,客户端 ...

  5. 【Java入门提高篇】Day13 Java中的反射机制

    前一段时间一直忙,所以没什么时间写博客,拖了这么久,也该更新更新了.最近看到各种知识付费的推出,感觉是好事,也是坏事,好事是对知识沉淀的认可与推动,坏事是感觉很多人忙于把自己的知识变现,相对的在沉淀上 ...

  6. Installing VirtualBox

    The (VirtualBox) website has a lot of quality documentation including: End-user documentation Techni ...

  7. BZOJ 1299: [LLH邀请赛]巧克力棒 [组合游戏]

    每次一人可以从盒子里取出若干条巧克力棒,或是将一根取出的巧克力棒吃掉正整数长度. Nim游戏多了一个决策:拿出一些石堆 显然只要给对方构造异或和为0的子集就行了 暴枚子集... #include &l ...

  8. 帝国CMS备份出现数据恢复不完整的问题

    今天linux主机中毒了,把用帝国备份王备份之后,恢复了快照到刚建主机的状态: 哎,只怪当初没有勤快的去做快照啊: 重新配置好后: 开始使用帝国备份王: 数据恢复之后,打开文章,提示"附加表 ...

  9. 撸一撸Spring Cloud Ribbon的原理

    说起负载均衡一般都会想到服务端的负载均衡,常用产品包括LBS硬件或云服务.Nginx等,都是耳熟能详的产品. 而Spring Cloud提供了让服务调用端具备负载均衡能力的Ribbon,通过和Eure ...

  10. u-boot核心初始化

    异常向量表:异常:因为内部或者外部的一些事件,导致处理器停下正在处理的工作,转而去处理这些发生的事件.ARM Architecture Reference Manual p54页.7种异常的类型:Re ...