事务
什么是事务?事务是指的是逻辑上的一组操作,这次操作要么全部成功,要么全部失败。
事务的特性:
原子性是指事务是一个不可分割的工作单位,事务中的操作,要么全都发生,要么都不发生。
一致性指事务前后数据的完整性必须保持一致。
隔离性指多个用户并发访问数据库时,一个用户的事物不能被其他用户的事物所干扰,多个并发事务之间数据要相互隔离。
持久性是指一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。
接口介绍
spring事务管理高层抽象主要包括3个接口:
PlatformTransactionManager 平台事务管理器;
TransactionDefinition 事务定义信息(隔离、传播、超时、只读)
TransactionStatus 事务具体运行状态
spring为不同持久化框架提供了不同的PlatformTransactionManager接口实现。
TransactionDefinition定义事务隔离级别,spring默认隔离级别和使用的数据库一样。mysql默认repeatable_read,oracle默认read_commited。
TransactionDefinition定义事务传播行为,事务传播行为解决业务层方法之间的相互调用的问题。事务七种传播行为,分为三类:在同一事务、不在同一事务、事务嵌套(若bbb方法失败,aaa回滚到事务保存点或全回滚)。
转账环境的搭建
spring支持两种方式事务管理。
1、编程式的事务管理:
在实际应用中很少很用,通过TransactionTemplate手动管理事务。
2、使用XML配置声明式事务:
开发中推荐使用(代码侵入性小),spring的声明式事务是通过AOP实现的。
创建数据表account
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', 'aaa', '1000'); INSERT INTO `account` VALUES ('2', 'bbb', '1000'); INSERT INTO `account` VALUES ('3', 'ccc', '1000');
项目需要jar包、配置文件:
jar包:
com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
com.springsource.org.apache.log4j-1.2.15.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
mysql-connector-java-5.0.8-bin.jar
spring-aop-3.2.0.RELEASE.jar
spring-aspects-3.2.0.RELEASE.jar
spring-beans-3.2.0.RELEASE.jar
spring-context-3.2.0.RELEASE.jar
spring-core-3.2.0.RELEASE.jar
spring-expression-3.2.0.RELEASE.jar
spring-jdbc-3.2.0.RELEASE.jar
spring-test-3.2.0.RELEASE.jar
spring-tx-3.2.0.RELEASE.jar
核心配置文件:applicationContext.xml, log4j.properties, jdbc.properties
jdbc.properties:
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://192.168.216.101\:3306/spring_transaction
jdbc.username=user
jdbc.password=123456
jdbc.url=jdbc\:mysql\://192.168.216.101\:3306/spring_transaction
jdbc.username=user
jdbc.password=123456
log4j.properties:
log4j.rootLogger=INFO,logfile,stdout
#log4j.logger.org.springframework.web.servlet=INFO,db
#log4j.logger.org.springframework.beans.factory.xml=INFO
#log4j.logger.com.neam.stum.user=INFO,db
#log4j.appender.stdout=org.apache.log4j.ConsoleAppender
#log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %p [%c] %X{remoteAddr} %X{remotePort} %X{remoteHost} %X{remoteUser} operator\:[\u59D3\u540D\:%X{userName} \u5DE5\u53F7\:%X{userId}] message\:<%m>%n
#write log into file
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.Threshold=warn
log4j.appender.logfile.File=${webapp.root}\\logs\\Spring-transaction.log
log4j.appender.logfile.DatePattern=.yyyy-MM-dd
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=[SpringTransaction] %d{yyyy-MM-dd HH\:mm\:ss} %X{remoteAddr} %X{remotePort} %m %n
#display in console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Threshold=info
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[SpringTransaction] %d{yyyy-MM-dd HH\:mm\:ss} %X{remoteAddr} %X{remotePort} %m %n
建maven项目。
建包cn.it.spring.demo1,下面的文件除了配置文件全建在这个包里。
面向接口开发,所以创建接口:AccountService。
/**
* 转账安全的业务层接口
*/
public interface AccountService { /** * @param out :转出账号 * @param in :转入账号 * @param money :转账金额 */ public void transfer(String out,String in,Double money); }
实现类AccountServiceImpl.java:
//注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。 private AccountDao accountDao; /** * @param out :转出账号 * @param in :转入账号 * @param money :转账金额 */ @Override public void transfer(String out, String in, final Double money) { accountDao.outMoney(out, money); //int i = 1/0; accountDao.inMoney(in, money); } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
AccountDao.java:
public interface AccountDao { /** * @param out :转出账号 * @param money :转账金额 */ public void outMoney(String out,Double money); /** * * @param in :转入账号 * @param money :转账金额 */ public void inMoney(String in,Double money); }
实现类AccountDaoImpl.java:
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {//继承JdbcDaoSupport就可以使用jdbc的模版了。继承了之后就可以查看相应的源代码。找到spring-jdbc-3.2.0.RELEASE.sources.jar(在spring-framework-3.2.0.RELEASE官方JAR包),里面可以看到提供了一个jdbc模板,下面有setJdbcTemplate,所在直接向dao注入模板就可以了。还有一种办法,里面有setDataSource设置了连接池,所以只要给dao连接池的话,它就会创建一个模板。所以可以直接向dao里注入连接池。因为只要给连接池,它就可以帮我们创建jdbc的模板。下面的配置有向dao注入连接池,只要注入连接池就有jdbc模板。 /** * @param out :转出账号 * @param money :转账金额 */ @Override public void outMoney(String out, Double money) { String sql = "update account set money = money-? where name = ?"; this.getJdbcTemplate().update(sql, money, out); //使用这个模板。后面两个是可变参数。 } /** * @param in :转入账号 * @param money :转账金额 */ @Override public void inMoney(String in, Double money) { String sql = "update account set money = money+? where name = ?"; this.getJdbcTemplate().update(sql,money,in); } }
上面这些类要用spring管理,所以要配置:
applicationContext:
<?xml version="1.0" encoding="UTF-8"?> <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" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd"> </beans>
下面的代码写在beans里。
<!-- 引入外部的属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 配置c3p0连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClass}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <!-- 配置业务层类 --> <bean id="accountService" class="com.zs.spring.demo1.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean> <!-- 配置DAO类(简化,会自动配置JdbcTemplate) --> <bean id="accountDao" class="com.zs.spring.demo1.AccountDaoImpl"> <property name="dataSource" ref="dataSource" /> //在dao里注入连接池。 </bean>
测试类 TransactionTest :
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext1.xml") public class TransactionTest { @Resource(name="accountService")//name就是applicationContext里配置的名字。 private AccountService accountService; @Test public void demo1(){ accountService.transfer("aaa", "bbb", 200d); } }
这时环境就搭好了,进行测试查看数据库,然后把AccountServiceImpl两句转账语句间加int i = 8/0进行测试。发现有事务问题。
编程式事务管理
spring为了简化编写代码,它给我们提供了事务管理的模板,叫TransactionTemplate,需要在哪个地方使用事务,就在哪个地方注入这个模板。
打开spring的API,找到PlatformTransactionManager接口,实现类有很多,比如HibernateTransactionManager, DataSourceTransactionManager等,这里使用第二个,首先配置事务管理器,在applicationContext.xml里配置:
<!-- ==================================1.编程式的事务管理。因使用事务的时候要自己手动写代码。哪个类需要事务管理呢,业务层的类,所以在业务层的类中进行模板的注入。=============================================== --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 使用jdbc的方式进行事务管理,所以bean里需要配置属性。因为jdbc进行事务管理是获得到连接对象connection.setAutoCommit(false);就不自动提交了,执行完代码的commit,如果发生异常就rollback。谁能获取到连接呢,事务连接池,所以在事务管理器的模板中注入连接池对象,因为事务管理模板是真正进行事务管理的类,连接池可以获得到连接对象: --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 但是直接进行事务管理的话代码比较繁琐,所以spring为了简化我们事务管理的代码,它给我们提供了事务管理的模板,叫:TransactionTemplate所以定义一个事务管理模板: --> <!-- 配置事务管理的模板:Spring为了简化事务管理的代码而提供的类。就像它还给我们提供过jdbc的模板。 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <!-- 这个模板是简化事务管理的,真正进行事务管理的,是transactionManager,所以注入: --> <property name="transactionManager" ref="transactionManager" /> </bean>
业务类AccountServiceImpl中注入,如果要注入的话,还有提供set方法:
//注入事务管理的模板 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; }
这里提供set方法了,配置文件中需要注入事务管理的模板,哪个类需要事务管理,就在哪个类中注入模板类:
<bean id="accountService" class="cn.it.spring.demo1.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> <!-- 注入事务管理的模板 --> <property name="transactionTemplate" ref="transactionTemplate" /> </bean>
AccountServiceImpl中的那两条转账语句就在一个事务里的,使用模板中的方法:
public void transfer(final String out, final String in, final Double money) { // accountDao.outMoney(out, money); // int i = 1/0; // accountDao.inMoney(in, money); transactionTemplate.execute(new TransactionCallbackWithoutResult() {//调用这个方法时看到它的参数要传一个TransactionCallback的接口,可以自己写一个类实现它,也可以直接使用匿名内部类的形式,new完之后里面有个方法doInTransactionWithoutResult。 @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {//方法里面要传一个对象,就是事务的状态,匿名内部类中使用了外部类中的变量,所以要把外部变量变成final accountDao.outMoney(out, money); int i = 1/0; accountDao.inMoney(in, money); } }); }
测试,发现事务回滚成功。
声明式事务管理方式一:基于TransactionProxyFactoryBean的方式
编程式事务管理需要手动改service代码,开发中不好用。
声明式事务管理基于AOP的思想完成的,也就是在转账的两句代码之前做一些事,之后做一些事。这就是AOP思想。
建包cn.it.spring.demo2,把上面的代码复制并恢复。
新建配置文件applicationContext2.xml。配置也还原。
因为基于aop,所以这里要引入aop包(和spring整合)和aop联盟(aop是一个联盟提出的)包:com.springsource.org.aopalliance-1.0.0.jar, spring-aop-3.2.0.RELEASE.jar
applicationContext2.xml:
<!-- 配置业务层类 --> <bean id="accountService" class="cn.it.spring.demo2.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean> <!-- 配置DAO类(简化,会自动配置JdbcTemplate) --> <bean id="accountDao" class="cn.it.spring.demo2.AccountDaoImpl"> <property name="dataSource" ref="dataSource" /> </bean> <!-- ==================================2.使用XML配置声明式的事务管理(原始方式)=============================================== --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置业务层的代理 --> <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <!-- 配置目标对象 --> <property name="target" ref="accountService" /> <!-- 注入事务管理器 --> <property name="transactionManager" ref="transactionManager"></property> <!-- 注入事务的属性 --> <property name="transactionAttributes"> <props> <!-- prop的格式: * PROPAGATION :事务的传播行为 * ISOTATION :事务的隔离级别 * readOnly :只读 * -EXCEPTION :发生哪些异常回滚事务 * +EXCEPTION :发生哪些异常不回滚事务 --> <prop key="transfer">PROPAGATION_REQUIRED</prop> <!-- <prop key="transfer">PROPAGATION_REQUIRED,readOnly</prop> --> <!-- <prop key="transfer">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop> --> </props> </property> </bean>
TransactionTest:
// @Resource(name = "accountService") // name就是applicationContext里配置的名字。 @Resource(name = "accountServiceProxy")//这里要用代理。 private AccountService accountService; @Test public void demo1() { accountService.transfer("aaa", "bbb", 200d); }
AccountServiceImpl:
// 注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。 private AccountDao accountDao; public void transfer(final String out, final String in, final Double money) { accountDao.outMoney(out, money); int i = 1/0; accountDao.inMoney(in, money); } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
声明式事务管理方式二:基于AspectJ的XML方式
上面的原始方式不经常使用,因为需要为每一个进行事务管理的类都需要配置一个transactionProxyFactoryBean。只能对一个目标进行增强。
所以要用另一种AspectJ的XML方式:
aspectJ是为是简化AOP编程,开源的第三方AOP开发框架。
aspectJ相应开发包:com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar(aspectJ包),spring-aspects-3.2.0.RELEASE.jar(spring整合aspectJ的包)。
建包cn.it.spring.demo3
复制applicationContext3.xml:
<!-- 配置业务层类 --> <bean id="accountService" class="cn.it.spring.demo3.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean> <!-- 配置DAO类(简化,会自动配置JdbcTemplate) --> <bean id="accountDao" class="cn.it.spring.demo3.AccountDaoImpl"> <property name="dataSource" ref="dataSource" /> </bean> <!-- ==================================3.使用XML配置声明式的事务管理,基于tx/aop=============================================== --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置事务的通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- propagation :事务传播行为 isolation :事务的隔离级别 read-only :只读 rollback-for:发生哪些异常回滚 no-rollback-for :发生哪些异常不回滚 timeout :过期信息 --> <tx:method name="transfer" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置切面 --> <aop:config> <!-- 配置切入点 --> <aop:pointcut expression="execution(* cn.it.spring.demo3.AccountService+.*(..))" id="pointcut1"/> <!-- 配置切面 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/> </aop:config>
不管用哪种方式,事务管理器是必须的。
这种代理方式是自动代理,就是在类的生成过程中,类的本身就是一个代理对象。而不需要注入代理对象。因为在产生的过程中就已经对这个类增强了。
TransactionTest:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext3.xml") public class TransactionTest { @Resource(name = "accountService") // name就是applicationContext里配置的名字。 private AccountService accountService; @Test public void demo1() { accountService.transfer("aaa", "bbb", 200d); } }
声明式事务管理方式三:基于注解的方式
建包cn.it.spring.demo4
复制applicationContext4.xml:
也要配事务管理器,因为它才是真正事务管理的类。里面注入连接池。开启注解事务后就能使用注解了。注解驱动标签,标签里配置事务管理器对象。然后在类上添加注解。注解里也有属性,像隔离级别,传播行为,只读,等等。
<!-- 配置业务层类 --> <bean id="accountService" class="cn.it.spring.demo4.AccountServiceImpl"> <property name="accountDao" ref="accountDao" /> </bean> <!-- 配置DAO类(简化,会自动配置JdbcTemplate) --> <bean id="accountDao" class="cn.it.spring.demo4.AccountDaoImpl"> <property name="dataSource" ref="dataSource" /> </bean> <!-- ==================================4.使用注解配置声明式事务============================================ --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 开启注解事务 --> <tx:annotation-driven transaction-manager="transactionManager"/>
AccountServiceImpl
/** *@Transactional中的的属性 *propagation :事务的传播行为 *isolation :事务的隔离级别 *readOnly :只读 *rollbackFor :发生哪些异常回滚 *noRollbackFor :发生哪些异常不回滚 *rollbackForClassName 根据异常类名回滚 */ //@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false) @Transactional public class AccountServiceImpl implements AccountService { // 注入转账的DAO,因为要调用dao,所以要注入dao。提供set方法进行注入。在下面有set方法。而且在applicationContext.xml里的要配置注入。spring和junit4的整合包有了,所以在测试类里引入applicationContext就好了。 private AccountDao accountDao; /** * @param out * :转出账号 * @param in * :转入账号 * @param money * :转账金额 */ public void transfer(final String out, final String in, final Double money) { accountDao.outMoney(out, money); // int i = 1/0; accountDao.inMoney(in, money); } public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } }
总结
Spring将事务管理分成了两类:
* 编程式事务管理
* 手动编写代码进行事务管理.(很少使用)
* 声明式事务管理:
* 基于TransactionProxyFactoryBean的方式.(很少使用)
* 需要为每个进行事务管理的类,配置一个TransactionProxyFactoryBean进行增强.
* 基于AspectJ的XML方式.(经常使用)
* 一旦配置好之后,类上不需要添加任何东西
* 基于注解方式.(经常使用)
* 配置简单,需要在业务层上添加一个@Transactional的注解.