什么是事务: 
事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败.

Spring事务的隔离性(5种)

在讲隔离界别前,我们先来讲讲下基础知识

事务的特性(ACID)

原子性 (atomicity):强调事务的不可分割.
一致性 (consistency):事务的执行的前后数据的完整性保持一致.
隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰
持久性(durability) :事务一旦结束,数据就持久到数据库

 

可能发生的数据安全问题

脏读:另一个事务还未提交,就已经读到数据未提交的数据。

不可重复读:在同一个事务中,读取一次数据,当另一个数据修改数据提交事务后,再次读取数据与上一次读取数据不同。

幻读:在同一个事务中,读取一次数据的数量。当另一个事务增加/删除一些数据时,数据数量与第一次不同。

 

MySQL默认隔离级别,可重复读

1.读未提交(最低事务隔离界别):可能发生问题 脏读,不可重复读,幻读

2.读已提交  解决问题:脏读  可能发生问题 不可重复读,幻读

3.可重复读(默认隔离级别) 解决问题:脏读 , 不可重复读  可能发生问题幻读

4.串行化 (最高事务隔离级别) 解决问题:脏读,不可重复读,幻读   

 

不难想象隔离级别越高,发生的问题就越少。当然也就意味着同步数据消耗的资源更多。

相信MySQL能把可重复读指定为默认隔离级别,说明其性能已经非常棒了

 

Spring隔离级别

1.ISOLATION_DEFAULT   使用数据库默认隔离级别

2.ISOLATION_READ_UNCOMMITTED 读未提交

3.ISOLATION_READ_COMMITTED 读已提交

4.ISOLATION_REPEATABLE_READ 可重复读

5.ISOLATION_SERIALIZABLE 串行化

 

Spring事务的传播行为(7种)

事务传播:当一个方法中调用另外一个方法时,处理这两个方法间的事务行为(是基于Spring的AOP实现的)

举例:

1 @Transactional(isolation = Isolation.SERIALIZABLE)
2 public void method1() {
3         method2();
4     }
5 
6 @Transactional(isolation = Isolation.READ_COMMITTED)
7 public void method2() {
8 }

现在两个方法事务的隔离级别显然不同,那我们对于这种情况就要使用Spring事务传播行为来解决。

 

propagation(传播)

在当前事务中运行:

  1. Propagation.REQUIRED (默认级别)method1事务存在,使用method1事务。method1事务不存在,新建事务)
  2. PROPAGATION_SUPPORTS(method1事务存在,使用method1事务。method1事务不存在,以非事务方式执行)

  3. PROPAGATION_MANDATORY(强制性)method1事务存在,使用method1事务。method1事务不存在,抛出异常)

不再当前事务中运行:

  1. PROPAGATION_REQUIRES_NEW(method1事务不存在,新建事务。method1事务存在,挂起当前事务,重建一个事务
  2. PROPAGATION_NOT_SUPPORTED(method1事务不存在,新建事务。method1事务存在,挂起当前事务
  3. PROPAGATION_NEVER(method1事务不存在,新建事务。method1事务存在,抛出异常
  1. PROPAGATION_NESTED(嵌套的事务执行)

 

事务的实现 

接下来我们看看都可以怎么实现Spring的事务

1.编程式事务管理

  手动编写事务代码(很少使用)

2.声明式事务管理

  基于TransactionProxyFactoryBean方式(很少使用)

  基于Aspect的XML方式(常用)

  基于注解方式(常用)

 

 

实验代码

dao层方法

1 @Repository("MyDao")
2 public interface MyDao {
3     int inCount(@Param("name") String name, @Param("count") double count);//增加钱
4 
5     int outCount(@Param("name") String name, @Param("count") double count);//减少钱
6 
7     double getCount(@Param("name") String name);//查询钱
8 }

查询余额

1 public interface GetCountService {
2     double getCount(String name);
3 }
 1 //获取当前余额
 2 @Service
 3 public class GetCountServiceImpl implements GetCountService {
 4 
 5     @Autowired
 6     private MyDao myDao;
 7 
 8     public double getCount(String name) {
 9         return myDao.getCount(name);
10     }
11 }

转出钱

1 public interface OutService {
2     boolean out(String out, double count);
3 }
1 @Service
2 public class OutServiceImpl implements OutService {
3     @Autowired
4     private MyDao myDao;
5 
6     public boolean out(String out, double count) {
7         return myDao.outCount(out,count)>0;
8     }
9 }

转入钱

1 public interface InService {
2     boolean in(String in, double count);
3 }
1 @Service
2 public class InServiceImpl implements InService {
3     @Autowired
4     private MyDao myDao;
5 
6     public boolean in(String in, double count) {
7         return myDao.inCount(in, count) > 0;
8     }
9 }

转账

1 public interface MyService {
2     boolean transferCount(String out, String in, double count);
3 }
 1 //实现转账
 2 @Service
 3 @Transactional
 4 //(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
 5 public class MyServiceImpl implements MyService {
 6 
 7     @Resource
 8     private GetCountService getCountService;
 9 
10     @Resource
11     private OutService outService;
12 
13     @Resource
14     private InService inService;
15 
16 
17     // 解耦是为了更多的复用
18     // 对于代码模块的修改,不影响接口的使用
19     public boolean transferCount(String out, String in, double count) {
20         //判断传出金额与当前金额
21         if (getCountService.getCount(out) < count) return false;
22         //转出钱
23         if (!outService.out(out, count)) return false;
24 
25         //制造异常
26         //int i = 10 / 0;
27 
28         //转入钱
29         return inService.in(in, count);
30     }
31 }

当关上注释的时候,能实现正常的转账功能。

但是手动制造异常除数等于0后发现,传出人的钱少了,但是转入人的钱缺没有发生变化,没有保证数据的原子性。

 

基于Aspect的XML方式

 1  <!-- 配置事务的通知(事务增强)   -->
 2     <tx:advice id="txAdvice" transaction-manager="transactionManager">
 3         <tx:attributes>
 4             <!--以transfer开头的方法   -->
 5             <tx:method name="transfer*" isolation="DEFAULT" propagation="REQUIRED"/>
 6         </tx:attributes>
 7     </tx:advice>
 8     <!-- 配置切面   -->
 9     <aop:config>
10         <!-- 配置切入点  -->
11         <!--*任意返回值  *任意方法 (..)任意参数  -->
12         <aop:pointcut id="pointcut1" expression="execution(* com.hg.service.*+.*(..))"/>
13         <!--  配置切面 -->
14         <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
15     </aop:config>                

 

基于注解方式(常用)

首先在XML中配置事务管理器

1   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
2         <!--注入数据库连接池-->
3         <property name="dataSource" ref="dataSource"/>
4   </bean>
5 
6     <!--开启注解-->
7     <tx:annotation-driven transaction-manager="transactionManager"/>

开启注解

@Transactional
//这些都是spring事务的默认属性,timeout是直接指定的时间
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT,readOnly = false,timeout = 1000,rollbackFor = Exception.class)
//这两种实现效果相同

 

对比

第一种明显是一种AOP编程思想(面向切面)

当对所有的切面指定好规则后,所有的方法都不需要在进行配置。

优点:

代码量较少

通过制定规则(insert开头的方法,delete开头的方法,select开头的方法,update开头的方法),不易出错

 

第二种注解的方式

优点

较第一种方法更为灵活。但是在某些情况下冗余太多,导致修改维护的困难

 

结论,没有最好的技术,只有最符合业务的技术,小伙根据自己的情况选择合适的方式实现spring的事务吧。