1.事务介绍java
事务(Transaction):访问并能更新数据库中数据项的一个程序执行单元。mysql
事务是一系列的动做,它们综合在一块儿才是一个完整的工做单元,这些动做必需要么所有完成,要么什么都不作,若是有一个失败了的话,那么事务就会回滚(RollBack)到最开始的状态,在企业级的应用程序中,事务管理是必不可少的,用来确保数据的完整性和一致性。spring
事务的特性(ACID)sql
举个例子,也是后面实现过程当中会一直用的例子:
1.Tom和Marry在某银行都有帐户,且他们每一个人的帐户中都有1000元数据库
2.Tom要向Marry汇款100元apache
那么这个汇款的执行能够分红下面几步:编程
1.检测Tom的帐户里面有没有100块钱,若是有则容许汇款,若是没有则不容许汇款后端
2.Tom的银行帐户,帐户余额减去100元服务器
3.Marry的银行帐户,帐户余额加上100元并发
那么问题来了,在2步和3步之间,可能会产生故障或者异常,最low的例子是2步完成以后,银行的服务器断电了。那么这个时候会不会出现Tom的银行帐户少了100元,而Marry的银行帐户却没有增长100元呢?
这个问题能够先说下:若是没有使用事务管理,这种状况确实是存在的。
那么如何模拟服务器断电这种异常呢:那就是在2步和3步之间加入一个异常(除零异常就能够)
Spring的事务管理的核心接口:
接着打开spring-tx包,能够继续查看org-springframework.transaction包
TransactionDefinition PlatformTransactionManager TransactionStatus是Spring事务管理的三个顶级接口,下面咱们用图来解释下这三个接口之间的关系:
PlatformTransaction事务管理器
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上图所示Spring并非直接管理事务,经过PlatformTransaction这个接口,Spring为各个平台如JDBC,Hibernate等提供对应的事务管理。也就是将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务实现。
进入到PlatformTransactionManager接口,查看源码:
也就是说Spring事务管理是为不一样的事务API提供统一的编程模型,具体的事务管理机制由对应的各个平台去实现:
有jdbc,orm平台:
TransactionDefinition基本事务属性定义
事务属性能够理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性:传播行为,隔离规则,是否只读,事务超时,回滚规则。
TransactionDefinition接口定义的方法以下:
事务的传播行为和隔离级别的具体定义:
// Compiled from TransactionDefinition.java (version 1.6 : 50.0, no super bit) public abstract interface org.springframework.transaction.TransactionDefinition { // Field descriptor #4 I public static final int PROPAGATION_REQUIRED = 0; // Field descriptor #4 I public static final int PROPAGATION_SUPPORTS = 1; // Field descriptor #4 I public static final int PROPAGATION_MANDATORY = 2; // Field descriptor #4 I public static final int PROPAGATION_REQUIRES_NEW = 3; // Field descriptor #4 I public static final int PROPAGATION_NOT_SUPPORTED = 4; // Field descriptor #4 I public static final int PROPAGATION_NEVER = 5; // Field descriptor #4 I public static final int PROPAGATION_NESTED = 6; // Field descriptor #4 I public static final int ISOLATION_DEFAULT = -1; // Field descriptor #4 I public static final int ISOLATION_READ_UNCOMMITTED = 1; // Field descriptor #4 I public static final int ISOLATION_READ_COMMITTED = 2; // Field descriptor #4 I public static final int ISOLATION_REPEATABLE_READ = 4; // Field descriptor #4 I public static final int ISOLATION_SERIALIZABLE = 8;
具体解释下传播行为:传播行为指的是当事务被另外一个事务调用时,必须指定事务如何传播的。可能这么说仍是有点不清晰,那么能够看具体定义的几种传播行为的具体含义
隔离级别:定义了一个事务可能受其余并发事务影响的程度
并发事务引发的问题:
不可重复的重点是“修改”,而幻读的重点是“新增”
在Spring的事务管理中,定义了以下隔离级别:
只读
这是事务的第三个特性,是否将事务设置为只读。数据库能够利用事务的只读属性来进行一些优化。经过将事务设置成只读,你就能够给数据库一个机会,让它应用它认为合适的优化措施。
事务超时
为了使应用更好的运行,事务不能运行太长时间,由于事务可能涉及对后端数据库的锁定,因此很长时间的事务会没必要要的占用数据库资源,事务超时就是事务的一个定时器,在特定的时间内若是事务没有执行完毕,那么就会自动回滚,而不是一直等待事务结束。
回滚规则
回滚规则定义了哪些异常会致使事务回滚而哪些异常不会致使回滚。默认状况下,事务遇到运行期异常时才会回滚,在遇到检查型异常时不会回滚。可是这些都是能够设置的。好比你能够设置检查型异常也进行事务回滚。
Spring编程式事务和声明式事务
编程式事务:所谓编程式事务就是指经过编码方式实现事务,容许用户在代码中精肯定义事务的边界。即相似JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务,推荐使用TransactionTemplate。
声明式事务管理:管理创建在AOP上,其本质是对方法先后进行拦截,而后再目标方法开始以前建立或加入一个事务,在执行完目标方法以后根据执行状况提交或回滚事务。声明式事务最大的优势就是不须要经过编程的凡是管理事务,这样就不须要在业务逻辑代码中掺琐事务管理的代码,只须要在配置文件中作相关的事务规则声明(或者基于注解@Transaction的形式)。
实验
不用事务实现转帐
数据库中有以下表:
package com.fpc.Dao; public interface AccountDao { /* * 汇款 * @param outer 汇款人 * @param money 汇款金额 * */ public void out( String outer , int money ); /* * 收款 * @param inner 收款人 * @param money 收款金额 * */ public void in( String inner , int money ); }
第二步:编写Dao层接口的实现:AccountDaoImpl:
package com.fpc.DaoImpl; import org.aspectj.weaver.patterns.ThisOrTargetAnnotationPointcut; import org.springframework.jdbc.core.support.JdbcDaoSupport; import com.fpc.Dao.AccountDao; public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao { @Override public void out(String outer, int money) { // TODO 自动生成的方法存根 this.getJdbcTemplate().update("update account set money = money - ? where username = ?",money,outer); } @Override public void in(String inner, int money) { // TODO 自动生成的方法存根 this.getJdbcTemplate().update("update account set money = money + ? where username = ?",money,inner); } }
第三步:实现Service层 IAccountService
package com.fpc.Service; public interface IAccountService { /* * 转帐 * @param outer 汇款人 * @param inner 收款人 * @param money 交易金额 * */ public void transfer( String outer , String inner , int money ); }
第四步:Service的具体实现 AccountServiceImpl
package com.fpc.ServiceImpl; import org.apache.shiro.authc.Account; import com.fpc.Dao.AccountDao; import com.fpc.Service.IAccountService; public class AccountServiceImpl implements IAccountService{ private AccountDao accounDao; public void setAccounDao(AccountDao accounDao) { this.accounDao = accounDao; } @Override public void transfer(String outer, String inner, int money) { // TODO 自动生成的方法存根 accounDao.out(outer, money); accounDao.in(inner, money); } }
第五步:配置applicationContext文件:
<bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl"> <property name="accounDao" ref="accountDao"></property> </bean>
第六步:编写单元测试类
package com.fpc.UnitTest; import org.apache.catalina.core.ApplicationContext; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.apple.eawt.Application; import com.fpc.Service.IAccountService; import com.fpc.ServiceImpl.AccountServiceImpl; public class TransactionTest { @Test public void TestNoTransaction(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); IAccountService accountService = (IAccountService) context.getBean("accountService"); //Tom 向 Marry转帐100元 accountService.transfer("Tom", "Marry", 100); } }
运行查看数据库中的结果:
可见正常状况下,转帐是能够成功的。
下面模拟异常状况:
那么这个时候咱们执行单元测试程序,很显然会报“除零异常”
此时再去查看数据库中该表:
咱们发现,Tom的帐户中再次减小了100元,而Marray的帐户中却没有增长100元。这在实际应用中确定是不被容许的。
那么怎么去解决这个问题呢?确定是引入事务管理
Dao层不变,咱们在Service层注入TransactionTemplate模板,由于是用模板来管理事务,因此模板须要注入事务管理器。DatasourceTransactionManager,而事务管理器说到底层是JDBC在管理,因此咱们须要在DatasourceTransactionManager中注入DataSource。
AccountServiceImpl:
package com.fpc.ServiceImpl; import org.apache.shiro.authc.Account; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.fpc.Dao.AccountDao; import com.fpc.Service.IAccountService; public class AccountServiceImpl implements IAccountService{ private AccountDao accounDao; private TransactionTemplate transactionTemplate; public void setAccounDao(AccountDao accounDao) { this.accounDao = accounDao; } public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } @Override public void transfer(String outer, String inner, int money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus arg0) { // TODO 自动生成的方法存根 accounDao.out(outer, money); int i = 1/0; accounDao.in(inner, money); } }); }
更改配置文件:
<!-- 事务管理,transaction manager,user JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl"> <property name="accounDao" ref="accountDao"></property> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean> <!-- 建立模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean> <!-- 配置事物管理器,管理器须要事务,事务从Connection得到,链接从链接池得到 --> <!-- transactionManager配置在上面 -->
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 800 | | 2 | Marry | 1100 | +----+----------+-------+
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 700 | | 2 | Marry | 1200 | +----+----------+-------+ 2 rows in set (0.00 sec)
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 700 | | 2 | Marry | 1200 | +----+----------+-------+ 2 rows in set (0.00 sec)
声明式事务处理实现(AOP)
DAO层和Service不须要改变,主要是applicationContext.xml文件发生了变化。<!-- 事务管理,transaction manager,user JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事务详情 :在AOP筛选基础上,好比对ABC三个肯定使用什么样的事物,例如AC读写,B只读等 <tx:attributes>用于配置事物详情(属性) <tx:method name=""/>详情具体配置 propagation:传播行为,REQUIRED:必须,REQUIRED_NEW:必须是新的,isolation:隔离级别 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!-- AOP切面编程,利用切入点表达式从目标类方法中,肯定加强的链接器,从而得到切入点 --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.fpc.ServiceImpl..*.*(..))"/> </aop:config> <bean id="accountDao" class="com.fpc.DaoImpl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="accountService" class="com.fpc.ServiceImpl.AccountServiceImpl"> <property name="accounDao" ref="accountDao"></property> <!-- <property name="transactionTemplate" ref="transactionTemplate"></property> --> </bean> <!-- 建立模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"></property> </bean> <!-- 配置事物管理器,管理器须要事务,事务从Connection得到,链接从链接池得到 --> <!-- transactionManager配置在上面 --> </beans>
package com.fpc.ServiceImpl; import org.apache.shiro.authc.Account; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import com.fpc.Dao.AccountDao; import com.fpc.Service.IAccountService; @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT) public class AccountServiceImpl implements IAccountService{ private AccountDao accounDao; public void setAccounDao(AccountDao accounDao) { this.accounDao = accounDao; } @Override public void transfer(String outer, String inner, int money) { accounDao.out(outer, money); int i = 1/0; accounDao.in(inner, money); } }
mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 700 | | 2 | Marry | 1200 | +----+----------+-------+ 2 rows in set (0.00 sec) mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 600 | | 2 | Marry | 1300 | +----+----------+-------+ 2 rows in set (0.00 sec) mysql> select * from account; +----+----------+-------+ | id | username | money | +----+----------+-------+ | 1 | Tom | 600 | | 2 | Marry | 1300 | +----+----------+-------+ 2 rows in set (0.00 sec)