转账案例使用了Spring事务管理,用两种方式实现:编程式事务管理和声明式事物管理。
其中,编程式事务管理是一种手动修改代码的方式,比较麻烦,在开发过程中很少使用;声明式事务管理有三种方法实现,分别是TransactionProxyFactoryBean的代理方式、基于AspectJ的xml配置方式和基于注解的声明方式,后两种在开发应用中常常出现。
源码连接:spring_transaction
1 一般Spring案例的创建
(源码见spring_transaction下的demo)
1.1创建数据库spring_transaction
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 DEFAULTCHARSET=utf8; INSERT INTO `account` VALUES ('1', 'aaa','1000'); INSERT INTO `account` VALUES ('2', 'bbb','1000'); INSERT INTO `account` VALUES ('3', 'ccc','1000');
1.2 MyEclipse上创建项目spring_transaction
1.3、引入Jar包
Spring开发最基本的6个包:
- Com.springsource.org.apache.commons.logging-1.1.1.jar
- Com.springsource.org.apache.log4j-1.2.15.jar
- Apring-beans-3.2.2.Release.jar
- Spring-context-3.2.0.Release.jar
- Spring-core-3.2.0.Release.jar
- Spring-expression-3.2.0.Release.jar
连接数据库、整合单元测试的包:
- Junit4.jar
- spring-jdbc-3.2.0.RELEASE.jar
- spring-test-3.2.0.RELEASE.jar
- spring-tx-3.2.0.RELEASE.jar
1.4 在src目录下编写配置配置文件
1.4.1 日志记录 Log4j.properties
# $Id: Action.java 5022962007-02-01 17:33:39Z niallp $ # # Licensed to the ApacheSoftware Foundation (ASF) under one # or more contributor licenseagreements. See the NOTICE file # distributed with this work foradditional information # regarding copyrightownership. The ASF licenses this file # to you under the ApacheLicense, Version 2.0 (the # "License"); you may notuse this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable lawor agreed to in writing, # software distributed under theLicense is distributed on an # "AS IS" BASIS, WITHOUTWARRANTIES OR CONDITIONS OF ANY # KIND, either express orimplied. See the License for the # specific language governingpermissions and limitations # under the License. log4j.rootLogger = WARN, stdout log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Threshold = WARN log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
1.4.2 配置数据库连接配置文件jdbc.properties,交代清楚数据库的驱动类、数据库连接地址、账户名和密码。
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring_transaction
jdbc.username=root
jdbc.password=root
1.4.3 核心配置文件 applicationContext.xml
Spring一般的核心配置文件分为几个部分:
第一部分:是spring开发最全的空间约束条件。
第二部分:引入外部属性文件jdbc.properties,方便连接数据库。
第三部分:配置c3p0连接池,连接jdbc.properties的四个属性,用于框架配置文件连接数据库。
第四部分:配置业务层类,同时注入属性Dao方便自动匹配,用于完成操作;并注入事务管理模板transactionTemplate,用于后面的事物管理。
第五部分:配置Dao层的类,同时注入连接池,方便连接池连接数据库得到的信息注入到对应的Dao。
<?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" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 上文最Spring开发最全的约束 --> <!-- 引入外部属性文件--> <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="cn.terence.spring.demo1.AccountServiceImpl"> <property name="accountDao"ref="accountDao"/> <!-- 注入事务管理模板 --> <property name="transactionTemplate"ref="transactionTemplate"/> </bean> <!-- 配置Dao层的类 --> <bean id="accountDao" class="cn.terence.spring.demo1.AccountDaoImpl"> <!-- 注入连接池,可以利用jdbc的jar包里面的连接池创建模板 --> <property name="dataSource"ref="dataSource"/> </bean> </beans>
1.5 编写类
1.5.1编写DAO,注入JdbcTemplate
/** * 转账案例Dao层实现类 * @author Terence *继承JdbcDaoSupport模板给Dao层实现类 */ public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { public void inMoney(String out, Double money) { String sql="update account setmoney=money-? where name=?"; this.getJdbcTemplate().update(sql,money,out); } public void outMoney(String in, Double money) { String sql="update account setmoney=money+? where name=?"; this.getJdbcTemplate().update(sql,money,in); } }
1.5.2 编写Service,注入DAO
public class AccountServiceImpl implements AccountService { //注入转账的Dao private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } /** * 一般的执行操作情况,没有添加事务管理 * 将操作绑定在一起,如果遇到异常,则发生事务回滚 * final型变量,内部使用 * @param out * @param in * @param money */ public void transfer(String out,String in,Double money) { accountDao.outMoney(out, money); //int i=1/0; accountDao.inMoney(in, money); } }
1.5.3 编写测试类springDemo
/** * @author Terence */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo { @Resource(name="accountService") private AccountService accountService; /* * 转账案例测试 */ @Test public void demo() { accountService.transfer("aaa", "bbb", 300d); }
测试执行类,通过@RunWith 和@Test加载测试类和测试方法,通过@ContextConfiguration加载核心配置文件,核心配置文件在空间约束下,加载数据库连接文件jdbc.properties,通过连接池匹配数据库连接文件里面的属性连接数据库;通过@Resource利用核心配置文件,自动注入Dao,并获取service对象accountService;然后通过测试demo()方法,测试Service对象的transfer()方法。
1.6 没有spring事务管理的执行情况
测试这个spring框架下的一般案例,执行demo()方法的transfer操作:在业务层,转账有两个操作,转出和转入,一般情况下先转出accountDao.outMoney(out, money),后转入accountDao.inMoney(in, money),这样数据库中的数据是没有什么错误的,但是如果在转出和转入操作之间需要加入一些其他的业务操作或执行,那么这中间的就可能出现错误抛出异常,导致程序停在中间,不能继续执行转入操作,数据库里的数据出现了不一致的情况 ,比如1除以0的分母为0的异常。
此时,就需要用Spring框架下的事物对其进行管理,通过事物管理器定义其传播行为、隔离级别等,控制转账案例的转出和转入两种操作,可以将事物回滚到转出操作之前的状态,保持数据库里的数据一致。
下面用两种事务管理方法来进行spring事务管理。
2 编程式事务管理-很少使用
通过编程式事物管理,手动修改代码,可以将转账案例的两种操作和中间的其他操作放在spring事物管理模板中,放在同一个事务中,一旦出现异常,则回滚到事物执行的最初状态,以此保证数据的完整性、一致性。
(源码见spring_transaction下的demo1)
2.1 配置事务管理器,定义事务管理模板
在1.4.3 核心配置文件applicationContext.xml里配置事务管理器transactionManager,并定义事务管理模板transactionTemplate,将事物管理器transactionManager注入到事物管理模板,方便将那些操作放在事务管理模板中。
<!-- 配置事物管理器 --> <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"></property> </bean>
2.2 业务层注入编程式事务管理模板
在AccountService中注入TransactionTemplate事物模板(TransactionTemplate依赖DataSourceTransactionManager,DataSourceTransactionmanager依赖DataSource构造)
public class AccountServiceImpl implements AccountService { //注入转账的Dao private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } //注入事务管理模板 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplatetransactionTemplate) { this.transactionTemplate = transactionTemplate; } /** * 编程式事务管理 * 将操作绑定在一起,如果遇到异常,则发生事务回滚 * final型变量,内部使用 * @param out * @param in * @param money */ public void transfer(final String out,final String in,final Double money) { //在事务模板中执行操作 transactionTemplate.execute(new TransactionCallbackWithoutResult(){ @Override protected void doInTransactionWithoutResult(TransactionStatustransactionstatus){ accountDao.outMoney(out, money); int i=1/0; accountDao.inMoney(in, money); } }); } }
测试类不变,和1.5.3的测试类一样,执行测试类,通过嘿咻你配置文件加载Service对象,执行的Service对象的转账操作被放在事务管理模板当中,如果出现异常,则回滚到事物的起初装态。
编程式事务管理需要手动改写代码,不便操作,在开发过程中不常用,常常用到的另一种事务管理方式:声明式事务管理。
3 声明式事务管理
声明式事务管理是基于AOP思想完成的,类似与在给业务层加入切面,对事务操作前后进行一定的事务管理控制。
3.1 声明式事务管理实现方式一:基于TransactionProxyFactoryBean的方式--很少使用
这种方式很少使用,因为为每个进行事务管理的类配置一个TransactionProxyFactoryBean进行增强,多个类需要配置多个增强类,这样导致配置和管理太麻烦,所以很少使用。
(源码见spring_transaction下的demo2)
(1)引入jar包:
- spring-aop-3.2.0.RELEASE.jar
- com.springsource.org.aopalliance-1.0.0.jar
(2)配置核心配置文件applicationContext.xml
在1.4.3 核心配置文件applicationContext.xml里配置事务管理器,并配置业务层增强类,即业务层代理类:
<!-- 配置事物管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"ref="dataSource"></property> </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:类中的方法名称 PROPAGATION:事务的传播行为 ISOLATION:事务的隔离级别 readOnly:只读(不可以进行修改操作) -Exception:发生哪些异常回滚事务 +Exception:发生哪些异常不回滚事务 <propkey="insert*">PROPAGATION_REQUIRED</prop> <propkey="update*">PROPAGATION_REQUIRED</prop> <propkey="*">PROPAGATION_REQUIRED,readOnly</prop> --> <!-- PROPAGATION_REQUIRED:事务传播行为,表示将两种操作放在同一个事务中。 -java.lang.ArithmeticException:出现此异常发生回滚 --> <prop key="transfer">PROPAGATION_REQUIRED,-java.lang.ArithmeticException</prop> </props> </property> </bean>
(3) Dao类、Service类和测试类demo2和1.5节的一般转账案例的类一样。
通过配置Service代理类来实现事务管理,可以将两种操作绑定在同一个事务当中,如果出现异常,则可以回滚到事物执行起初装态。也可以通过属性-Exception或者+Exception设置指定哪些异常回滚,哪些异常不会滚。
3.2 声明式事务管理实现方式二:基于AspectJ的XML配置的文件--经常使用
(源码见spring_transaction下的demo3)
这种基于AspectJ的切面事务管理方式避免了为每个业务类配置要给加强的代理类。
(1)引入jar包
- spring-aspects-3.2.0.RELEASE.jar
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
(2)引入tx/aop空间约束
(3)applicationContext3.xml配置
在1.4.3 核心配置文件applicationContext.xml里配置事务管理器、增强类、aop相应配置(事务通知和事务切面)
<!-- 1.配置事物管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事务通知:(事务的增强) -->
<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" isolation="DEFAULT"read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 3.配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(*cn.terence.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice"pointcut-ref="pointcut1"/> </aop:config>
(4)Dao类、Service类和测试类demo2和1.5节的一般转账案例的类一样。
通过配置Service代理类来实现事务管理,可以将两种操作绑定在同一个事务当中,如果出现异常,则可以回滚到事物执行起初装态。也可以通过属性rollback-for或者no-rollback-for设置指定哪些异常回滚,哪些异常不会滚。
3.3 声明式事务管理实现方式三:基于注解的配置方式(经常使用)
(源码见spring_transaction下的demo4)
(1)在业务层实现类上加注解@Transactional,Service类和测试类不变,和1.5节中的Service类和测试类一样
/** * @Transactional注解中的属性 * propagation:事务的传播行为 * isolation:事务的隔离级别 * readOnly:只读 * rollbackFor:发生哪些异常事务回滚 * noRollbackFor:发生哪些异常事务不回滚 * */ @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false) public class AccountServiceImpl implements AccountService { //注入转账的Dao private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } /** * @param out * @param in * @param money */ public void transfer(String out,String in,Double money) { accountDao.outMoney(out, money); int i=1/0; accountDao.inMoney(in, money); } }
(2)配置核心配置文件applicationContext.xml
在1.4.3 核心配置文件applicationContext.xml里注入事务管理器和开启注解事务实现注解驱动。
<!-- 1.配置事物管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"ref="dataSource"></property> </bean> <!-- 2.开启注解事务 ,实现注解驱动--> <tx:annotation-driven transaction-manager="transactionManager"/>
(3)执行测试类
执行测试类可以实现事务的回滚,保证数据的完整一致。这种方式在开发过程中常常用到,配置简单,不用修改代码。
附:
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。