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>
比较来看,最后的基于注解的方式是最容易,也是代码量最少的。