1.JdbcTemplate

    当不使用事务时,jdbcTemplate的模板类,通过
    Connection con = DataSourceUtils.getConnection(getDataSource());
    方法,首先去当前线程的上下文中寻找绑定的数据库连接,若没找到,则新建一个连接,即从DataSource中创建一个新的连接:
  1. ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  2. if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
  3. conHolder.requested();
  4. if (!conHolder.hasConnection()) {
  5. logger.debug("Fetching resumed JDBC Connection from DataSource");
  6. conHolder.setConnection(dataSource.getConnection());
  7. }
  8. return conHolder.getConnection();//首先从当前的线程上下文中获取
  9. }
  10. // Else we either got no holder or an empty thread-bound holder here.
  11.  
  12. logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();//若线程上下文中未找到,则新建一个连接
当执行完查询,将数据抽取封装好之后,最后将Statement和connection关闭:
  1. public Object execute(StatementCallback action) throws DataAccessException {
  2. Assert.notNull(action, "Callback object must not be null");
  3.  
  4. Connection con = DataSourceUtils.getConnection(getDataSource());
  5. Statement stmt = null;
  6. try {
  7. Connection conToUse = con;
  8. if (this.nativeJdbcExtractor != null &&
  9. this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
  10. conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
  11. }
  12. stmt = conToUse.createStatement();
  13. applyStatementSettings(stmt);
  14. Statement stmtToUse = stmt;
  15. if (this.nativeJdbcExtractor != null) {
  16. stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
  17. }
  18. Object result = action.doInStatement(stmtToUse);
  19. SQLWarning warning = stmt.getWarnings();
  20. throwExceptionOnWarningIfNotIgnoringWarnings(warning);
  21. return result;//返回语句执行的结果
  22. }
  23. catch (SQLException ex) {
  24. // Release Connection early, to avoid potential connection pool deadlock
  25. // in the case when the exception translator hasn't been initialized yet.
  26. JdbcUtils.closeStatement(stmt);
  27. stmt = null;
  28. DataSourceUtils.releaseConnection(con, getDataSource());
  29. con = null;
  30. throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
  31. }
  32. finally {
  33. JdbcUtils.closeStatement(stmt);//最后释放资源关闭连接
  34. DataSourceUtils.releaseConnection(con, getDataSource());
  35. }
  36. }
可见,在不使用事务时,每执行一个语句都是一个单独的事务,和原来不使用spring时是一样的,完毕之后spring会自动释放所有资源和关闭连接。
2.如果使用事务呢? 
首先需要定义一个
  1. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  2. <property name="dataSource" ref="datasource"/>

然后定义一个:DefaultTransactionDefinition 

  1. DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

由此创建一个TransactionStatus:

  1. private TransactionStatus beginTransaction(){
  2. DefaultTransactionDefinition def = new DefaultTransactionDefinition();
  3. def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
  4. def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  5. return getMager().getTransaction(def);
  6. }
  7. private DataSourceTransactionManager getMager(){
  8. DataSourceTransactionManager man= (DataSourceTransactionManager) con.getBean("transactionManager");
  9. return man;
  10. }

下面看创建status时做了什么?

  1. connectionholder字面意思为连接持有者,即为每个线程保存绑定连接的对象,每个线程绑定的连接保存在该对象中,然后保存在一个map里面,key为当前的DataSource,该DataSource为应用程序级的,可以是一个tns连接描述,也可以是一个jndi也可以是一个连接池对象,总之所有线程共享,通过该key值找到线程自己通过该DateSource创建的连接,该Map保存到线程自己的本地变量中,以便下次获取,下次线程获取连接时,首先去自己的本地变量中寻找map,看map释放为空,如果为空,说明没有绑定的连接,直接创建,若有,则通过jdbcTemplate中的DataSource对象作为keymap中拿出绑定的连接使用。

  1. protected void doBegin(Object transaction, TransactionDefinition definition) {
  2. DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  3.  
  4. Connection con = null;
  5.  
  6. try {
  7. if (txObject.getConnectionHolder() == null) {//如果当前的connectionholder不存在
  8. Connection newCon = this.dataSource.getConnection();//则创建一个新连接
  9. if (logger.isDebugEnabled()) {
  10. logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
  11. }
  12. txObject.setConnectionHolder(new ConnectionHolder(newCon), true);//并将该连接进行持有
  13. }
  14.  
  15. txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
  16. con = txObject.getConnectionHolder().getConnection();
  17.  
  18. Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
  19. txObject.setPreviousIsolationLevel(previousIsolationLevel);
  20.  
  21. // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
  22. // so we don't want to do it unnecessarily (for example if we've explicitly
  23. // configured the connection pool to set it already).
  24. if (con.getAutoCommit()) {
  25. txObject.setMustRestoreAutoCommit(true);
  26. if (logger.isDebugEnabled()) {
  27. logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
  28. }
  29. con.setAutoCommit(false);//为事务做准备
  30. }
  31. txObject.getConnectionHolder().setTransactionActive(true);
  32.  
  33. if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
  34. txObject.getConnectionHolder().setTimeoutInSeconds(definition.getTimeout());
  35. }
  36.  
  37. // Bind the session holder to the thread.
  38. if (txObject.isNewConnectionHolder()) {
  39. TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
  40. }//重要的一步,将该连接和当前的线程进行绑定,并将该连接的自动提交设为false,为事务使用做准备
  41. }
  42.  
  43. catch (SQLException ex) {//出现异常则关闭任何打开的连接,物理释放
  44. DataSourceUtils.releaseConnection(con, this.dataSource);
  45. throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
  46. }
  47. }

这时,status其实就是将连接和当前线程绑定,然后做一些必要的准备工作,如事务传播级别等等的设置:

  1. public static void bindResource(Object key, Object value) throws IllegalStateException {
  2. Assert.notNull(key, "Key must not be null");
  3. Assert.notNull(value, "Value must not be null");
  4. Map map = (Map) resources.get();//从ThreadLocal中获取当前连接信息的Map结构
  5. // set ThreadLocal Map if none found
  6. if (map == null) {
  7. map = new HashMap();
  8. resources.set(map);//如果未找到,则将其设置进去
  9. }
  10. if (map.containsKey(key)) {
  11. throw new IllegalStateException("Already value [" + map.get(key) + "] for key [" + key +
  12. "] bound to thread [" + Thread.currentThread().getName() + "]");
  13. }
  14. map.put(key, value);
  15. if (logger.isDebugEnabled()) {
  16. logger.debug("Bound value [" + value + "] for key [" + key + "] to thread [" +
  17. Thread.currentThread().getName() + "]");
  18. }
  19. }

如果想获得当前会话中执行的数据库连接,则使用以下方法:

  1. Connection conn = DataSourceUtils.getConnection(dataSource);
  1. 这个获得和当前事务绑定的连接,否则使用getDataSource().getConnection()会创建一个全新的连接,这样会造成出现事务从而被隔离了。

当执行了getTransaction方法并返回一个TransactionStatus之后,表示事务已经开启了,后续所有的执行语句的操作都会使用一个数据库连接,而该连接在步骤1中会自动从当前线程的上下文中获取:

  1. public static Object getResource(Object key) {
  2. Assert.notNull(key, "Key must not be null");
  3. Map map = (Map) resources.get();//从当前线程的上下文中拿到线程保持的map,为什么不直接将连接保持到resources中,而是放入一个map再放入resource中呢?原因是,类TransactionSynchronizationManager为一个事务同步的管理器,不光是为了持有连接,还有其他资源,所以,将线程自身其他的一些资源放入map,可以保寸多种对象不至于和其他线程产生冲突
  4. if (map == null) {
  5. return null;
  6. }
  7. Object value = map.get(key);//如果是想拿到连接,此时的key是一个DataSource对象,说明是想从线程的变量集合中拿到本线程绑定的数据库连接
  8. if (value != null && logger.isDebugEnabled()) {
  9. logger.debug("Retrieved value [" + value + "] for key [" + key + "] bound to thread [" +
  10. Thread.currentThread().getName() + "]");
  11. }
  12. return value;
  13. }

未来后续的所有的操作使用的连接都将是该连接,而不是建立新的连接,这和不使用事务时有根本的区别。而事务开始之后,每个语句执行完毕之后,finally语句都会关闭连接释放资源,那么此时将会如何执行呢?

  1. public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {
  2. if (con == null) {
  3. return;
  4. }
  5.  
  6. if (dataSource != null) {
  7. ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
  8. if (conHolder != null && conHolder.hasConnection() && connectionEquals(conHolder.getConnection(), con)) {//在status创建时初始化的holder对象在这个地方区分出到底是事务连接还是普通的非事务连接,如果是事务连接,那么执行Holder自己的释放方法
  9. // It's the transactional Connection: Don't close it.
  10. conHolder.released();
  11. return;
  12. }
  13. }
  14.  
  15. // Leave the Connection open only if the DataSource is our
  16. // special data source, and it wants the Connection left open.
  17. if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
  18. logger.debug("Returning JDBC Connection to DataSource");
  19. con.close();//这是不使用事务时使用的释放方法,即为物理关闭连接,执行完后,该连接即实实在在的关闭
  20. }
  21. }

那么conHolder.released();如何执行呢?

  1. public void released() {
  2. this.referenceCount--;
  3. if (this.currentConnection != null) {
  4. this.connectionHandle.releaseConnection(this.currentConnection);//这是一个空方法
  5. this.currentConnection = null;//释放引用
  6. }
  7. }
可见,此处释放连接并没有真正的关闭连接,而是将指向ThreadLocal中和当前线程绑定的连接的引用释放,因为连接和当前线程绑定,只要当前线程没用执行完,堆栈未被回收释放,则该连接会一直存在,事务中其他语句执行时使用的连接都是该连接,所以需要有一个到当前连接的引用,语句执行完后finally中释放的连接即是该引用而已。连接为被真正释放,什么时候连接真正关闭呢?
在Commit之后,在TransactionManager的doCleanupAfterCompletion(Object transaction) 方法中:
  1. protected void doCleanupAfterCompletion(Object transaction) {
  2. DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  3.  
  4. // Remove the connection holder from the thread, if exposed.
  5. if (txObject.isNewConnectionHolder()) {
  6. TransactionSynchronizationManager.unbindResource(this.dataSource);//解除连接绑定
  7. }
  8.  
  9. // Reset connection.
  10. Connection con = txObject.getConnectionHolder().getConnection();
  11. try {
  12. if (txObject.isMustRestoreAutoCommit()) {
  13. con.setAutoCommit(true);//虽然将要关闭连接,但是仍然将该连接的自动提交恢复,为什么呢?原因就是,该连接可能是从连接池中拿到的,从连接池中拿到的连接并不会真正关闭,而是返回连接池,因此需要将该连接重置初始化,否则该连接被其他线程拿到时会影响执行结果,如无法自动提交,丢失事务等。可见spring想的非常周到。而且后续会将该连接一系列的属性重置,如事务的隔离级别、只读事务等等
  14. }
  15. DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
  16. //重置连接的一些属性,以为了连接重用
  17. }
  18. catch (Throwable ex) {
  19. logger.debug("Could not reset JDBC Connection after transaction", ex);
  20. }
  21.  
  22. if (txObject.isNewConnectionHolder()) {
  23. if (logger.isDebugEnabled()) {
  24. logger.debug("Releasing JDBC Connection [" + con + "] after transaction");
  25. }
  26. DataSourceUtils.releaseConnection(con, this.dataSource);//最后会释放连接,由于连接已经和当前线程解除绑定,因此关闭连接是物理关闭(此处关闭的方法由数据库的驱动程序决定,驱动程序关闭时会判断是普通连接还是连接池连接,对应用程序是透明的)
  27. }
  28.  
  29. txObject.getConnectionHolder().clear();//清除Holder信息,将当前为事务做的准备工作和信息全部清除归位
  30. }

【原创】Spring连接、事务代码分析的更多相关文章

  1. 老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(三)

    老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(三) 前言 上一篇文章老生常谈系列之Aop--Aop的经典应用之Spring的事务实现分析(二)从三个问题导入,分析了Spring ...

  2. Spring AOP实现声明式事务代码分析

    众所周知,Spring的声明式事务是利用AOP手段实现的,所谓"深入一点,你会更快乐",本文试图给出相关代码分析. AOP联盟为增强定义了org.aopalliance.aop.A ...

  3. 备忘:spring jdbc事务代码 mybatis, nhibernate

    http://files.cnblogs.com/files/mikelij/mymavenMar1.rar

  4. spring transaction源码分析--事务架构

    1. 引言  事务特性 事务是并发控制的单元,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性.事 ...

  5. Spring事务原理分析-部分二

    Spring事务原理分析-部分二 说明:这是我在蚂蚁课堂学习了余老师Spring手写框架的课程的一些笔记,部分代码代码会用到余老师的课件代码.这不是广告,是我听了之后觉得很好. 课堂链接:Spring ...

  6. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

  7. Spring事务原理分析-部分一

    Spring事务原理分析-部分一 什么事务 事务:逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败. 事务基本特性 ⑴ 原子性(Atomicity) 原子性是指事务包含的所有操作要 ...

  8. Mybatis整合Spring实现事务管理的源码分析

    一:前言 没有完整看完,但是看到了一些关键的地方,这里做个记录,过程会有点乱,以后逐渐补充最终归档为完整流程:相信看过框架源码的都知道过程中无法完全确定是怎样的流程,毕竟不可能全部都去测试一遍 ,但是 ...

  9. Spring Cloud 请求重试机制核心代码分析

    场景 发布微服务的操作一般都是打完新代码的包,kill掉在跑的应用,替换新的包,启动. spring cloud 中使用eureka为注册中心,它是允许服务列表数据的延迟性的,就是说即使应用已经不在服 ...

随机推荐

  1. ffmpeg键盘命令响应程序详解

    一.对终端进行读写 当一个程序在命令提示符中被调用时, shell负责将标准输入和标准输出流连接到你的程序, 实现程序与用户间的交互.   1. 标准模式和非标准模式 在默认情况下, 只有用户按下回车 ...

  2. SQL*PLUS命令的使用大全

    Oracle的sql*plus是与oracle进行交互的客户端工具.在sql*plus中,可以运行sql*plus命令与sql*plus语句. 我们通常所说的DML.DDL.DCL语句都是sql*pl ...

  3. 深度理解DOM事件(实例)

    前言 通过如下两个实例来理解DOM事件 实例1--点击别处关闭浮层 onclick与addEventListener的区别 实例2--点击后颜色一层一个层出现的漂亮的彩虹圈 1  实例1--点击别处关 ...

  4. P1343 地震逃生

    题目描述 汶川地震发生时,四川**中学正在上课,一看地震发生,老师们立刻带领x名学生逃跑,整个学校可以抽象地看成一个有向图,图中有n个点,m条边.1号点为教室,n号点为安全地带,每条边都只能容纳一定量 ...

  5. Linux 搭建互信后,仍需要密码验证

    修改ssh配置文件: vi /etc/ssh/sshd_config PermitRootLogin no 注释掉

  6. Type system-Type checking

    类型系统的属性: 1.结构属性: 2.规则属性:类型系统定义了一套规则(内部数据的访问规则.函数的访问规则.类型的比较与转化规则),以供编译和运行时进行检查. In programming langu ...

  7. ZBrush中如何把模型的细节映射到低模上

    我们在ZBrush®雕刻模型的时候,发现模型布线不利于雕刻,这使我们不得不对模型进行重建细分,而重建细分之后的模型细节已经没有了,这个时候我们就需要把原来高模的细节映射到新的模型上面. 接下来我们介绍 ...

  8. https://blog.csdn.net/sxf359/article/details/71082404

    https://blog.csdn.net/sxf359/article/details/71082404

  9. 第九章 Python之面向对象

    面向对象编程 面向对象编程是一种程序设计思想,它把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数 面向过程的程序设计把计算机程序视为一系列命令的集合,即一组函数的顺序执行.为了简化程序设计 ...

  10. day07 分支,循环

    目录 if(分支) if的语法 if...else... if...elif...else if的嵌套 for循环 for-else 语句 for循环的嵌套(重要) range介绍 while循环 w ...