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

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

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

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

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

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 事务配置如下:

<!-- 配置哪些方法要加入事务控制 -->
<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中的方法定义如下:

@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,运行后日志信息如下:

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)

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

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

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来回滚)、隔离级别、传播行为等。最后:日志也分析了,现在应该知道怎么处理了吧!其实这问题导致的原因也是在于我们程序员开发的时候没有养成一个好的习惯,如:命名规范等,其实这都是在爬自己的坑,爬完后就要好好总结,这样才会有收获。这篇文章大概就这些内容了,下一篇文章准备写多数据源和单数据源下简单事务和嵌套事务的分析。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ryan Miao

SpringCloud学习5-如何创建一个服务提供者provider

1353
来自专栏乐沙弥的世界

重新配置与卸载 11gR2 Grid Infrastructure

      Oracle 11g R2 Grid Infrastructure 的安装与配置较之前的版本提供了更多的灵活性。在Grid Infrastructu...

1021
来自专栏用户2442861的专栏

ubuntu16安装nginx

https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-...

1752
来自专栏Kubernetes

Kubernetes Nginx Ingress Controller源码分析之创建篇

main controllers/nginx/pkg/cmd/controller/main.go:29 func main() { // start a ...

8577
来自专栏java学习

使用intellij idea搭建MAVEN+SSM(Spring+SpringMVC+MyBatis)框架

Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-On...

4555
来自专栏开发与安全

linux系统编程之管道(二):管道读写规则和Pipe Capacity、PIPE_BUF

一、当没有数据可读时 O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。 O_NONBLOCK enable:r...

3359
来自专栏JAVA技术站

Spring整合Rabbitmq

没有找到一篇完整的文章介绍Spring如何整合Rabbitmq应用,琢磨一天搞出的一个入门的demo与伙伴们分享.

682
来自专栏LanceToBigData

JavaWeb(三)JSP概述

一、JSP概述 1.1、JSP简介   一种动态网页开发技术。它使用JSP标签在HTML网页中插入Java代码。标签通常以<%开头以%>结束。JSP是一种Jav...

3656
来自专栏菩提树下的杨过

spring cloud 学习(1) - 基本的SOA示例

有过dubbo/dubbox使用经验的朋友,看到下面这张图,一定很熟悉,就是SOA架构的最基本套路。 ? 与dubbo对比,上图的3大要素中,spring cl...

3228
来自专栏Kubernetes

Kubernetes Nginx Ingress Controller源码分析

main controllers/nginx/pkg/cmd/controller/main.go:29 func main() { // start a ...

55510

扫码关注云+社区

领取腾讯云代金券