1.论事务管理的重要性
1.论事务管理的重要性
在任何一个使用Spring框架的web工程中,事务管理是一个必须要考虑的东西。首先来回顾一下事物的四大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。关于这四大特性,这里不做解释,不明白的朋友可以百度一下。总之,只需要知道事务保证了,某段对数据库执行insert、update、delete操作的代码,要么全部执行、要么全都不执行这一点。在我们常见的业务有转账业务,比如账目表,表名account,字段id(唯一标识)、name(用户名)、money(余额)。表里面有两条记录:01(id)、AA(name)、1000(money);02(id)、BB(name)、1000(money),现在AA向BB转账200,需要根据ID更新AA的余额,减少200,;更新BB的余额,增加200。这两个更新操作要么全都执行,要么全都不执行。为了满足这个业务需求,就需要使用Spring的事务管理机制。
2.Spring 事务管理的三种方法
在进行事务管理时,先搭建一个简单的web工程,模拟一个开发中遇到一个转账情景。需要引入的jar包如下
文件目录如下图:
applicationContext.xml文件的主要内容如下
<span style="font-weight: normal;"><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的属性文件 -->
<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.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service层类 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao层类 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans></span>
业务层的AccountServiceImpl实现类只有一个方法如下
<span style="font-weight: normal;">package cn.spring.transaction.demo1;
public class AccountServiceImpl implements AccountService {
// 注入Dao类
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
/**
* 转账接口
* @param outName 转出账户
* @param inName 转入账户
* @param money 转账金额
*/
@Override
public void transfer(String outName, String inName, Double money) {
accountDao.outMoney(outName, money);
accountDao.inMoney(inName, money);
}
}</span>
持久层的AccountDaoImpl实现类有两个方法,一个是转出方法,另一个是转入方法,代码如下:
<span style="font-weight: normal;"><span style="font-size:12px;">package cn.spring.transaction.demo1;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
/**
* 转账的DAO接口的实现类
* @author wuqinghai
*
*/
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
/**
* 转出
* @param out 转出账户
* @param money 转出金额
*/
@Override
public void outMoney(String outName, Double money) {
String sql = "update account set money = money - ? where name = ?";
this.getJdbcTemplate().update(sql, money, outName);
}
/**
* 转入
* @param inName 转入账户
* @param money 转入金额
*/
@Override
public void inMoney(String inName, Double money) {
String sql = "update account set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money, inName);
}
}</span></span>
相应的数据库的sql语句<span style="font-weight: normal;">CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', 'AA', '1000');
INSERT INTO `account` VALUES ('2', 'BB', '1000');
INSERT INTO `account` VALUES ('3', 'CC', '1000'); </span>
(1). 编程式事务管理
在自己的Service中使用TransactionTemplate,然后TransactionTemplate依赖DataSourceTransactionManager,最后DataSourceTransactionManager依赖注入DataSource。在applicationContext.xml文件里添加相关类的注入如下的bean
<span style="white-space:pre"> </span><!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务管理器的模板: Spring为简化事务管理的代码而提供的类 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager" />
</bean>
因为数据库的连接使用的是JDBC,所以使用DataSourceTransactionManager类管理事务,如果使用的是hibernate,则使用hibernate相关的事务管理类。编程式事务管理,顾名思义,需要在自己的代码里编写相关的代码来进行事务的管理。修改后的transfer方法代码如下。 // 注入Dao类
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
// 注入事务管理的模板
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
/**
* 转账接口
* @param outName 转出账户
* @param inName 转入账户
* @param money 转账金额
*/
@Override
public void transfer(final String outName, final String inName, final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
@Override
protected void doInTransactionWithoutResult(TransactionStatus arg0) {
accountDao.outMoney(outName, money);
accountDao.inMoney(inName, money);
}});
}
由此看见,编程式的事务管理在实际开发中相当繁琐需要在添加事务管理的代码处,编写事务管理的代码。在实际开发中很少很少使用该方式进行事务管理。 (2). 声明式事务管理
相对于编程式的事务管理方法,声明式的事务管理在实际开发中应用普遍。第一种使用代理的方式进行事务管理。该方式将目标类进行了增强,使得该类具备了事务管理的功能。实现这种方式只需要在applicationContext.xml文件里配置Service类的代理即可。代码如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的属性文件 -->
<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.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service层类 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao层类 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置业务层的代理类 -->
<bean id="accoutServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 配置目标对象 -->
<property name="target" ref="transactionManager" />
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 注入事务的属性 -->
<property name="transactionAttributes">
<props>
<!-- prop的格式:key属性填写的目标类里需要事务管理的方法,其value值有一下五中
* PROPAGATION : 事务的传播行为
* ISOLATION : 事务的隔离级别
* readOnly : 只读
* -Exception : 发生哪些异常回滚事务
* -Exception : 发生哪些异常不会滚事务
-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
这样目标类AccountServiceImpl的transfer方法就具备了事务管理的能力。这里需要注意的是在controller注入该Service类时,需要注入该类的代理对象accountServiceProxy。 第二种使用Spring中的切面原理配置事务管理,具体的配置内容如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的属性文件 -->
<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.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service层类 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao层类 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</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>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* cn.spring.transaction.demo1.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
</beans>
第三种是基于注解的事务管理配置,这种是项目中最常用的一种,具体配置如下<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
<!-- 引用外部的属性文件 -->
<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.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置Service层类 -->
<bean id="accoutService" class="cn.spring.transaction.demo1.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置Dao层类 -->
<bean id="accountDao" class="cn.spring.transaction.demo1.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
这样配置完成后,只需要在需要事务管理的类上加上@Transactional注解即可。