Spring事务

大家好,我是张强,今天说一下spring的事务,以及简单介绍spring中事务的编写。有兴趣的同学可以看看,才疏学浅,有错误的地方恳请指正。

width="640" height="498" src="https://v.qq.com/iframe/player.html?vid=r0730m3qg8a&tiny=0&auto=0" allowfullscreen="">

以上为视频

事务的概念

我们在实际业务场景中,经常会遇到数据频繁修改读取的问题。在同一时刻,不同的业务逻辑对同一个表数据进行修改,这样就会造成数据库结果错乱,所以就需要有一种方法来保证数据在同一时刻不会被同时修改,这就要用事务来对数据进行管理。

比如我们去ATM取钱,首先是从银行卡扣钱,然后是ATM机吐钱。这两个操作要么同时失败,要么同时成功。如果其中一步失败的话要么是银行卡扣了钱没出来,要么是钱出来了银行卡没扣,这样对双方都没有好处。如果使用事务就会避免这样的操作,特别是这种涉及金钱操作显得尤为重要。只要有一步出现错误,那么其它执行成功的操作都会回滚到初始化状态。这就是事务的作用。

事务的特性

所有的事务都必须遵循ACID原则,也就是下面这些事务的特性:

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。

  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据也不应该被破坏。

  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。

  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

Spring通过PlatformTransactionManager接口为各个平台提供对应的事务管理器,通过TransactionDefinition接口控制事务的属性。

Spring事务管理有两种方式,一种是声明式事务管理,一种是编程式事务管理。

  • 声明式事务管理:在配置文件中配置属性声明,基于AOP方式实现事务操作。也就是将将事务管理作为一个切面,织入到目标方法实现事务的管理;

  • 声明式事务管理也可以通过直接注解来实现

  • 编程式事务管理:自己编写管理事务方式,即进行提交和异常回滚等操作。

声明式事务管理

最简单的方式,配置:

<tx:annotation-driven proxy-target-class="true"/>

然后在dao层或者service层添加注解,在类上面配置相应的属性:

@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,rollbackFor = RuntimeException.class)

或者再方法上直接注解也可以

@Transactional

还有使用切面的声明式管理,直接配置好对应的切点,设置好事务属性:

<!--事务 advice 使用tx标签配置 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--配置不同的方法使用不同的事务管理方式 -->
       <!--
           propagation 传播行为
           isolation隔离行为
           read-only只读
           rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
           no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
       -->
<tx:method name="insert*" read-only="true" propagation="REQUIRED" isolation="READ_COMMITTED"  no-rollback-for="" rollback-for=""/>
<tx:method name="select*" read-only="true" propagation="REQUIRED"/>
<tx:method name="update*" read-only="false" propagation="REQUIRED"/>
<!--发生异常的时候回滚-->
       <tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>
</tx:attributes>
</tx:advice>

<!--配置事务支持的方法 -->
<aop:config proxy-target-class="true">
<!--execution  public方法  (com.目录下 StudentMapper里面 .*(..) 所有参数所有方法) 所有符合的方法都会使用txAdvice里的事务控制方式-->
   <aop:advisor pointcut="execution(public * com.dao.mapper.StudentMapper.*(..))" advice-ref="txAdvice"/>
</aop:config>

使用编程式事务管理,首先配置事务管理器(上面使用切面声明式事务管理也需要配置需要对应的管理器)

<!--Spring DataSourceTransactionManager事务管理器  使用编程式事务管理和注解事务管理需要 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

在目标类中设置好对应的属性,然后设置提交和回滚等等:

@Test
public void curdTest(){

//定义事务隔离级别,传播行为,
   DefaultTransactionDefinition def = new DefaultTransactionDefinition();
   //定义隔离规则
   def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
   //定义传播行为(运行方式,在什么地方运行)
   def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
   //获取事务状态 事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
   TransactionStatus status = txManager.getTransaction(def);

   student = new RandomStudent().getStudent();

   try{
studentMapper.insertOne(student);

       //sma.selectOne(33);
       //sma.updateOne(student);

       //提交
       txManager.commit(status);
   }catch (RuntimeException e){
//回滚
       txManager.rollback(status);
   }

}

spring针对编程式事务管理也提供了模板,在上面配置了事务管理器的基础上配置模板:

<!-- Spring 编程式事务 管理使用模板-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>

然后在目标类里面使用模板的execute方式就可以了,这里写的比较简单,既然是模板也可以自己再配置相关属性:

@Test
public void curdTest(){

//重写execute方法实现事务管理
   transactionTemplate.execute(new TransactionCallbackWithoutResult(){

@Override
       protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
studentMapper.insertOne(student);
           //int i = 1/0;

       }
});

}

事务属性

pring通过TransactionDefinition接口控制事务的属性,在TransactionDefinition里可以设置下面的属性:

  • (1)传播行为 int getPropagationBehavior(); 返回int,数字代表不同的行为,共七种。当一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播。方法可能继续在现有事务中运行,也可能开启一个新事务,在新事物中运行。

  • (2)隔离规则 getIsolationLevel(); 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据,也就是定义一个事务可能受其他并发事务影响的程度。

  • (3)是否只读 boolean isReadOnly(); 如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。

  • (4)事务超时 int getTimeout();事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

  • (5)回滚规则(默认): 默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚,但是可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

TransactionDefinition接口:
源码:

针对上面几个比较重要的点说一下:

传播行为

int PROPAGATION_REQUIRED = 0;

表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务。

int PROPAGATION_SUPPORTS = 1;

表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行。

int PROPAGATION_MANDATORY = 2;

表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常

int PROPAGATION_REQUIRES_NEW = 3;

表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

int PROPAGATION_NOT_SUPPORTED = 4;

表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

int PROPAGATION_NEVER = 5;

表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。

int PROPAGATION_NESTED = 6;

表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

隔离规则

多个事务并发运行,经常会操作相同的数据来完成各自的任务,所以可能会导致一些数据错乱的问题,隔离规则就是规定事务之间隔离的级别。数据中错乱的现象大致为下面这些:

脏读(Dirty reads)

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据,当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。

不可重复读(Nonrepeatable read)

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

幻读(Phantom read)

幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录。

隔离级别

  • ISOLATION_DEFAULT

使用后端数据库默认的隔离级别

  • ISOLATION_READ_UNCOMMITTED

最低的隔离级别,允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读

  • ISOLATION_READ_COMMITTED

允许读取并发事务已经提交的数据。可以阻止脏读,但是幻读或不可重复读仍有可能发生

  • ISOLATION_REPEATABLE_READ

对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

  • ISOLATION_SERIALIZABLE

最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

还有Spring对各个事务管理器的支持:

Spring本身使用PlatformTransactionManager接口管理事务管理器,然后又把事务管理器针对常用的平台做了支持:

JDBC事务管理器 org.springframework.jdbc.datasource.DataSourceTransactionManager

<!--jdbc事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

Hibernate事务 org.springframework.orm.hibernate5.HibernateTransactionManager

<!--hibernate 事务-->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

Java持久化API事务(JPA:Java Persistence APIjava持久层API,sun自己的orm映射) org.springframework.orm.jpa.JpaTransactionManager

<!--Java持久化API事务(JPA)-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

Java原生API事务

org.springframework.transaction.jta.JtaTransactionManager

<!--Java原生API事务-->
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>

讨论:

Q:除了这些基础的操作如何判断其它什么地方需要使用事务?
A:所有需要保证一致性操作的地方都可以使用事务,不仅仅是操作数据库。

Q:事务是如何保证操作一致性的
A:第一是回滚,第二是通过锁,还有版本和快照等技术,细节需要仔细研究。(保留疑问)

Q:事务是多线程的么
A:因为线程不属于spring托管,故线程不能够默认使用spring的事务,也不能获取spring注入的bean在被spring声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值