Spring学习笔记-事务管理
Spring支持两种事务管理的方式:
编程式的事务管理,在实际应用中很少使用,通过TransactionTemplate手动管理事务。
声明式的事务管理,使用XML配置声明,开发中推荐使用(代码侵入性最小),Spring的声明式事务是通过AOP实现的。
什么是事务
事务指的是逻辑上的一组操作,这组操作要么全都成功,要么全都失败。最典型的例子就是银行转账的问题。
事务的特性:
原子性:事务是不可分割的工作单位
一致性:事务前后数据的完整性必须保持一致
隔离性:多个并发事务数据要相互隔离
持久性:事务一旦提交,它对数据库的改变是永久性的
Spring事务接口介绍
Spring事务管理高层抽象接口主要包括3个接口:
1、PlatformTransactionManager事务管理器
Spring为不同的持久层框架提供了不同的PlatformTransactionManager接口实现。如JDBC、Mybatis使用DataSourceTransactionManager,Hibernate用HibernateTransactionManager等。
2、TransactionDefinition事务定义信息
包含(隔离级别、传播行为、超时、是否只读)等一些信息。
若不考虑隔离性,将会造成脏读、不可重复读、幻读等。
事务的传播行为主要解决service层里方法的相互调用,解决事务是如何传递的。
3、TransactionStatus事务具体运行状态
事务隔离级别:
事务的传播行为:
测试环境搭建
applicationContext.xml
<context:annotation-config/>
<context:component-scan base-package="com"></context:component-scan>
<!--引入配置文件-->
<!--1.配置数据库相关参数–>-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--2.数据库连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--配置连接池属性-->
<property name="driverClass" value="${jdbc_driver}" />
<!-- 基本属性 url、user、password -->
<property name="jdbcUrl" value="${jdbc_url}" />
<property name="user" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
<!--c3p0私有属性-->
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<!--关闭连接后不自动commit-->
<property name="autoCommitOnClose" value="false"/>
<!--获取连接超时时间-->
<property name="checkoutTimeout" value="1000"/>
<!--当获取连接失败重试次数-->
<property name="acquireRetryAttempts" value="2"/>
</bean>
Dao层
@Repository
public interface AccountDao {
void outMoney(String out, Double money);
void inMoney(String in, Double money);
}
@Repository("AccountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Autowired
private ComboPooledDataSource dataSource;
/**解决
* java.lang.IllegalArgumentException:
* 'dataSource' or 'jdbcTemplate' is required
* 的问题。
* 继承了JdbcDaoSupport注入dataSource出现null的情况
*/
@PostConstruct
private void initialize() {
setDataSource(dataSource);
}
public void outMoney(String out, Double money) {
String sql = "update test set money = money - ? where name = ?";
this.getJdbcTemplate().update(sql, money, out);
}
public void inMoney(String in, Double money) {
String sql = "update test set money = money + ? where name = ?";
this.getJdbcTemplate().update(sql, money, in);
}
}
Service层
@Service
public interface AccountService {
void transfer(String out, String in, Double money);
}
@Service("AccountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out, String in, Double money) {
System.out.println(out+"向"+in+"转账"+money);
accountDao.outMoney(out,money);
//出现异常
//若没有事务处理,这时out账户钱转出,但是in账户钱未转入
int i = 1/0;
accountDao.inMoney(in,money);
}
}
Main.java
在未进行事务处理时,会出现aaa中钱减少,bbb中钱未增加的情况。
public class Main {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext(
"applicationContext.xml");
AccountService accountService = (AccountService) context.getBean("AccountService");
accountService.transfer("aaa","bbb",100.0);
}
}
编程式事务控制
Spring提供了事务管理的模板TransactionTemplate,在哪里使用事务就在哪里注入这个模板。
在applicationContext.xml文件中添加如下代码,配置事务管理器和模板。
<!--配置事务管理器-->
<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>
编写AccountServiceImpl2.java,进行编程式事务处理
@Service("AccountService2")
public class AccountServiceImpl2 implements AccountService {
@Autowired
private AccountDao accountDao;
//注入事务管理模板
@Autowired
private TransactionTemplate transactionTemplate;
//使用匿名内部类,传入的参数需要final
public void transfer(final String out, final String in, final Double money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//使用匿名内部类实现编程式事务的处理
System.out.println(out + "向" + in + "转账" + money);
accountDao.outMoney(out, money);
//出现异常
//若没有事务处理,这时out账户钱转出,但是in账户钱未转入
int i = 1 / 0;
accountDao.inMoney(in, money);
}
});
}
}
测试
AccountService accountService2 = (AccountService) context.getBean("AccountService2");
accountService2.transfer("aaa","bbb",100.0);
出现异常时不会出现aaa中钱减少而bbb中钱未增加的情况,出现异常进行了事务的一个回滚。
这种方式修改代码较多,不推荐这种方式,推荐使用下面的声明式事务的方式。
声明式事务控制
基于AOP思想进行处理。
方法一,基于TransactionProxyFactoryBean
配置applicationContext.xml,添加TransactionProxyFactoryBean的相关配置。
这里是通过代理实现功能的增强,基于AOP拦截规则。
这里说明一下prop的格式 :
PROPAGATION,ISOLATION,readOnly,-Exception,+Exception分别代表
传播行为(7种),隔离级别,只读事务,发生这些异常回滚,发生这些异常提交事务。
<!--声明式事务-->
<!--方法一:基于TransactionProxyFactoryBean-->
<!--配置业务层代理-->
<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="insert*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<!--这里配置对应的方法进行AOP拦截,*拦截所有,这里测试transfer方法-->
<!--设置隔离级别-->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
AccountServiceImpl不用修改任何代码,在测试类中获得的bean是代理类的对象。
AccountService accountService3 = (AccountService) context.getBean("accountServiceProxy");
accountService3.transfer("aaa","bbb",100.0);
方法二,使用XML配置声明事务(原始方式)
配置applicationContext.xml,添加声明事务。
<!--方法二:使用XML配置声明事务(原始方式)-->
<!--配置事务的通知:(事务的增强)-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--下面属性分别为传播行为,隔离级别,只读,发生这些异常回滚,发生这些异常提交事务-->
<tx:method name="transfer"
propagation="REQUIRED"
isolation="DEFAULT"
read-only="false"
rollback-for=""
no-rollback-for=""/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!--切入点配置-->
<aop:pointcut id="pointcut1" expression="execution(* com.service.AccountService+.*(..))"/>
<!--配置切面-->
<!--在pointcut1这个切入点上使用txAdvice这个增强-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
</aop:config>
也是基于AOP拦截规则,其他不用修改任何代码,在测试时直接调用原来的方法。
AccountService accountService = (AccountService) context.getBean("AccountService");
accountService.transfer("aaa","bbb",100.0);
方法三,使用注解
配置applicationContext.xml,添加基于注解的声明事务
<!--方法三:配置基于注解的声明式事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>
然后在需要进行事务处理的类或方法上使用@Transactional注解
//同样有传播行为,隔离级别等属性进行设置
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT)
public void transfer(String out, String in, Double money) {
System.out.println(out+"向"+in+"转账"+money);
accountDao.outMoney(out,money);
//出现异常
//若没有事务处理,这时out账户钱转出,但是in账户钱未转入
int i = 1/0;
accountDao.inMoney(in,money);
}
总结
编程式事务管理:手动编写代码,很少使用
声明式事务管理:
l 方法一:基于TransactionProxyFactoryBean方式,需要为每个进行事务管理的类配置一个TransactionProxyFactoryBean进行增强,很少使用。
l 方法二:基于AspectJ的XML方式,一旦配置好,类上不需要添加任何东西,经常使用。
l 方法三:基于注解方式,配置简单,需要在业务层上添加@Transactional注解,经常使用。