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注解,经常使用。