UnexpectedRollbackException简单思考
family-decorating-their-christmas-tree-3303614.jpg
简介:
事务一直以来都是一个老生常谈的话题,要想真正的把它搞懂,还是要看源码,然后再实践。后续我会继续研究spring事务源码这块,有心得的话,会记录下来和大家一起分享、学习讨论。我只是大致跟踪了一下spring事务从头到尾执行的流程,涉及到的代码具体细节,还没有细细研究,这篇文章仅仅以demo的形式验证了一些事务的常见疑问,和UnexpectedRollbackException这个异常抛出来的场景。
类图:
事务.jpg
场景1:
代码:
@Transactional
public void test01() {
jdbcTemplate.execute("insert into apple (name) values ('apple001')");
test03();
System.out.println("能走到这里么?");
}
public void test03() {
jdbcTemplate.execute("insert into apple (name) values (null)");
}
日志:
--- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : GET "/apple", parameters={}
--- [nio-8082-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.hao.datasourcelearn.controller.DatasourceControllerTest#test01()
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.hao.datasourcelearn.service.DatasourceJdbcTest.test01]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
--- [nio-8082-exec-1] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@963096416 wrapping com.mysql.cj.jdbc.ConnectionImpl@6c5c1e05] for JDBC transaction
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@963096416 wrapping com.mysql.cj.jdbc.ConnectionImpl@6c5c1e05] to manual commit
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values ('apple001')]
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values (null)]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [HikariProxyConnection@963096416 wrapping com.mysql.cj.jdbc.ConnectionImpl@6c5c1e05]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [HikariProxyConnection@963096416 wrapping com.mysql.cj.jdbc.ConnectionImpl@6c5c1e05] after transaction
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
--- [nio-8082-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null] with root cause
结果:
回滚
java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.18.jar:8.0.18]
说明:
在同一个类里面的方法调用,test03方法的代码直接就运行在test01的事务里面,等价于把test03的代码直接放到test01里面。
场景2:
代码:
@Transactional
public void test01() {
jdbcTemplate.execute("insert into apple (name) values ('apple001')");
test03();
System.out.println("能走到这里么?");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test03() {
jdbcTemplate.execute("insert into apple (name) values (null)");
}
日志:
--- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : GET "/apple", parameters={}
--- [nio-8082-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.hao.datasourcelearn.controller.DatasourceControllerTest#test01()
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.hao.datasourcelearn.service.DatasourceJdbcTest.test01]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
--- [nio-8082-exec-1] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@491071113 wrapping com.mysql.cj.jdbc.ConnectionImpl@73cdc2a4] for JDBC transaction
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@491071113 wrapping com.mysql.cj.jdbc.ConnectionImpl@73cdc2a4] to manual commit
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values ('apple001')]
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values (null)]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [HikariProxyConnection@491071113 wrapping com.mysql.cj.jdbc.ConnectionImpl@73cdc2a4]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [HikariProxyConnection@491071113 wrapping com.mysql.cj.jdbc.ConnectionImpl@73cdc2a4] after transaction
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
--- [nio-8082-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null] with root cause
结果:
回滚
java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.18.jar:8.0.18]
说明:
在同一个service里面的方法之间的调用,是不牵涉到什么嵌套事务的,被调用方法上面的事务注解也不会生效的,因为spring的事务的实现机制是动态代理(CGlib),因为动态代理一般都是代理的类,只有通过service.test的形式调用,才有机会用到代理。
场景3:
代码:
@Service
public class DatasourceJdbcTest {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private Test2 test2;
@Transactional
public void test01() {
jdbcTemplate.execute("insert into apple (name) values ('apple001')");
test2.test02();
System.out.println("能走到这里么?");
}
}
@Service
public class Test2 {
@Resource
private JdbcTemplate jdbcTemplate;
@Transactional
public void test02() {
jdbcTemplate.execute("insert into apple (name) values (null)");
}
}
日志:
--- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : GET "/apple", parameters={}
--- [nio-8082-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.hao.datasourcelearn.controller.DatasourceControllerTest#test01()
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.hao.datasourcelearn.service.DatasourceJdbcTest.test01]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
--- [nio-8082-exec-1] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@1973985077 wrapping com.mysql.cj.jdbc.ConnectionImpl@1ce42990] for JDBC transaction
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@1973985077 wrapping com.mysql.cj.jdbc.ConnectionImpl@1ce42990] to manual commit
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values ('apple001')]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Participating in existing transaction
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values (null)]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Participating transaction failed - marking existing transaction as rollback-only
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Setting JDBC transaction [HikariProxyConnection@1973985077 wrapping com.mysql.cj.jdbc.ConnectionImpl@1ce42990] rollback-only
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [HikariProxyConnection@1973985077 wrapping com.mysql.cj.jdbc.ConnectionImpl@1ce42990]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [HikariProxyConnection@1973985077 wrapping com.mysql.cj.jdbc.ConnectionImpl@1ce42990] after transaction
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
--- [nio-8082-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null] with root cause
结果:
回滚
java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.18.jar:8.0.18]
说明:
这个例子涉及到不同service的方法调用,就会涉及到嵌套事务了,由于两个方法上面的事务注解都是@Transactional,所以test02方法会运行在test01的事务里面,从日志里面也可以看的出来,Participating in existing transaction这句话就印证了。这里抛出的异常依然是SQLIntegrityConstraintViolationException,还没有抛出UnexpectedRollbackException这个异常,System.out.println("能走到这里么?"),这句话也木有打印证明test01没有执行到这里,就被test02抛出的异常打断了。
场景4:
代码:
@Service
public class DatasourceJdbcTest {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private Test2 test2;
@Transactional
public void test01() {
jdbcTemplate.execute("insert into apple (name) values ('apple001')");
test2.test02();
System.out.println("能走到这里么?");
}
}
@Service
public class Test2 {
@Resource
private JdbcTemplate jdbcTemplate;
@Transactional
public void test02() {
try {
jdbcTemplate.execute("insert into apple (name) values (null)");
} catch (DataAccessException e) {
System.out.println("插入数据库出错!");;
}
}
}
日志:
2019-12-25 19:45:22.653 INFO 32704 --- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2019-12-25 19:45:22.653 INFO 32704 --- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-12-25 19:45:22.653 DEBUG 32704 --- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
2019-12-25 19:45:22.657 DEBUG 32704 --- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2019-12-25 19:45:22.657 INFO 32704 --- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 4 ms
2019-12-25 19:45:22.661 DEBUG 32704 --- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : GET "/apple", parameters={}
2019-12-25 19:45:22.663 DEBUG 32704 --- [nio-8082-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.hao.datasourcelearn.controller.DatasourceControllerTest#test01()
2019-12-25 19:45:22.671 DEBUG 32704 --- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.hao.datasourcelearn.service.DatasourceJdbcTest.test01]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2019-12-25 19:45:22.671 INFO 32704 --- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-12-25 19:45:22.672 WARN 32704 --- [nio-8082-exec-1] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
2019-12-25 19:45:22.746 INFO 32704 --- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-12-25 19:45:22.747 DEBUG 32704 --- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@207098194 wrapping com.mysql.cj.jdbc.ConnectionImpl@7350e8d1] for JDBC transaction
2019-12-25 19:45:22.748 DEBUG 32704 --- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@207098194 wrapping com.mysql.cj.jdbc.ConnectionImpl@7350e8d1] to manual commit
2019-12-25 19:45:22.752 DEBUG 32704 --- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values ('apple001')]
2019-12-25 19:45:22.758 DEBUG 32704 --- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Participating in existing transaction
2019-12-25 19:45:22.760 DEBUG 32704 --- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values (null)]
插入数据库出错!
能走到这里么?
2019-12-25 19:45:22.825 DEBUG 32704 --- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
2019-12-25 19:45:22.825 DEBUG 32704 --- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [HikariProxyConnection@207098194 wrapping com.mysql.cj.jdbc.ConnectionImpl@7350e8d1]
2019-12-25 19:45:22.830 DEBUG 32704 --- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [HikariProxyConnection@207098194 wrapping com.mysql.cj.jdbc.ConnectionImpl@7350e8d1] after transaction
结果:
commit
说明:
test01和test02的注解都是@Transactional,从日志上看,依然是同一个事务,如果test02捕获自己的异常,自己消化掉,那么整个事务就不会受到异常的影响,会commit。
场景5:
代码:
@Service
public class DatasourceJdbcTest {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private Test2 test2;
@Transactional
public void test01() {
jdbcTemplate.execute("insert into apple (name) values ('apple001')");
try {
test2.test02();
} catch (Exception e) {
System.out.println("捕获到test02的异常");;
}
System.out.println("能走到这里么?");
}
}
@Service
public class Test2 {
@Resource
private JdbcTemplate jdbcTemplate;
@Transactional
public void test02() {
jdbcTemplate.execute("insert into apple (name) values (null)");
}
}
日志:
--- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : GET "/apple", parameters={}
--- [nio-8082-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.hao.datasourcelearn.controller.DatasourceControllerTest#test01()
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.hao.datasourcelearn.service.DatasourceJdbcTest.test01]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
--- [nio-8082-exec-1] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@792507397 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] for JDBC transaction
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@792507397 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] to manual commit
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values ('apple001')]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Participating in existing transaction
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values (null)]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Participating transaction failed - marking existing transaction as rollback-only
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Setting JDBC transaction [HikariProxyConnection@792507397 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] rollback-only
捕获到test02的异常
能走到这里么?
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Global transaction is marked as rollback-only but transactional code requested commit
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [HikariProxyConnection@792507397 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [HikariProxyConnection@792507397 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] after transaction
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
--- [nio-8082-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only] with root cause
结果:
回滚
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:871) ~[spring-tx-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:708) ~[spring-tx-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:631) ~[spring-tx-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) ~[spring-tx-5.2.2.RELEASE.jar:5.2.2.RELEASE]
说明:
UnexpectedRollbackException这个异常出现了,当test02不捕获test01异常的时候,不会抛出这个异常,是因为程序直接就被异常打断了,不会执行commit方法,而这个异常的抛出就是在commit方法里面,但是当test02捕获test01异常的时候,神奇的事情出现了,这个异常出现了,为啥呢?下面是个人看代码的理解:从日志来看Participating in existing transaction依然是只有一个事务,但是可以逻辑上分为外层和内层事务。
从代码上看,其实这个异常是由unexpectedRollback这个属性值来控制的,为true就会抛出目标异常,而什么情况下为true呢,当test02捕获test01异常的时候,当内外层事务都执行完的时候,程序会执行commit操作,这commit操作里面会以参数的形式直接传个true:processRollback(defStatus, true);:关键就是这个true参数起到了关键作用,这篇文章不分析具体的细节,只是说明一下大概,具体的事务代码详细分析,后续会陆续写一下的,感觉挺多的,还是一点一点的拆分着写比较好,一次写一个主题或者一段代码。
@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
processCommit(defStatus);
}
其实从技术上来说如果想避免抛出这个异常UnexpectedRollbackException,有两种方法,首先这个场景一般都是在嵌套业务里面出现,那么我们可以在test02方法上面开启一个新事务,或者把test01的异常在test02方法上继续上抛,让框架处理。
场景6:
代码:
@Service
public class DatasourceJdbcTest {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private Test2 test2;
@Transactional
public void test01() {
jdbcTemplate.execute("insert into apple (name) values ('apple001')");
try {
test2.test02();
} catch (Exception e) {
throw e;
}
System.out.println("能走到这里么?");
}
}
@Service
public class Test2 {
@Resource
private JdbcTemplate jdbcTemplate;
@Transactional
public void test02() {
jdbcTemplate.execute("insert into apple (name) values (null)");
}
}
日志:
--- [nio-8082-exec-2] o.s.web.servlet.DispatcherServlet : GET "/apple", parameters={}
--- [nio-8082-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.hao.datasourcelearn.controller.DatasourceControllerTest#test01()
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.hao.datasourcelearn.service.DatasourceJdbcTest.test01]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@1832385864 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] for JDBC transaction
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@1832385864 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] to manual commit
--- [nio-8082-exec-2] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values ('apple001')]
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Participating in existing transaction
--- [nio-8082-exec-2] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values (null)]
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Participating transaction failed - marking existing transaction as rollback-only
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Setting JDBC transaction [HikariProxyConnection@1832385864 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] rollback-only
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [HikariProxyConnection@1832385864 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8]
--- [nio-8082-exec-2] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [HikariProxyConnection@1832385864 wrapping com.mysql.cj.jdbc.ConnectionImpl@301132b8] after transaction
--- [nio-8082-exec-2] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
--- [nio-8082-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: StatementCallback; SQL [insert into apple (name) values (null)]; Column 'name' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null] with root cause
结果:
回滚
java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.StatementImpl.executeInternal(StatementImpl.java:764) ~[mysql-connector-java-8.0.18.jar:8.0.18]
说明:
test02把这个异常抛出去,让框架捕获处理,test02后面的代码也不用执行了,也就不会执行后续的commit,不会抛出UnexpectedRollbackException。
场景7:
代码:
@Service
public class DatasourceJdbcTest {
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private Test2 test2;
@Transactional
public void test01() {
jdbcTemplate.execute("insert into apple (name) values ('apple001')");
test2.test02();
System.out.println("能走到这里么?");
}
}
@Service
public class Test2 {
@Resource
private JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test02() {
jdbcTemplate.execute("insert into apple (name) values (null)");
}
}
日志:
--- [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 5 ms
--- [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : GET "/apple", parameters={}
--- [nio-8082-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.hao.datasourcelearn.controller.DatasourceControllerTest#test01()
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.hao.datasourcelearn.service.DatasourceJdbcTest.test01]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
--- [nio-8082-exec-1] com.zaxxer.hikari.util.DriverDataSource : Registered driver with driverClassName=com.mysql.jdbc.Driver was not found, trying direct instantiation.
--- [nio-8082-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [HikariProxyConnection@1410196551 wrapping com.mysql.cj.jdbc.ConnectionImpl@27bf96e7] for JDBC transaction
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [HikariProxyConnection@1410196551 wrapping com.mysql.cj.jdbc.ConnectionImpl@27bf96e7] to manual commit
--- [nio-8082-exec-1] o.s.jdbc.core.JdbcTemplate : Executing SQL statement [insert into apple (name) values ('apple001')]
--- [nio-8082-exec-1] o.s.j.d.DataSourceTransactionManager : Suspending current transaction, creating new transaction with name [com.hao.datasourcelearn.service.Test2.test02]
结果:
回滚
java.sql.SQLIntegrityConstraintViolationException: Column 'name' cannot be null
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:117) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.StatementImpl.executeInternal(StatementImpl.java:764) ~[mysql-connector-java-8.0.18.jar:8.0.18]
at com.mysql.cj.jdbc.StatementImpl.execute(StatementImpl.java:648) ~[mysql-connector-java-8.0.18.jar:8.0.18]
说明:
从日志打印来看,Suspending current transaction, creating new transaction with name [com.hao.datasourcelearn.service.Test2.test02],是两个不同的事务,不是嵌套事务,虽然抛出异常,但是不会被marked as rollback-only,也就不会抛出UnexpectedRollbackException。但是这个异常会被抛出到父级test01里面,会影响test01的事务提交的。
总结:
这篇文章主要偏重于从例子上验证一些常见的疑问,没有对具体的源码进行分析,只是从代码的角度简单的解释了一下UnexpectedRollbackException产生的原因和场景。感觉读源码不容易呀,要想完全搞懂一个,确实需要很多精力和耐心,真的要死磕到底,才能有所收获,路漫漫其修远兮,吾将上下而求索,后面继续......欢迎大家指点错误,一起讨论,多多指教。觉察即自由。