Spring-事务管理(Transaction)

1.事务介绍java

事务(Transaction):访问并能更新数据库中数据项的一个程序执行单元。mysql

事务是一系列的动做,它们综合在一块儿才是一个完整的工做单元,这些动做必需要么所有完成,要么什么都不作,若是有一个失败了的话,那么事务就会回滚(RollBack)到最开始的状态,在企业级的应用程序中,事务管理是必不可少的,用来确保数据的完整性和一致性。spring

事务的特性(ACID)sql

  1. 原子性(Atomicity):事务是一个原子操做,事务的原子性保证这些动做要么所有都作,要么什么都不作。
  2. 一致性(Consistency):一旦事务完成(不管是成功仍是失败),事务必须始终保持系统处于一致的状态,无论在任何给定的时间并发事务由多少。也便是说,若是事务是并发多个,系统也必须入串行事务同样操做,其主要特征是保护性和不可变性,以转帐案例为例子,假设有5个帐户,每一个帐户余额是100,那么五个帐户的总额是500,无论并发是多少,五个帐户之间怎么频繁进行转帐,总额都会保证是500,这就是保护性和一致性。
  3. 隔离性(Isolation):可能有许多事务会同时处理相同的数据,所以每一个事务都应该与其余事务隔离开来,防止数据损坏。
  4. 持久性(Durability):事务一旦完成,事务的结果必须被持久化到存储器中。

举个例子,也是后面实现过程当中会一直用的例子:
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接口,查看源码:

  1. TransactionStatus getTransaction(TransactionDefinition definition) ,事务管理器经过TransactionDefinition,得到事务状态,从而管理事务
  2. void commit( TransactionStatus status)根据状态提交事务
  3. void rollback( TransactionStatus status)根据状态回滚事务

也就是说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;

具体解释下传播行为:传播行为指的是当事务被另外一个事务调用时,必须指定事务如何传播的。可能这么说仍是有点不清晰,那么能够看具体定义的几种传播行为的具体含义

  1. PROPAGATION_REQUIRED:required,必须,默认值,A若是有事务,B将使用该事务;若是A没有事务,B将建立一个新事务。
  2. PROPAGATION_SUPPORTS:supports,支持,A若是有事务,B将使用该事务;若是A没有事务,B将以非事务执行。
  3. PROPAGATION_MANDATORY:mandatory,强制,A若是有事务,B使用该事物;若是A没有事务,B将抛出异常。
  4. PROPAGATION_REQUIRES_NEW:requires_new,必须新的,A若是有事务,将A的事务挂起,B建立一个新的事务;若是A没有事务,B建立一个新的事务。
  5. PROPAGATION_NOT_SUPPORTED:not_supported,不支持,A若是有实物,将A的事务挂起,B以非事务执行;若是A没有事务,B以非事务执行。
  6. PROPAGATION_NEVER:never,从不,若是A有事务,B将抛出异常,若是A没有事务,B将以非事务执行。
  7. PROPAGATION_NESTED:nested,嵌套,A和B地产采用保存点机制,造成嵌套事务。

隔离级别:定义了一个事务可能受其余并发事务影响的程度

并发事务引发的问题:

  1. 脏读(dirty reads):脏读数据发生在一个事务A读取了另外一个事务B改写可是还未提交的数据时,若是事务B稍后将事务回滚了,那么A读取到的数据就是无效的,也便是事务A读取到了脏的数据。读脏数据
  2. 不可重复读(Nonrepeatable read):不可重复读发生在一个事务A执行相同的查询两次或者两次以上,可是会获得不一样的数据,这一般是由于在事务A读取数据的同时,另外一个并发事务B在事务A的两次查询之间修改了数据。
  3. 幻读(Phantom read):幻读和不可重复读相似,它发生在一个事务A,读取了几行数据,接着另外一个事务B插入了一些数据,在随后的查询中,第一个事务A就会看到一些本来不在的记录。

不可重复的重点是“修改”,而幻读的重点是“新增”

在Spring的事务管理中,定义了以下隔离级别:

  1. ISOLATION_DEFAULT:使用后端数据库默认的隔离级别。
  2. ISOLATION_READ_UNCOMMITTED:最低隔离级别,容许读取修改但还没有提交的数据,脏读,不可重复读,幻读都有可能发生。
  3. ISOLATION_REPEATABLE_READ:对同一个字段的屡次读取结果都是一致的,除非是事务本身修改了数据,能够阻止脏读和不可重复读,可是幻读依然可能存在。
  4. ISOLATION_READ_COMMITTED:容许事务读取修改且提交了的数据,能够防止脏读,但不可重复读和幻读依然可能存在。
  5. ISOLATION_SERIALIZABLE:最高隔离级别,脏读,不可重复读,幻读均可以免,一般是彻底锁定事务相关的数据表来实现,也是最慢的一种事务隔离级别。

只读

这是事务的第三个特性,是否将事务设置为只读。数据库能够利用事务的只读属性来进行一些优化。经过将事务设置成只读,你就能够给数据库一个机会,让它应用它认为合适的优化措施。

 

事务超时

为了使应用更好的运行,事务不能运行太长时间,由于事务可能涉及对后端数据库的锁定,因此很长时间的事务会没必要要的占用数据库资源,事务超时就是事务的一个定时器,在特定的时间内若是事务没有执行完毕,那么就会自动回滚,而不是一直等待事务结束。

回滚规则

回滚规则定义了哪些异常会致使事务回滚而哪些异常不会致使回滚。默认状况下,事务遇到运行期异常时才会回滚,在遇到检查型异常时不会回滚。可是这些都是能够设置的。好比你能够设置检查型异常也进行事务回滚。

 

Spring编程式事务和声明式事务

 编程式事务:所谓编程式事务就是指经过编码方式实现事务,容许用户在代码中精肯定义事务的边界。即相似JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务,推荐使用TransactionTemplate。

声明式事务管理:管理创建在AOP上,其本质是对方法先后进行拦截,而后再目标方法开始以前建立或加入一个事务,在执行完目标方法以后根据执行状况提交或回滚事务。声明式事务最大的优势就是不须要经过编程的凡是管理事务,这样就不须要在业务逻辑代码中掺琐事务管理的代码,只须要在配置文件中作相关的事务规则声明(或者基于注解@Transaction的形式)。

 

实验

不用事务实现转帐

数据库中有以下表:

有两个帐户Tom和Marry,他们的初始帐户余额都为10000.这时候咱们进行以下业务:Tom向Marry转帐100块,那么这在程序中能够分解为两个步骤:
①、Tom的帐户余额1000减小100块,剩余900块
②、Marry的帐户余额1000增长100块,变为1100块
上面两个步骤要么都执行成功,要么都不执行。咱们经过TransactionTemplate编程式事务来控制:
 
编程式实现事务:
第一步:编写Dao层 AccountDao:
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文件发生了变化。
咱们在applicationContext.xml中配置AOP自动生成代理,进行事务管理。
  1. 配置管理器
  2. 配置事务详情
  3. 配置AOP
  <!-- 事务管理,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>
 
AccountServiceImpl修改:
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)
相关文章
相关标签/搜索