Spring的声明式事务使用起来比较简单,[email protected],就将一个方法变成事务方法了。使用声明式事务,可以将事务管理代码从业务方法中抽离出来(也就是不用在方法里写commit和rollback),以声明的方式来实现事务管理。
Spring的核心事务管理抽象是PlatformTransactionManager,要想使用声明式事务,必须先在xml中先将其声明为bean,而且还要记得配置数据源
<!--这么写就可以扫描到db.properties的配置信息-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!--配置数据源-->
<bean id = "dataSource" class = "com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverName}"></property>
</bean>
<!--配置事务管理器的bean对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--如果代码中用到jdbc操作数据库,可以加下面的配置-->
<!--将JdbcTemplate注册为bean-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务管理器的配置-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
#db.properties
jdbc.username = root
jdbc.password = root
jdbc.url = jdbc:mysql://localhost:3306/tx
jdbc.driverName = com.mysql.jdbc.Driver
@Transactional
下面说明一下Transactional里的重要参数
-
ioslation:隔离级别。可配置为可重复读,读已提交,读未提交,序列化,DEFAULT(这个配置将使用选择的数据库的默认事务隔离级别)
-
timeout:超时时间。若方法的执行时间超过设定值,则报超时异常,单位是秒
-
readOnly:只读,表示该方法不允许修改数据
-
rollbackFor:指定一个或多个异常,当方法发生这个异常时,回滚
如@Transactional(rollbackFor = {java.lang.ArithmeticException.class})
。这一项,强烈建议每个声明式事务都必须配置 -
rollbackForClassName:与上同,只是传参形式不同
如@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
-
noRollbackFor:指定一个或多个异常,当方法发生这个异常时,不回滚
如@Transactional(noRollbackFor = {java.lang.ArithmeticException.class})
-
noRollbackForClassName:与上同,只是传参形式不同
如@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
-
propagation:传播特性,表示不同事务之间执行的关系,下文将重点阐述传播特性
事务的传播特性
传播特性:表示不同事务之间执行的关系。或者说当一个事务方法A被另一个事务方法B调用时,A和B会发生怎样的关系
下面的文字比较绕,如果看不懂(一般人都看不懂…)直接看下面的例子解释就行
注意下面所有multi方法与buyBook和updatePrice不在一个类中。若在一个类,multi对buyBook和updatePrice的调用将会是普通调用,不会产生事务传播特性
REQUIERD(这是默认传播特性)
@Transactional()
public void multi(){
bookService.buyBook(); [email protected](propagation = Propagation.REQUIRED)
bookService.updatePrice(BOOK_ID); [email protected](propagation = Propagation.REQUIRED)
}
REQUIRED的效果是:当内部的事务方法配置了REQUIRED之后,事务的控制交由外部事务方法(multi)控制。也就是说,将buyBook和updatePrice统一成一个事务,只有buyBook和updatePrice都commit时,整个multi才算commit。不论buyBook还是updatePrice发生异常(不论是否在multi被try-catch捕获),二者都会rollback,一个都不会commit。如果取消multi的事务声明使之变为普通方法,那么buyBook和updatePrice,谁发生异常谁rollback,不影响另一个方法的commit
REQUIRES_NEW
@Transactional()
public void multi(){
bookService.buyBook(); [email protected](propagation = Propagation.REQUIRES_NEW)
bookService.updatePrice(BOOK_ID); [email protected](propagation = Propagation.REQUIRED)
}
REQUIRES_NEW的效果是:配置成REQUIRES_NEW的方法会单独执行一个事务,若buyBook发生异常(不论是否被try-catch捕获),则buyBook会rollback,不影响updatePrice的commit。从REQUIRES_NEW这一名字就可联想到,配成REQUIRES_NEW的方法,需要一个新事务单独执行,而由于事务的隔离性,所以配成REQUIRES_NEW的事务不会影响其他事务,即使它们被捆绑到同一个事务方法(multi)里
SUPPORTS
@Transactional()
public void multi(){
bookService.buyBook(); [email protected](propagation = Propagation.SUPPORTS)
}
SUPPORTS的效果是:如果multi是事务方法,则buyBook受事务控制。如果multi是普通方法,则buyBook不受事务控制(即,发生异常的代码前的逻辑会成功commit,发生异常代码后的逻辑会报错失败)。SUPPORTS方式几乎不用
NOT_SUPPORTED
@Transactional()
public void multi(){
bookService.buyBook(); [email protected](propagation = Propagation.NOT_SUPPORTED)
}
NOT_SUPPORTED的效果是:不论multi是不是事务方法,buyBook均不受事务控制(即,发生异常的代码前的逻辑会成功commit,发生异常代码后的逻辑会报错失败)。NOT_SUPPORTED方式几乎不用
MANDATORY
[email protected]()
public void multi(){
bookService.buyBook(); [email protected](propagation = Propagation.NEVER)
}
NEVER的效果是:必须在buyBook外面再套一层事务方法,否则会报下列错误。MANDATORY几乎不用(唯一的用处可能就是,强制让其他程序员在使用buyBook时,必须要与其他事务一起使用,捆绑在一个事务里)
NEVER
@Transactional()
public void multi(){
bookService.buyBook(); [email protected](propagation = Propagation.NEVER)
}
NEVER的效果是:不允许buyBook外面再套一层事务方法,否则会报下列错误。NEVER与MANDATORY正好相反。NEVER方式几乎不用(唯一的用处可能就是,强制让其他程序员在使用buyBook时,不允许再在外层套一个事务方法)
NESTED
@Transactional(rollbackFor = {
java.lang.ClassCastException.class})
public void multi(){
try {
bookService.buyBook();[email protected](propagation = Propagation.NESTED)
}catch(Exception e){
}
bookService.updatePrice(BOOK_ID);[email protected]
int i = 1/0;
}
NEVER的效果是:如果buyBook方法发生异常(updatePrice无异常),不被捕获,则buyBook和updatePrice都不会回滚。可是如果buyBook被捕获了,并且multi发生异常时,则buyBook和updatePrice都会回滚。一句话说就是:NESTED的配置会导致,不论multi里怎么对异常方法处理了,所有方法均会回滚。换言之,multi里的所有方法,都被multi里都异常所制约
对REQUIRED,REQUIRES_NEW,NESTED做一个区别
- REQUIRED:将所有事务方法捆绑为一个事务,一荣俱荣,一殒具殒
- REQUIRES_NEW:单独将配置成REQUIRES_NEW的那个事务视为独立的事务,不管它有没有被捆绑到外部事务中,不管外部事务是否发生异常,它的成功commit与否,均与其他事务无关。
- NESTED:不论被捆绑的事务在外部事务中做了怎样的异常捕获,均被外部事务中的异常所制约,一旦外部事务发生异常,所有捆绑到外部事务中的事务,都要回滚。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/20577.html