Spring管理事务的方式有两种:
编程式事务:利用手动代码编写事务相关的业务逻辑,这种方式比较复杂、啰嗦,但是更加灵活可控制
public void testTransactionTemplate() {
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager); transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); //设置事务隔离级别 transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置为required传播级别 .... transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //事务块 jdbcTemplate.update(INSERT_SQL, "test"); }}); }
声明式事务:为了避免我们每次都手动写代码,利用Spring AOP的方式对每个方法代理环绕,利用xml配置避免了写代码。
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!--设置所有匹配的方法,然后设置传播级别和事务隔离--> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="merge*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="put*" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* org.transaction..service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config>
同时也可以用注解的方式
<tx:annotation-driven transaction-manager="transactioManager" /><!--开启注解的方式-->
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED;//传播级别 Isolation isolation() default Isolation.DEFAULT;//事务隔离级别 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//事务超时时间 boolean readOnly() default false;//只读事务 Class<? extends Throwable>[] rollbackFor() default {};//抛出哪些异常 会执行回滚 String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {};//不回滚的异常名称 } //Transaction注解可以放在方法上或者类上
注意事项:
@Transactional注解可以作用在接口、类、类方法。
作用于类:当把@Transactional 注解放在类上时,表示所有该类的public方法都配置相同的事务属性信息。
作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息。
作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效
Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。
请注意仅仅 @Transactional 注解的出现不足于开启事务行为,并且只有配置了<tx:annotation-driven/>元素,@Transactional才会有效。
Spring建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
@Transactional不生效的场景
1、用在public 修饰的方法上
如果在protected、private 修饰的方法上使用 @Transactional 注解,不会有任何报错,但是事务无效
还是动态代理的原因,类内部方法的调用是通过this调用的,不会使用动态代理对象,事务不会回滚。
3、异常被处理了,比如使用了try..catch
Spring是根据抛出的异常来回滚的,如果异常被捕获了没有抛出的话,事务就不会回滚。
4、注解 rollbackFor属性设置不对
Spring默认抛出unchecked异常或Error时才会回滚事务,要想其他类型异常也回滚则需要设置rollbackFor属性的值。
5、数据库引擎不支持事务
常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了
Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,
Spring共支持7种传播行为
@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
为了演示事务传播行为,我们新建一张用户表
CEATE TABLE user ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `pwd` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;
Required:
必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务,如下图所示;
测试的代码如下,在account插入的地方主动回滚
public int insertAccount(final String customer, final int money) {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置为required传播级别
int re= transactionTemplate.execute(new TransactionCallback<Integer>() { public Integer doInTransaction( TransactionStatus status) { int i = accountDao.insertAccount(customer, money); status.setRollbackOnly();//主动回滚 return i; } }); return re; } public int inertUser(final String username, final String password) { transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置为required传播级别 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { int i = userDao.inertUser(username, password); int hahha = accountService.insertAccount("hahha", 2222); // status.setRollbackOnly(); System.out.println("user==="+i); System.out.println("account===="+hahha); } }); return 0; }
按照required的逻辑,代码执行的逻辑如下:
- 在调用userService对象的insert方法时,此方法用的是Required传播行为且此时Spring事务管理器发现还没开启逻辑事务,因此Spring管理器觉得开启逻辑事务
- 在此逻辑事务中调用了accountService对象的insert方法,而在insert方法中发现同样用的是Required传播行为,因此直接使用该已经存在的逻辑事务;
- 返回userService,执行完并关闭事务
所以在这种情况下,两个事务属于同一个事务,一个回滚则两个任务都回滚。
RequiresNew:
创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)如下图所示:
Supports:
支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行,如下图所示:
NotSupported:
不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行。
Mandatory:
必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException)。当运行在存在逻辑事务中则以当前事务运行,如果没有运行在事务中,则抛出异常
Never
不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException)
Nested:
嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚。
Nested和RequiresNew的区别:
- RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
- Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
-
Nested使用JDBC 3的保存点(save point)实现,即如果使用低版本驱动将导致不支持嵌套事务。
使用嵌套事务,必须确保具体事务管理器实现的nestedTransactionAllowed属性为true,否则不支持嵌套事务,如DataSourceTransactionManager默认支持,而HibernateTransactionManager默认不支持,需要设置来开启。
关于spring的事务隔离级别与数据库的一样(详见mysql事务),也是那四个,多了一个default
MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED
比如下面的spring的配置方法:
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
</props>
就以find为例,可以这么配置,前面是控制传播行为,后面是控制事务隔离级别的。那么这时哪怕数据库层面上是重复读(Repeatable read),但是还是以这里为准,你会发现在同一个事务中两次查询的结果是不一样的。
最后问题,readonly这个属性,是放在传播行为中的, readonly并不能影响数据库隔离级别,只是配置之后,不允许在事务中对数据库进行修改操作,仅此而已。