一. 事务的传播机制/required 跟 required new 的使用与区别
基础回顾
1.1 事务的隔离级别:
ISOLATION_READ_UNCOMMITTED(读未提交)
ISOLATION_READ_COMMITTED(读已提交)
ISOLATION_REPEATABLE_READ(可重复读)
ISOLATION_SERIALIZABLE(序列化)
ISOLATION_DEFAULT(使用数据库默认隔离级别)
1.2 脏读,不可重复读以及幻读
脏读:一个事务读取到另一事务未提交的更新新据。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。
幻读:事务1正常查询一次,之后事务2插入满足事务1查询条件的新行,再次用事务1查询,得到多出来的数据。
1.3 事务的传播属性:
PROPAGATION_REQUIRED: 支持当前事务,没有则新建
PROPAGATION_REQUIRESNEW: 新建事务,如果当前存在事务,把当前事务挂起
PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常
PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起。也就是说业务方法不需要事务
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。也就是说业务方法绝对不能在事务范围内执行
PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按REQUIRED属性执行
二. 事务的回滚及传播机制总结
1. 首先要注意的是 spring 的默认回滚,roll-back = “runtimeException”,即 spring 只回滚接收到的运行时异常,对于其他异常则不回滚;
源码解析:
/*** Handle a throwable, completing the transaction.* We may commit or roll back, depending on the configuration.* @param txInfo information about the current transaction* @param ex throwable encountered*/protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {if (txInfo != null && txInfo.hasTransaction()) {if (logger.isTraceEnabled()) {logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +"] after exception: " + ex);}if (txInfo.transactionAttribute.rollbackOn(ex)) {try {txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());}catch (TransactionSystemException ex2) {logger.error("Application exception overridden by rollback exception", ex);ex2.initApplicationException(ex);throw ex2;} ...
注意上述有一段是 txInfo.transactionAttribute.rollbackOn(ex) 方法,跟进此方法:
/*** The default behavior is as with EJB: rollback on unchecked exception.* Additionally attempt to rollback on Error.* <p>This is consistent with TransactionTemplate's default behavior.*/public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);}
所以,默认不修改的话,只回滚 RuntimeException 或者 Error;
2. 对此我们可以针对性的添加需要回滚的策略,或者直接显示设置 roll-back = “Exception”,即回滚所有异常;
3. 需要注意的是:普通情况下,如果异常被 try-catch 处理了,并为进一步抛出异常,那么由于 spring 并未接受到异常, 所以也不会回滚;
本周讨论补充知识:
4. 异常分为 应用层抛出 的异常,以及 数据库 的异常,如果是前者,可以正常应用 spring 的事务传播规则,如果是后者,那么一旦发生,数据库端就已经放弃事务,也就没有回滚一说();
例: int 10/0 为应用层的异常,可以正常回滚; insert 提交一个已存在的 唯一字段,那么数据库会报错,由于事务本身也是由 spring 告之数据库处理,这种情况下,由于 数据库已经放弃事务,那么 spring 层面也不能进行事务回滚;
5. 正常情况下不会出现一个接口的 bean 进行嵌套调用,如果有,该情况下,由于代理类不再使用 spring 动态代理中的接口实现的方式,而是采用 cglib 的继承实现方式,同样也会导致传播失效;
6. 事务的传播机制较为常用的为:required,required-new,support。讨论结果为 required-new 跟 required 相比,使用应较为谨慎,原因:同一事务粒度更细更可控,同时也能避免 嵌套事务 可能导致的复杂情况,当然,具体情况需要根据具体业务来定,比如本次讨论中的项目,由于每一次操作的对象都要及时被可能存在的另一进程知悉,这种情况下需要 required-new 来实时开启事务告之其他进程;
查询总结过程中发现的其它知识点
1. 注意的小点,MySQL 中 InnoDB 支持事务,MyIsam 并不支持事务;
2. spring 已经提供了异常处理机制,其基类为 DataAccessException ,它是 RuntimeExcption 的一个子类,据此可以知道,所有从 数据库返回的异常,都会被 spring 处理后 归属于 RuntimeException 中,其体系如下:
2. 在 org.springframework.jdbc.support包下有sql-error-codes.xml文件,里面预定义了一些错误代码和信息,其 bean 为 HSQL,在 第8行,我们可以发现重复插入的 code 为 -104;
bean id="HSQL" class="org.springframework.jdbc.support.SQLErrorCodes" property name="databaseProductName" valueHSQL Database Engine/value /property property name="badSqlGrammarCodes" value-22,-28/value /property property name="duplicateKeyCodes" value-104/value /property property name="dataIntegrityViolationCodes" value-9/value /property property name="dataAccessResourceFailureCodes" value-80/value /property /bean
3. 此外,我们可以知道发散性的看,我们完全可以自定义数据库异常的信息,方法如下:
3.1 重新新建一个sql-error-codes.xml代码,并将它放到类路径的根目录下,这样Spring会发现它并使用我们自定义的文件。同时HSQL的bean的名称不要改,并将useSqlStateForTranslation置为false,就可以使用我们自己定义的异常类;
bean id="HSQL" class="org.springframework.jdbc.support.SQLErrorCodes" property name="databaseProductName" value="HSQL Database Engine" / property name="useSqlStateForTranslation" value="false" / property name="customTranslations" list ref local="vehicleDuplicateKeyTranslation" / /list /property /bean bean id="vehicleDuplicateKeyTranslation" class="org.springframework.jdbc.support.CustomSQLErrorCodesTranslation" property name="errorCodes" value="-104" / property name="exceptionClass" value="org.ourpioneer.vehicle.exception.VehicleDuplicateKeyException" / /bean
3.2 可以看到结果,控制台所打印的异常类型为我们自己定义的异常类型;
3.3 除此之外,还可以实现SQLExceptionTranslator接口,并在JDBC模板中注入其实例来实现异常控制
1. package org.ourpioneer.vehicle.exception; 2. import java.sql.SQLException; 3. import org.springframework.dao.DataAccessException; 4. import org.springframework.jdbc.UncategorizedSQLException; 5. import org.springframework.jdbc.support.SQLExceptionTranslator; 6. public class VehicleDuplicateKeyTranslator implements SQLExceptionTranslator { 7. public DataAccessException translate(String task, String sql, 8. SQLException ex) { 9. if (task == null) { 10. task = ""; 11. } 12. if (sql == null) { 13. } 14. if (ex.getErrorCode() == -104) { 15. return new VehicleDuplicateKeyException(buildMessage(task, sql, ex)); 16. } else { 17. return new UncategorizedSQLException(task, sql, ex); 18. } 19. } 20. private String buildMessage(String task, String sql, SQLException ex) { 21. return "数据库操作异常:" + task + "; SQL [" + sql + "]; " + ex.getMessage(); 22. } 23. }
3.4 translate方法有三个参数,task表示当前操作要进行的任务是什么,sql就是执行的sql语句,ex表示SQLException,我们可以从中获取异常信息,其处理代码仅仅捕捉了错误码为-104(HSQL数据库)的错误,其余的配置信息可以根据需要来自行添加。之后要在Spring中重新配置它们:
1. bean id="vehicleDuplicateKeyTranslator" 2. class="org.ourpioneer.vehicle.exception.VehicleDuplicateKeyTranslator"/bean 3. bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" 4. property name="exceptionTranslator" ref="vehicleDuplicateKeyTranslator" / 5. property name="dataSource" ref="dataSource" / 6. /bean 7. bean id="vehicleDAO" class="org.ourpioneer.vehicle.dao.VehicleDAOImpl" 8. property name="jdbcTemplate" ref="jdbcTemplate" / 9. /bean
3.5 测试结果
三. 总结
其实之前对事务的理解是比较基础的,经过周六的交流和学习,才对事务有了略深一层的理解,但同时也愈发觉得事务的博大精深应远不止于此,本文大部分都是结合各位?同事的理解和网上的一些资料整理的文章,所以本文作用更多的督促自己的学习进步,希望以后自己能不断的吸收,整理和提炼知识?。
参考文献:
mysql隔离级别及事务传播:https://gist.github.com/JagoWang/4555317
mysql事务管理及spring声明式事务中主动异常抛出使数据库回滚:http://www.cnblogs.com/wanglonghai/p/4866512.html
Spring访问数据库异常的处理方法:http://kb.cnblogs.com/page/89216/