前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >项目中Spring 声明式事务使用的一些坑点分析01

项目中Spring 声明式事务使用的一些坑点分析01

作者头像
秋日芒草
发布2018-05-15 17:35:58
3.7K0
发布2018-05-15 17:35:58
举报
文章被收录于专栏:JavaWebJavaWeb

项目中Spring 声明式事务使用的一些坑点分析

        事务的中重要性我在这就不用提了,10个系统基本10个都需要用到事务;事务从早期的存储过程代码中手动提交事务和回滚事务、Spring早期的编程事务管理到现在的声明事务管理,事务处理越来越简单化,可能你一点都不同事务的原理,你也可以直接copy大神的代码(搬砖了);当自己写的业务中使用大神那里copy过来的代码,你要是不懂copy的是什么,只知道这代码就能实现事务,我才不去管了,我业务写完我就可以休息了,你最终会把自己坑掉。但自己写的代码出现问题了,就各种百度(这个时候心里迷茫呀),各种乱投医,我之前也是这样的。在这里我会将按照自己学习的角度去分析spring事务的强大和常见开发中的一些坑点。

1.     先介绍一个自己定位bug的技巧:

        就是我们在使用各种开源框架的,要直接定位到自己的bug,第一步就是看日志,看错误信息,有的错误非常明了,有的需要自己结合理论知识去分析,日志分析也是对开源框架更加深入去掌握和使用,在项目中我们一般都是用log4j来配置日志,这里配置就不用讲了,拿到错误信息后需要先定位这个错误信息属于什么模块的,比如如下错误栗子,抛出的是sql异常这里,这里就能想到离sql异常最近的就是Spring Jdbc模块了,这个时候我们就可以考虑将这个模块的日志级别调低一点(在日志配置文件这样配置:log4j.logger.org.springframework.jdbc=DEBUG),然后再去比较详细的去分析这些架构都帮我们做了什么。

代码语言:javascript
复制
2018-05-04 18:22:49,520 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader:316] - Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
2018-05-04 18:22:49,535 INFO [org.springframework.jdbc.support.SQLErrorCodesFactory:126] - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
2018-05-04 18:22:49,542 ERROR [cn.edu.his.pay.exceptions.WebExceptionHandler:50] - Exception
org.springframework.dao.TransientDataAccessResourceException: 
### Error updating database.  Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
### The error may involve cn.edu.his.pay.mapper.SecurityAdditionMapper.insert-Inline
### The error occurred while setting parameters
### SQL: insert into security_addition (id, order_no, card,        name, amt)     values (?, ?, ?,        ?, ?)
### Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
; SQL []; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
	at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:106)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
	at com.sun.proxy.$Proxy322.insert(Unknown Source)

2.     如上图,日志错误信息其实是我们在使用Spring事务经常遇到的一个坑点,就是在配置文件中已经配置了service中指定方法为只读后,还在这个方法中直接插入或修改等操作,这个时候就能看到上面的异常信息了。

spring 事务配置如下:

代码语言:javascript
复制
<!-- 配置哪些方法要加入事务控制 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<!-- 让所有的方法都加入事务管理,为了提高效率,可以把一些查询之类的方法设置为只读的事务 -->
		<tx:method name="*" propagation="REQUIRED" read-only="true" />
		<!-- 以下方法都是可能设计修改的方法,就无法设置为只读 -->
		<tx:method name="add*" propagation="REQUIRED" />
		<tx:method name="insert*" propagation="REQUIRED" />
		<tx:method name="del*" propagation="REQUIRED" />
		<tx:method name="update*" propagation="REQUIRED" />
		<tx:method name="save*" propagation="REQUIRED" />
		<tx:method name="clear*" propagation="REQUIRED" />
		<tx:method name="handle*" propagation="REQUIRED" />
	</tx:attributes>
</tx:advice>

service中的方法定义如下:

代码语言:javascript
复制
@Override
public void readOnly(boolean flag) {
	if(flag == true) {
		SecurityAddition record = new SecurityAddition();
		record.setAmt(new Double(20));
		record.setCard(System.currentTimeMillis()+"");
		record.setOrder_no("Order-"+System.currentTimeMillis());
		record.setName("张三");
		securityAdditionMapper.insert(record);
	}
}

当flag=true的时候,上面的异常就产生了。

3.     既然上面的问题都产生了,我们就带着这个问题去分析一下日志,看看能不能带来意外的收获,现在将jdbc模块的日志设置为:log4j.logger.org.springframework.jdbc=DEBUG,运行后日志信息如下:

代码语言:javascript
复制
DEBUG [org.springframework.web.servlet.DispatcherServlet:925] - Last-Modified value for [/his_pay/add] is: -1
DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager:367] - Creating new transaction with name [cn.edu.his.pay.service.impl.TransactionServiceImpl.readOnly]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager:207] - Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] for JDBC transaction
DEBUG [org.springframework.jdbc.datasource.DataSourceUtils:153] - Setting JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] read-only
DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager:224] - Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] to manual commit
DEBUG [cn.edu.his.pay.mapper.SecurityAdditionMapper.insert:159] - ==>  Preparing: insert into security_addition (id, order_no, card, name, amt) values (?, ?, ?, ?, ?) 
DEBUG [cn.edu.his.pay.mapper.SecurityAdditionMapper.insert:159] - ==> Parameters: null, Order-1525429819624(String), 1525429819624(String), 张三(String), 20.0(Double)
DEBUG [org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator:281] - Unable to translate SQLException with Error code '0', will now try the fallback translator
DEBUG [org.springframework.jdbc.support.SQLStateSQLExceptionTranslator:94] - Extracted SQL state class 'S1' from value 'S1009'
DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager:847] - Initiating transaction rollback
DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager:282] - Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306]
DEBUG [org.springframework.jdbc.datasource.DataSourceUtils:222] - Resetting read-only flag of JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306]
DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager:325] - Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] after transaction
DEBUG [org.springframework.jdbc.datasource.DataSourceUtils:327] - Returning JDBC Connection to DataSource
DEBUG [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver:134] - Resolving exception from handler [public java.lang.String cn.edu.his.pay.controller.test.TransactionController.add(java.lang.String)]: org.springframework.dao.TransientDataAccessResourceException: 
### Error updating database.  Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
### The error may involve cn.edu.his.pay.mapper.SecurityAdditionMapper.insert-Inline
### The error occurred while setting parameters
### SQL: insert into security_addition (id, order_no, card,        name, amt)     values (?, ?, ?,        ?, ?)
### Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
; SQL []; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
2018-05-04 18:30:19,630 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory:249] - Returning cached instance of singleton bean 'webExceptionHandler'
2018-05-04 18:30:19,630 DEBUG [org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver:338] - Invoking @ExceptionHandler method: public cn.edu.his.pay.common.base.ApiCommonResultVo cn.edu.his.pay.exceptions.WebExceptionHandler.processException(java.lang.Exception,javax.servlet.http.HttpServletRequest)
2018-05-04 18:30:19,630 ERROR [cn.edu.his.pay.exceptions.WebExceptionHandler:50] - Exception
org.springframework.dao.TransientDataAccessResourceException: 
### Error updating database.  Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
### The error may involve cn.edu.his.pay.mapper.SecurityAdditionMapper.insert-Inline
### The error occurred while setting parameters
### SQL: insert into security_addition (id, order_no, card,        name, amt)     values (?, ?, ?,        ?, ?)
### Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
; SQL []; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
	at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:106)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)

正常情况下是由一个将手动提交事务提交的过程,如下图:

现在我们挑出日志核心日志进行分析一下:

代码语言:javascript
复制
Last-Modified value for [/his_pay/add] is: -1
# 在执行这个readOnly方法的时候,DataSourceTransactionManager创建了一个新的事务,而且能看到这个事务走的事务# 传播行为是PROPAGATION_REQUIRED(如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务)
# 事务的隔离级别为:ISOLATION_DEFAULT(表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。而MySQL的默认事务隔离级别是:Repeatable Read,可能会造成幻读)
# TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
Creating new transaction with name [cn.edu.his.pay.service.impl.TransactionServiceImpl.readOnly]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
# 为事务获取连接
Acquired Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] for JDBC transaction
# 设置连接为只读
Setting JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] read-only
# 切换jdbc连接为手动提交模式
Switching JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] to manual commit
# 准备插入
Preparing: insert into security_addition (id, order_no, card, name, amt) values (?, ?, ?, ?, ?) 
# 参数信息
Parameters: null, Order-1525429819624(String), 1525429819624(String), 张三(String), 20.0(Double)

Unable to translate SQLException with Error code '0', will now try the fallback translator
Extracted SQL state class 'S1' from value 'S1009'
# 启动事务回滚
Initiating transaction rollback
# 在Connection上回滚JDBC事务
Rolling back JDBC transaction on Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306]
# 重置JDBC连接的只读标志
Resetting read-only flag of JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306]
# 释放连接
Releasing JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@50ea6306] after transaction
# 将连接还给数据源(这里就是连接池的作用)
Returning JDBC Connection to DataSource
Resolving exception from handler [public java.lang.String cn.edu.his.pay.controller.test.TransactionController.add(java.lang.String)]: org.springframework.dao.TransientDataAccessResourceException: 

如上图其实能发现很多东西,这些东西可以直接再仔细的琢磨一下,如:数据库连接池、Spring如何处理事务的(其实就是将手动提交事务,和编程式事物管理差不多,出错后通过调用api来回滚)、隔离级别、传播行为等。最后:日志也分析了,现在应该知道怎么处理了吧!其实这问题导致的原因也是在于我们程序员开发的时候没有养成一个好的习惯,如:命名规范等,其实这都是在爬自己的坑,爬完后就要好好总结,这样才会有收获。这篇文章大概就这些内容了,下一篇文章准备写多数据源和单数据源下简单事务和嵌套事务的分析。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 项目中Spring 声明式事务使用的一些坑点分析
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档