上一篇博客中讲到的是编程式事务管理,这种方式并不好,因为这种方式把一些非业务代码侵入到了业务逻辑代码中。
这一讲讲声明式事务管理。
一、使用XML配置声明式事务管理
使用XML配置声明式事务管理用到了之前讲到的AOP技术,前面讲过AOP技术能够动态的在逻辑代码中切入一些非业务性代码。
1.复制上一个工程为一个新的工程Spring405-02;
2.由于使用的声明式事务管理不会修改业务的逻辑代码,因此修改BankServiceImpl文件为:
package com.test.service.impl;
import com.test.dao.BankDao;
import com.test.service.BankService;
public class BankServiceImpl implements BankService{
private BankDao bankDao;
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
@Override
public void transferAccounts(final int count,final int userIdA,final int userIdB) {
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
}
这里把那些非业务性的代码都去掉了。
3.配置Spring文件:首先要添加新的命名空间以及需要的文件地址
4.修改后的配置文件为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务切面 -->
<aop:config>
<!--配置切点 -->
<aop:pointcut expression="execution(* com.test.service.*.*(..))" id="serviceMethod"/>
<!--配置事务通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
<bean id="bankDao" class="com.test.dao.impl.BankDaoImpl">
<property name="namedParameterJdbcTemplate" ref="namedParameterJdbcTemplate"></property>
</bean>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="bankService" class="com.test.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
</bean>
</beans>
进行说明:首先定义了事务管理器的一个实例对象,配置事务的时候需要用到事务管理器。然后使用<tx:advice>标签定义事务通知,事务通知中使用<tx:attributes>标签配置各个事务的属性(个人理解,一个事务其实就是一个执行方法,向这里用到的转账例子,转账方法transferAccounts()就是一个事务,这个事务包含了两个数据库操作,无论其中那个操作出现错误都会回滚到这个事务执行之前的状态。这样理解的话,一个执行方法就是一个事务),使用<tx:method>标签配置具体事务的属性,这个例子中使用了*值,也就是无论是什么事务都采用默认的事务属性值(对于转账事务,按照这里的配置也是使用默认的事务属性)。
使用<aop:config>配置事务切面,也就是配置具体的事务。里面使用<aop:pointcut>配置切点,这里也就是当执行com.test.service包下的所有方法时。使用<aop:advisor>配置该事务要使用的事务通知。
总结一下流程:根据<aop:config>的配置知道,当程序执行BankServiceImpl类中的transferAccounts()方法时会把这个方法当做一个事务进行处理,处理这个事务时要遵守的规则是txAdvice这个事务通知中指定的规则。而txAdvice这个事务通知配置了所有的事务都使用默认的规则。
这里使用<aop:config>标签了配置具体的各个事务:通过<aop:pointcut>标签配置要把哪些方法当成事务;通过<aop:advisor>标签配置当执行具体的事务时要遵守什么样的事务规则。
使用<tx:advice>标签配置事务通知,也就是配置执行事务时要遵守的规则。这个标签定义的事务通知是一个公用的通知,能够被其它的具体事务(<aop:config>标签定义的具体事务)所关联。
5.运行测试方法:虽然工程报错了,但是数据库中的数据并没有变化:这说明已经发生了回滚
二、使用注解配置声明式事务
第一部分使用的是使用xml配置的方式配置声明式事务。这一部分讲述使用注解方法配置声明式事务。
1.使用xml配置声明式事务时使用<aop:config>标签来定义具体的事务,而使用注解方式的时候只需要在要封装成事务的方法所在的类上面添加注解@Transactional即可。修改BankServiceImpl为:
package com.test.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.test.dao.BankDao;
import com.test.service.BankService;
@Transactional
public class BankServiceImpl implements BankService{
private BankDao bankDao;
public void setBankDao(BankDao bankDao) {
this.bankDao = bankDao;
}
@Override
public void transferAccounts(final int count,final int userIdA,final int userIdB) {
bankDao.outMoney(count, userIdA);
bankDao.inMoney(count, userIdB);
}
}
这里在这个类的上部添加了注解@Transactional,这样Spring就会把这个类中的每个public方法都当做是一个事务来处理。
2.修改Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="bankDao" class="com.test.dao.impl.BankDaoImpl">
<property name="namedParameterJdbcTemplate" ref="namedParameterJdbcTemplate"></property>
</bean>
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="bankService" class="com.test.service.impl.BankServiceImpl">
<property name="bankDao" ref="bankDao"></property>
</bean>
</beans>
这里还是首先要定义一个事务管理器实例用来管理具体的数据源。然后使用<tx:annotation-driven>标签来给各个事务定义事务管理器。这样定义之后,工程中所有被@Transactional注解的事务都会使用这里定义的事务管理器。执行事务时遵守默认的规则。
3.运行测试方法,效果与使用xml配置方式相同。
三、事务传播行为
一般情况下使用xml配置方式配置声明式事务要更好一些。对于大的项目会有很多的service,使用xml配置的话只需在配置文件中配置<aop:config>即可,但是如果使用注解的话则要添加几十个注解。另外使用xml配置方式的话会很容易的配置事务传播行为。
什么是事务传播行为呢?
事务传播行为:Spring 中,当一个service 方法调用另外一个service 方法的时候,因为每个service 方法都有事务,这时候就出现了事务的嵌套;由此,就产生了事务传
播行为;在Spring 中,通过配置Propagation,来定义事务传播行为;
使用xml配置中讲到过使用<tx:advice>标签来配置事务通知(处理事务时遵守的规则也就是事务的各种属性),具体使用<tx:method>标签来定义具体事务的各个属性。事务传播行为是事务最常用的属性,使用propagation属性来表示,事务的传播行为有如下几种:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
事务的具体的属性列表如图所示:
上面是事务的各个属性,里面很多都不需要配置,使用默认值即可。最常见的一种配置方式为:
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="edit*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="new*" propagation="REQUIRED" />
<tx:method name="set*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="change*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" read-only="true" />
<tx:method name="find*" propagation="REQUIRED" read-only="true" />
<tx:method name="load*" propagation="REQUIRED" read-only="true" />
<tx:method name="*" propagation="REQUIRED" read-only="true" />
</tx:attributes>
这个配置中,对于以insert开始的方法(也就是事务名以insert开始的事务),这种事务一般都会操作数据库中的数据,采用REQUIRED方式的传播行为,其他的属性使用默认值。对于以get、find、load开头的方法,这些事务一般都只是查询数据库中的数据,并不会改变数据库中的数据,添加read-only属性值为true,设为只读事务。