Java Spring-事务管理
2017-11-12 16:31:59
Spring的事务管理分为两种:
- 编程式的事务管理:手动编写代码
- 声明式的事务管理:只需要配置就可以
一、最初的环境搭建
public interface AccountDAO { public void out(String to,Double money); public void in(String from, Double money); } public class AccountDAOImpl extends JdbcDaoSupport implements AccountDAO { @Override public void out(String from, Double money) { String sql = "update account set money = money - ? where name = ?"; this.getJdbcTemplate().update(sql, money,from); } @Override public void in(String to, Double money) { String sql = "update account set money = money + ? where name = ?"; this.getJdbcTemplate().update(sql, money , to); } } public interface AccountService { public void transfer(String from, String to, Double money); } public class AccountServiceImpl implements AccountService { @Resource(name = "accountDao") private AccountDAO accountDAO; @Override public void transfer(String from, String to, Double money) { accountDAO.out(from,money); accountDAO.in(to,money); } } // 测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:config4.xml") public class TestDemo { @Resource(name = "accountservice") private AccountService accountService; @Test public void demo(){ accountService.transfer("aaa","bbb",100d); } }
二、手动式的事务管理
可以发现,在没有引入事务管理的时候,如果在转账的out和in之间出现了异常,那么就会导致转账的结果出错。所以我们需要引入事务管理技术。
Spring提供了事务管理的模板(工具类),可以方便我们对事务进行管理。
具体步骤:
- 第一步:注册事务管理器
- 第二步:注册事务模板类
- 第三步:在业务层注入模板类
- 第四步:在业务层代码上使用模板
public class AccountServiceImpl implements AccountService { @Resource(name = "accountDao") private AccountDAO accountDAO; @Resource(name = "transactionTemplate") private TransactionTemplate transactionTemplate; @Override public void transfer(String from, String to, Double money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { accountDAO.out(from,money); accountDAO.in(to,money); } }); } }
XML的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置连接池--> <!--<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">--> <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>--> <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>--> <!--<property name="username" value="host"/>--> <!--<property name="password" value="hy1102"/>--> <!--</bean>--> <!-- 配置DBCP连接池 --> <!--<bean id="datasource" class=" org.apache.commons.dbcp.BasicDataSource ">--> <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>--> <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>--> <!--<property name="username" value="host"/>--> <!--<property name="password" value="hy1102"/>--> <!--</bean>--> <!-- 引入该属性文件 --> <!--<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">--> <!--<property name="location" value="classpath:jdbc.properties"/>--> <!--</bean>--> <!-- 使用 context 标签引入属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置 c3p0 连接池 --> <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 定义模板 --> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="datasource"/> </bean> <bean id="userdao" class="spring3.UserDao"> <property name="jdbcTemplate" ref="jdbctemplate"/> </bean> <!--业务层--> <bean id="accountservice" class="spring4.AccountServiceImpl"/> <!--持久层--> <bean id="accountDao" class="spring4.AccountDAOImpl"> <!--事实上可以直接注入连接池来创建模板--> <property name="dataSource" ref="datasource"/> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 需要注入连接池,通过连接池获得连接 --> <property name="dataSource" ref="datasource"/> </bean> <!-- 事务管理的模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> </beans>
三、声明式的事务管理
手动编码方式类似于对transfer方法进行增强,所以考虑代理Service对象。
- 基于原始的TransactionProxyFactoryBean
// 业务代码 public class AccountServiceImpl implements AccountService { @Resource(name = "accountDao") private AccountDAO accountDAO; @Override public void transfer(String from, String to, Double money) { accountDAO.out(from,money); accountDAO.in(to,money); } } // 测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:config4.xml") public class TestDemo { @Resource(name = "accountServiceProxy") private AccountService accountService; @Test public void demo(){ accountService.transfer("aaa","bbb",100d); } }
XML的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置连接池--> <!--<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">--> <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>--> <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>--> <!--<property name="username" value="host"/>--> <!--<property name="password" value="hy1102"/>--> <!--</bean>--> <!-- 配置DBCP连接池 --> <!--<bean id="datasource" class=" org.apache.commons.dbcp.BasicDataSource ">--> <!--<property name="driverClassName" value="com.mysql.jdbc.Driver"/>--> <!--<property name="url" value="jdbc:mysql://localhost:3306/testdb"/>--> <!--<property name="username" value="host"/>--> <!--<property name="password" value="hy1102"/>--> <!--</bean>--> <!-- 引入该属性文件 --> <!--<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">--> <!--<property name="location" value="classpath:jdbc.properties"/>--> <!--</bean>--> <!-- 使用 context 标签引入属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置 c3p0 连接池 --> <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 定义模板 --> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="datasource"/> </bean> <bean id="userdao" class="spring3.UserDao"> <property name="jdbcTemplate" ref="jdbctemplate"/> </bean> <!--业务层--> <bean id="accountservice" class="spring4.AccountServiceImpl"/> <!--持久层--> <bean id="accountDao" class="spring4.AccountDAOImpl"> <!--事实上可以直接注入连接池来创建模板--> <property name="dataSource" ref="datasource"/> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 需要注入连接池,通过连接池获得连接 --> <property name="dataSource" ref="datasource"/> </bean> <!-- 事务管理的模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> <!-- 配置生成代理对象 --> <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 目标对象 --> <property name="target" ref="accountservice"/> <!-- 注入事务管理器 --> <property name="transactionManager" ref="transactionManager"/> <!-- 事务的属性设置 --> <property name="transactionAttributes"> <props> <prop key="transfer">PROPAGATION_REQUIRED</prop> </props> </property> </bean> </beans>
prop格式:PROPAGATION,ISOLATION,readOnly,-Exception,+Exception
* 顺序:传播行为、隔离级别、事务是否只读、发生哪些异常可以回滚事务(所有的异常都回滚)、发生了哪些异常不回
滚
<prop key="transfer">PROPAGATION_REQUIRED,readonly</prop> <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop>
- 基于切面自动代理
上面的方法每次都要手动生成代理,显然是不太合适的,所以可以使用基于切面的自动代理。
public interface AccountDAO { public void out(String to,Double money); public void in(String from, Double money); } public class AccountDAOImpl extends JdbcDaoSupport implements AccountDAO { @Override public void out(String from, Double money) { String sql = "update account set money = money - ? where name = ?"; this.getJdbcTemplate().update(sql, money,from); } @Override public void in(String to, Double money) { String sql = "update account set money = money + ? where name = ?"; this.getJdbcTemplate().update(sql, money , to); } } public interface AccountService { public void transfer(String from, String to, Double money); } public class AccountServiceImpl implements AccountService { @Resource(name = "accountDao") private AccountDAO accountDAO; @Override public void transfer(String from, String to, Double money) { accountDAO.out(from,money); accountDAO.in(to,money); } } //测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:config5.xml") public class TestDemo { @Resource(name = "accountservice") private AccountService accountService; @Test public void demo(){ accountService.transfer("aaa","bbb",100d); } }
XML的配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 使用 context 标签引入属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置 c3p0 连接池 --> <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 定义模板 --> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="datasource"/> </bean> <bean id="userdao" class="spring3.UserDao"> <property name="jdbcTemplate" ref="jdbctemplate"/> </bean> <!--业务层--> <bean id="accountservice" class="spring4.AccountServiceImpl"/> <!--持久层--> <bean id="accountDao" class="spring4.AccountDAOImpl"> <!--事实上可以直接注入连接池来创建模板--> <property name="dataSource" ref="datasource"/> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 需要注入连接池,通过连接池获得连接 --> <property name="dataSource" ref="datasource"/> </bean> <!-- 定义一个增强 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 增强(事务)的属性的配置 --> <tx:attributes> <!-- isolation:DEFAULT :事务的隔离级别. propagation :事务的传播行为. read-only :false.不是只读 timeout :-1 no-rollback-for :发生哪些异常不回滚 rollback-for :发生哪些异常回滚事务 --> <tx:method name="transfer" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- aop配置定义切面和切点的信息 --> <aop:config> <!-- 定义切点:哪些类的哪些方法应用增强 --> <aop:pointcut expression=" execution(* spring4.AccountService+.*(..)) " id="mypointcut"/> <!-- 定义切面: --> <aop:advisor advice-ref="txAdvice" pointcut-ref="mypointcut"/> </aop:config> </beans>
- 基于注解的事务配置
具体步骤:
- 第一步:事务管理器
- 第二步:注解事务
- 第三步:在Service上使用注解
* 注解中有属性值:
* isolation
* propagation
* readOnly
...
- 第四步:测试
@Transactional public class AccountServiceImpl implements AccountService { @Resource(name = "accountDao") private AccountDAO accountDAO; @Override public void transfer(String from, String to, Double money) { accountDAO.out(from,money); // int i = 1/0; accountDAO.in(to,money); } }
XML配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 使用 context 标签引入属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置 c3p0 连接池 --> <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 定义模板 --> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="datasource"/> </bean> <bean id="userdao" class="spring3.UserDao"> <property name="jdbcTemplate" ref="jdbctemplate"/> </bean> <!--业务层--> <bean id="accountservice" class="spring4.AccountServiceImpl"/> <!--持久层--> <bean id="accountDao" class="spring4.AccountDAOImpl"> <!--事实上可以直接注入连接池来创建模板--> <property name="dataSource" ref="datasource"/> </bean> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 需要注入连接池,通过连接池获得连接 --> <property name="dataSource" ref="datasource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
比较来看,最后的基于注解的方式是最容易,也是代码量最少的。