How to make transactional failed? How to use @Transactional
怎么做会使事务失败? 怎么用事务注解
1. @Transactional
事务注解会让一个方法在执行完之后,要么提交所有的改变,要么什么都不改变。ACID 原则
1.1 Mysql engine: use InnoDB 数据库引擎必须支持事务
MyISAM: Transactions NO Before MySQL 5.5.5, MyISAM is the default storage engine. (The default was changed to InnoDB in MySQL 5.5.5.) MyISAM is based on the older (and no longer available) ISAM storage engine but has many useful extensions.
如果 MySQL 数据库的引擎是 MyISAM,那它根本不支持事务
1.2 Make sure the class will be instance as a Bean: @Service类必须实例化成 Spring 的 Bean
1.3 Function must be public方法必须是 public 的
Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
1.4 Self-invocation 自我调用问题
在同一个 Bean 中 一个正常的方法叫一个带事务注解的方法,事物是不会奏效的 testSelfInvocation叫save方法,虽然最后有 Runtime 的异常,但数据依然插入了 我们可以通过延迟注入一个相同类型的 Bean 来解决这个问题
@Service class ManService(
private val manRepository: ManRepository, @Lazy private val self: ManService
) { // NO INSERT fun notSelfInvocation() { val man = Man("1", Woman("1")) self.save(man) } // NO INSERT @Transactional fun save(man: Man): Man { val result = manRepository.save(man) throw RuntimeException("test") return result } /* In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct. */ /* Hibernate: insert into woman (reference, id) values (?, ?) Hibernate: insert into man (reference, woman_id, id) values (?, ?, ?) */ fun testSelfInvocation() { val man = Man("1", Woman("1")) this.save(man) } }
1.5 catch custom exception but do not throw it, insert won’t be rollback只抓不抛异常是不会让事务回滚的
class MyException(m: String) : Exception(m) @Transactional fun catchException() { try { val man = Man("1", Woman("1"))
manRepository.save(man) throw MyException("custom exception")
} catch (e: Exception) {
}
}
Hibernate: insert into woman (reference, id) values (?, ?)
Hibernate: insert into man (reference, woman_id, id) values (?, ?, ?)
1.6 Throws exception but do not rollback抛出自定义异常是不会让事务回滚的
By default, a transaction will be rolling back on RuntimeException and Error but not on checked exceptions (business exceptions).
下图是java中异常类的关系图:
- Not Runtime Exception, so transaction won’t be rollback 不是 Runtime 的异常不行
@Transactional @Throws(MyException::class) fun catchThrowsException() { try { val man = Man("1", Woman("1"))
manRepository.save(man) throw MyException("custom exception")
} catch (e: MyException) { throw e
}
}
Hibernate: insert into woman (reference, id) values (?, ?)
Hibernate: insert into man (reference, woman_id, id) values (?, ?, ?)
- Declare rollbackFor 声明需要为哪些异常回滚
@Transactional(rollbackFor = [MyException::class]) @Throws(MyException::class) fun catchThrowsExceptionWithTransactional() { try { val man = Man("1", Woman("1"))
manRepository.save(man) throw MyException("custom exception")
} catch (e: MyException) { throw e
}
} // NO INSERT
- RuntimeException 默认的回滚异常
@Transactional fun runtimeException() { val man = Man("1", Woman("1"))
manRepository.save(man) throw RuntimeException("custom exception")
} // NO INSERT
2. Propagation 事务的传播模式
2.1 REQUIRED & REQUIRED_NEW
- REQUIRED
- 如果没有就创建一个,如果有,就用当前的
- REQUIRED_NEW
- 每次都会创建一个新的,如果当前就有一个,那么会把当前的挂起
/** * Support a current transaction, create a new one if none exists. * Analogous to EJB transaction attribute of the same name. * <p>This is the default setting of a transaction annotation. */ REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * Create a new transaction, and suspend the current transaction if one exists. * Analogous to the EJB transaction attribute of the same name. * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code javax.transaction.TransactionManager} to be * made available to it (which is server-specific in standard Java EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
- REQUIRED 如果两个方法共享一个实务,一个需要回滚,一个需要提交,就会出现UnexpectedRollbackException
// NO INSERT TO DB
@Transactional
fun runtimeException() {
val man = Man("1", Woman("1"))
manRepository.save(man) throw RuntimeException("custom exception")
} // org.springframework.transaction.UnexpectedRollbackException: // Transaction silently rolled back because it has been marked as rollback-only
@Transactional
fun callAnotherTransactionalThrowRuntimeException(){
val woman = Woman("1")
womanRepository.save(woman) try {
manService.runtimeException()
}catch (e:RuntimeException){}
}
- REQUIRED_NEW 两个方法都有自己的事务,所以不会互相影响。
These two functions are in the same transaction. When function runtimeException throws an exception, it will mark current transaction should be rollback. But callAnotherTransactional ThrowRuntimeException catch it, so current transaction think it should commit. That’s why it throws UnexpectedRollbackException . Another example with @Transactional(propagation = Propagation.REQUIRES_NEW)
// NO INSERT TO DB
@Transactional(propagation = Propagation.REQUIRED_NEW)
fun runtimeExceptionWithPropagationRequiredNew() {
val man = Man("1", Woman("1"))
manRepository.save(man)
throw RuntimeException("custom exception")
} /* Hibernate: select woman0_.id as id1_1_0_, woman0_.created_at as created_2_1_0_, woman0_.reference as ... Hibernate: select man0_.id as id1_0_1_, man0_.created_at as created_2_0_1_, ... Hibernate: select woman0_.id as id1_1_0_, woman0_.created_at as created_2_1_0_, ... Hibernate: insert into woman (reference, id) values (?, ?) */ @Transactional fun callAnotherTransactional(){
val woman = Woman("1")
womanRepository.save(woman)
try {
manService.runtimeExceptionWithPropagationRequiredNew()
}catch (e:RuntimeException){}
}
runtimeExceptionWithPropagationRequiredNew will rollback, but callAnotherTransactional will commit.
2.2 SUPPORTS 如果当前有事务就用,如果没有就不用事务的
/** * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: For transaction managers with transaction synchronization, * {@code SUPPORTS} is slightly different from no transaction at all, * as it defines a transaction scope that synchronization will apply for. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) * will be shared for the entire specified scope. Note that this depends on * the actual synchronization configuration of the transaction manager. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS)
See example below
@Transactional(propagation = Propagation.SUPPORTS) fun propagationSupport(){ val man = Man("1", Woman("1"))
manRepository.save(man) throw RuntimeException("custom exception")
} /* Hibernate: insert into woman (reference, id) values (?, ?) Hibernate: insert into man (reference, woman_id, id) values (?, ?, ?) */ fun nonTransactionalCallSupport() {
manService.propagationSupport()
} // NO INSERT @Transactional fun transactionalCallSupport() { manService.propagationSupport()
2.3 NESTED 不建议使用,和 JDBC 版本有关
/** * Execute within a nested transaction if a current transaction exists, * behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB. * <p>Note: Actual creation of a nested transaction will only work on specific * transaction managers. Out of the box, this only applies to the JDBC * DataSourceTransactionManager. Some JTA providers might support nested * transactions as well. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */ NESTED(TransactionDefinition.PROPAGATION_NESTED)
there is no test.
I see somewhere says that DataSourceTransactionManager only available for JdbcTemplate and ibatis, need JDBC3.0
org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities
最新评论
mat插件可以检测内存数据
标识接口?
序列化serializabel就是一个标识
就差一个MAC了
mark
除了预置sql查询字段,其他我竟然都没用过
可以,这个问题遇到过
mybatis多个参数: 1. 注解(最常用) 2. 转化为对象或MAP 3. 按顺序(这个最蠢,写的代码看得费劲) 单个参数需要注意得: 1.基本数据类型随便写 2.数组用array,l