吉安建设局官方网站,WordPress清除ID沉余,wordpress模板命名规则,网站备案被注销的原因一、事务简单介绍 事务指逻辑上的一组操作#xff0c;组成这组操作的各个单元#xff0c;要不全部成功#xff0c;要不全部不成功。
1.1 事务基本要素 原子性(Atomicity): 事务开始后所有操作#xff0c;要么全部做完#xff0c;要么全部不做#xff0c;不可能停滞在…一、事务简单介绍 事务指逻辑上的一组操作组成这组操作的各个单元要不全部成功要不全部不成功。
1.1 事务基本要素 原子性(Atomicity): 事务开始后所有操作要么全部做完要么全部不做不可能停滞在中间环节。事务执行过程中出错会回滚到事务开始前的状态所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体就像化学中学过的原子是物质构成的基本单位。 一致性(Consistency): 事务开始前和结束后数据库的完整性约束没有被破坏。比如A向B转账不可能A扣了钱B却没收到。 隔离性(Isolation): 同一时间只允许一个事务请求同一数据不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱在A取钱的过程结束前B不能向这张卡转账。 持久性(Durability): 事务完成后事务对数据库的所有更新将被保存到数据库不能回滚。
1.2 Spring事务属性 Spring事务属性包含了5个方面传播行为、隔离规则、回滚规则、事务超时、是否只读。
1.2.1 传播行为 当事务方法被另一个事务方法调用时必须指定事务应该如何传播。例如方法可能继续在现有事务中运行也可能开启一个新事务并在自己的事务中运行。Spring定义了七种传播行为
传播行为含义TransactionDefinition.PROPAGATION_REQUIRED如果当前没有事务就新建一个事务如果已经存在一个事务则加入到这个事务中。这是最常见的选择。TransactionDefinition.PROPAGATION_SUPPORTS支持当前事务如果当前没有事务就以非事务方式执行。TransactionDefinition.PROPAGATION_MANDATORY表示该方法必须在事务中运行如果当前事务不存在则会抛出一个异常TransactionDefinition.PROPAGATION_REQUIRED_NEW表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务在该方法执行期间当前事务会被挂起。TransactionDefinition.PROPAGATION_NOT_SUPPORTED表示该方法不应该运行在事务中。如果当前存在事务就把当前事务挂起。TransactionDefinition.PROPAGATION_NEVER表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行则会抛出异常TransactionDefinition.PROPAGATION_NESTED如果当前存在事务则在嵌套事务内执行。如果当前没有事务则执行与PROPAGATION_REQUIRED类似的操作。
1.2.2 隔离规则 隔离级别定义了一个事务可能受其他并发事务影响的程度。 在实际开发过程中我们绝大部分的事务都是有并发情况。下多个事务并发运行经常会操作相同的数据来完成各自的任务。在这种情况下可能会导致以下的问题: 脏读Dirty reads—— 事务A读取了事务B更新的数据然后B回滚操作那么A读取到的数据是脏数据。 不可重复读Nonrepeatable read—— 事务 A 多次读取同一数据事务 B 在事务A多次读取的过程中对数据作了更新并提交导致事务A多次读取同一数据时结果不一致。 幻读Phantom read—— 系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级但是系统管理员B就在这个时候插入了一条具体分数的记录当系统管理员A改结束后发现还有一条记录没有改过来就好像发生了幻觉一样这就叫幻读。 不可重复读的和幻读很容易混淆不可重复读侧重于修改幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行解决幻读需要锁表 咱们已经知道了在并发状态下可能产生: 脏读、不可重复读、幻读的情况。因此我们需要将事务与事务之间隔离。根据隔离的方式来避免事务并发状态下脏读、不可重复读、幻读的产生。Spring中定义了五种隔离规则:
隔离级别含义脏读不可重复读幻读TransactionDefinition.ISOLATION_DEFAULT使用后端数据库默认的隔离级别TransactionDefinition.ISOLATION_READ_UNCOMMITTED允许读取尚未提交的数据变更(最低的隔离级别)是是是TransactionDefinition.ISOLATION_READ_COMMITTED允许读取并发事务已经提交的数据否是是TransactionDefinition.ISOLATION_REPEATABLE_READ对同一字段的多次读取结果都是一致的除非数据是被本身事务自己所修改否否是TransactionDefinition.ISOLATION_SERIALIZABLE最高的隔离级别完全服从ACID的隔离级别也是最慢的事务隔离级别因为它通常是通过完全锁定事务相关的数据库表来实现的否否否
ISOLATION_SERIALIZABLE 隔离规则类型在开发中很少用到。举个很简单的例子。咱们使用了ISOLATION_SERIALIZABLE规则。A,B两个事务操作同一个数据表并发过来了。A先执行。A事务这个时候会把表给锁住B事务执行的时候直接报错。
补充: 事务隔离级别为ISOLATION_READ_UNCOMMITTED时写数据只会锁住相应的行。 事务隔离级别为可ISOLATION_REPEATABLE_READ时如果检索条件有索引(包括主键索引)的时候默认加锁方式是next-key锁如果检索条件没有索引更新数据时会锁住整张表。一个间隙被事务加了锁其他事务是不能在这个间隙插入记录的这样可以防止幻读。 事务隔离级别为ISOLATION_SERIALIZABLE时读写数据都会锁住整张表。 隔离级别越高越能保证数据的完整性和一致性但是对并发性能的影响也就越大。 1.2.3 回滚规则 事务回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚。而在遇到检查型异常时不会回滚。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样你还可以声明事务遇到特定的异常不回滚即使这些异常是运行期异常。
1.2.4 事务超时 为了使应用程序很好地运行事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定也会占用数据库资源。事务超时就是事务的一个定时器在特定时间内事务如果没有执行完毕那么就会自动回滚而不是一直等待其结束。
1.2.5 是否只读 如果在一个事务中所有关于数据库的操作都是只读的也就是说这些操作只读取数据库中的数据而并不更新数据, 这个时候我们应该给该事务设置只读属性这样可以帮助数据库引擎优化事务。提升效率。
二、Transactional使用 Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式: 编程式事务:允许用户在代码中精确定义事务的边界。编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理spring推荐使用TransactionTemplate。 声明式事务: 基于AOP,有助于用户将操作与事务规则进行解耦。其本质是对方法前后进行拦截然后在目标方法开始之前创建或者加入一个事务在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理也有两种常用的方式一种是在配置文件(xml)中做相关的事务规则声明另一种是基于Transactional注解的方式。显然基于注解的方式更简单易用更清爽。Transactional注解的使用也是我们本文着重要理解的部分。 显然声明式事务管理要优于编程式事务管理这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染一个普通的POJO对象只要加上注解就可以获得完全的事务支持。和编程式事务相比声明式事务唯一不足地方是后者的最细粒度只能作用到方法级别无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求也存在很多变通的方法比如可以将需要进行事务管理的代码块独立为方法等等。
2.1 Transactional介绍 Transactional注解 可以作用于接口、接口方法、类以及类方法上。当作用于类上时该类的所有 public 方法将都具有该类型的事务属性同时我们也可以在方法级别使用该标注来覆盖类级别的定义。 虽然Transactional 注解可以作用于接口、接口方法、类以及类方法上但是 Spring 建议不要在接口或者接口方法上使用该注解因为这只有在使用基于接口的代理时它才会生效。另外 Transactional注解应该只被应用到 public 方法上这是由Spring AOP的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 Transactional 注解这将被忽略也不会抛出任何异常。 默认情况下只有来自外部的方法调用才会被AOP代理捕获也就是类内部方法调用本类内部的其他方法并不会引起事务行为即使被调用方法使用Transactional注解进行修饰。 2.2 Transactional注解属性 Transactional注解里面的各个属性和咱们在上面讲的事务属性里面是一一对应的。用来设置事务的传播行为、隔离规则、回滚规则、事务超时、是否只读。
Target({ElementType.METHOD, ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
Inherited
Documented
public interface Transactional {/*** 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。*/AliasFor(transactionManager)String value() default ;/*** 同上。*/AliasFor(value)String transactionManager() default ;/*** 事务的传播行为默认值为 REQUIRED。*/Propagation propagation() default Propagation.REQUIRED;/*** 事务的隔离规则默认值采用 DEFAULT。*/Isolation isolation() default Isolation.DEFAULT;/*** 事务超时时间。*/int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;/*** 是否只读事务*/boolean readOnly() default false;/*** 用于指定能够触发事务回滚的异常类型。*/Class? extends Throwable[] rollbackFor() default {};/*** 同上指定类名。*/String[] rollbackForClassName() default {};/*** 用于指定不会触发事务回滚的异常类型*/Class? extends Throwable[] noRollbackFor() default {};/*** 同上指定类名*/String[] noRollbackForClassName() default {};}2.2.1 value、transactionManager属性 它们两个是一样的意思。当配置了多个事务管理器时可以使用该属性指定选择哪个事务管理器。大多数项目只需要一个事务管理器。然而有些项目为了提高效率、或者有多个完全不同又不相干的数据源从而使用了多个事务管理器。机智的Spring的Transactional管理已经考虑到了这一点首先定义多个transactional manager并为qualifier属性指定不同的值然后在需要使用Transactional注解的时候指定TransactionManager的qualifier属性值或者直接使用bean名称。配置和代码使用的例子
tx:annotation-driven/bean idtransactionManager1 classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdatasource1/propertyqualifier valuedatasource1Tx/
/beanbean idtransactionManager2 classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdatasource2/propertyqualifier valuedatasource2Tx/
/bean
public class TransactionalService {Transactional(datasource1Tx)public void setSomethingInDatasource1() { ... }Transactional(datasource2Tx)public void doSomethingInDatasource2() { ... }}2.2.2 propagation属性 propagation用于指定事务的传播行为默认值为 REQUIRED。propagation有七种类型就是我们在上文中讲到的事务属性传播行为的七种方式如下所示:
propagation属性事务属性-传播行为含义REQUIREDTransactionDefinition.PROPAGATION_REQUIRED如果当前没有事务就新建一个事务如果已经存在一个事务则加入到这个事务中。这是最常见的选择。SUPPORTSTransactionDefinition.PROPAGATION_SUPPORTS支持当前事务如果当前没有事务就以非事务方式执行。MANDATORYTransactionDefinition.PROPAGATION_MANDATORY表示该方法必须在事务中运行如果当前事务不存在则会抛出一个异常。REQUIRES_NEWTransactionDefinition.PROPAGATION_REQUIRES_NEW表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务在该方法执行期间当前事务会被挂起。NOT_SUPPORTEDTransactionDefinition.PROPAGATION_NOT_SUPPORTED表示该方法不应该运行在事务中。如果当前存在事务就把当前事务挂起。NEVERTransactionDefinition.PROPAGATION_NEVER表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行则会抛出异常。NESTEDTransactionDefinition.PROPAGATION_NESTED如果当前存在事务则在嵌套事务内执行。如果当前没有事务则执行与PROPAGATION_REQUIRED类似的操作。
2.2.3 isolation属性 isolation用于指定事务的隔离规则默认值为DEFAULT。Transactional的隔离规则和上文事务属性里面的隔离规则也是一一对应的。总共五种隔离规则如下所示:
isolation属性事务属性-隔离规则含义脏读不可重复读幻读DEFAULTTransactionDefinition.ISOLATION_DEFAULT使用后端数据库默认的隔离级别READ_UNCOMMITTEDTransactionDefinition.ISOLATION_READ_UNCOMMITTED允许读取尚未提交的数据变更(最低的隔离级别)是是是READ_COMMITTEDTransactionDefinition.ISOLATION_READ_COMMITTED允许读取并发事务已经提交的数据否是是REPEATABLE_READTransactionDefinition.ISOLATION_REPEATABLE_READ对同一字段的多次读取结果都是一致的除非数据是被本身事务自己所修改否 否是SERIALIZABLETransactionDefinition.ISOLATION_SERIALIZABLE最高的隔离级别完全服从ACID的隔离级别也是最慢的事务隔离级别因为它通常是通过完全锁定事务相关的数据库表来实现的否否否
2.2.4 timeout timeout用于设置事务的超时属性。
2.2.5 readOnly readOnly用于设置事务是否只读属性。
2.2.6 rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName rollbackFor、rollbackForClassName用于设置那些异常需要回滚 noRollbackFor、noRollbackForClassName用于设置那些异常不需要回滚。他们就是在设置事务的回滚规则。
注意 1、Transactional 默认在error和runtimeException运行时异常会进行回滚非运行时异常不会回滚。 运行时异常空指针异常类型转换异常等非运行时异常io异常sql异常等。 2、 Transactional(rollbackFor Exception.class)使所有异常抛出时都会回滚另需注意使用try-catch{}时如果不用throw抛出异常时transactional不会生效只有使用try-catch{throw new exception}抛出异常时transactional才会生效会进行回滚。 2.3 Transactional注解的使用 Transactional注解的使用关键点在理解Transactional注解里面各个参数的含义。这个咱们在上面已经对Transactional注解参数的各个含义做了一个简单的介绍。接下来咱们着重讲一讲Transactional注解使用过程中一些注意的点。 Transactional注解内部实现依赖于Spring AOP编程。而AOP在默认情况下只有来自外部的方法调用才会被AOP代理捕获也就是类内部方法调用本类内部的其他方法并不会引起事务行为。 2.3.1 Transactional 注解尽量直接加在方法上 为什么因为Transactional直接加在类或者接口上Transactional注解会对类或者接口里面所有的public方法都有效(相当于所有的public方法都加上了Transactional注解而且注解带的参数都是一样的)。第一影响性能可能有些方法我不需要Transactional注解第二方法不同可能Transactional注解需要配置的参数也不同比如有一个方法只是做查询操作那咱们可能需要配置Transactional注解的readOnly参数。所以强烈建议Transactional注解直接添加的需要的方法上。
2.3.2 Transactional 注解必须添加在public方法上private、protected方法上是无效的 在使用Transactional 的时候一定要记住在private,protected方法上添加Transactional 注解不会有任何效果。相当于没加一样。即使外部能调到protected的方法也无效。和没有添加Transactional一样。
2.3.3 函数之间相互调用 关于有Transactional的函数之间调用会产生什么情况。这里咱们通过几个例子来说明。
2.3.3.1 同一个类中函数相互调用 同一个类AClass中有两个函数aFunction、aInnerFunction。aFunction调用aInnerFunction。而且aFunction函数会被外部调用。
情况0: aFunction添加了Transactional注解aInnerFunction函数没有添加。aInnerFunction抛异常。
public class AClass {Transactional(rollbackFor Exception.class)public void aFunction() {//todo: 数据库操作A(增删该)aInnerFunction(); // 调用内部没有添加Transactional注解的函数}private void aInnerFunction() {//todo: 操作数据B(做了增删改 操作)throw new RuntimeException(函数执行有异常!);}}结果两个函数操作的数据都会回滚。
情况1两个函数都添加了Transactional注解。aInnerFunction抛异常。
public class AClass {Transactional(rollbackFor Exception.class)public void aFunction() {//todo: 数据库操作A(增删该)aInnerFunction(); // 调用内部没有添加Transactional注解的函数}Transactional(propagation Propagation.REQUIRES_NEW, rollbackFor Exception.class)private void aInnerFunction() {//todo: 操作数据B(做了增删改 操作)throw new RuntimeException(函数执行有异常!);}}结果同第一种情况一样两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候内部函数添加Transactional注解无效。Transactional注解只有外部调用才有效。
情况2: aFunction不添加注解aInnerFunction添加注解。aInnerFunction抛异常。
public class AClass {public void aFunction() {//todo: 数据库操作A(增删该)aInnerFunction(); // 调用内部没有添加Transactional注解的函数}Transactional(rollbackFor Exception.class)protected void aInnerFunction() {//todo: 操作数据B(做了增删改 操作)throw new RuntimeException(函数执行有异常!);}}结果两个函数对数据库的操作都不会回滚。因为内部函数Transactional注解添加和没添加一样。
情况3aFunction添加了Transactional注解aInnerFunction函数没有添加。aInnerFunction抛异常不过在aFunction里面把异常抓出来了。
public class AClass {Transactional(rollbackFor Exception.class)public void aFunction() {//todo: 数据库操作A(增删该)try {aInnerFunction(); // 调用内部没有添加Transactional注解的函数} catch (Exception e) {e.printStackTrace();}}private void aInnerFunction() {//todo: 操作数据B(做了增删改 操作)throw new RuntimeException(函数执行有异常!);}}结果两个函数里面的数据库操作都成功。事务回滚的动作发生在当有Transactional注解函数有对应异常抛出时才会回滚。(当然了要看你添加的Transactional注解有没有效)。
2.3.3.1. 不同类中函数相互调用 两个类AClass、BClass。AClass类有aFunction、BClass类有bFunction。AClass类aFunction调用BClass类bFunction。最终会在外部调用AClass类的aFunction。
情况0aFunction添加注解bFunction不添加注解。bFunction抛异常。
Service()
public class AClass {private BClass bClass;Autowiredpublic void setbClass(BClass bClass) {this.bClass bClass;}Transactional(rollbackFor Exception.class)public void aFunction() {//todo: 数据库操作A(增删该)bClass.bFunction();}}Service()
public class BClass {public void bFunction() {//todo: 数据库操作A(增删该)throw new RuntimeException(函数执行有异常!);}
}结果两个函数对数据库的操作都回滚了。
情况1aFunction、bFunction两个函数都添加注解bFunction抛异常。
Service()
public class AClass {private BClass bClass;Autowiredpublic void setbClass(BClass bClass) {this.bClass bClass;}Transactional(rollbackFor Exception.class)public void aFunction() {//todo: 数据库操作A(增删该)bClass.bFunction();}}Service()
public class BClass {Transactional(rollbackFor Exception.class)public void bFunction() {//todo: 数据库操作A(增删该)throw new RuntimeException(函数执行有异常!);}
}结果两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。这种情况下你可以认为事务rollback了两次。两个函数都有异常。
情况2aFunction、bFunction两个函数都添加注解bFunction抛异常。aFunction抓出异常。
Service()
public class AClass {private BClass bClass;Autowiredpublic void setbClass(BClass bClass) {this.bClass bClass;}Transactional(rollbackFor Exception.class)public void aFunction() {//todo: 数据库操作A(增删该)try {bClass.bFunction();} catch (Exception e) {e.printStackTrace();}}}Service()
public class BClass {Transactional(rollbackFor Exception.class)public void bFunction() {//todo: 数据库操作A(增删该)throw new RuntimeException(函数执行有异常!);}
}结果两个函数数据库操作都没成功。而且还抛异常了。org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。看打印出来的解释也很好理解把。咱们也可以这么理解两个函数用的是同一个事务。bFunction函数抛了异常调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行aFunction函数里面把异常给抓出来了这个时候aFunction函数没有抛出异常既然你没有异常那事务就需要提交会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了不让调了。
情况3aFunction、bFunction两个函数都添加注解bFunction抛异常。aFunction抓出异常。这里要注意bFunction函数Transactional注解我们是有变化的加了一个参数propagation Propagation.REQUIRES_NEW控制事务的传播行为。表明是一个新的事务。其实咱们情况3就是来解决情况2的问题的。
Service()
public class AClass {private BClass bClass;Autowiredpublic void setbClass(BClass bClass) {this.bClass bClass;}Transactional(rollbackFor Exception.class)public void aFunction() {//todo: 数据库操作A(增删该)try {bClass.bFunction();} catch (Exception e) {e.printStackTrace();}}}Service()
public class BClass {Transactional(propagation Propagation.REQUIRES_NEW, rollbackFor Exception.class)public void bFunction() {//todo: 数据库操作A(增删该)throw new RuntimeException(函数执行有异常!);}
}结果bFunction函数里面的操作回滚了aFunction里面的操作成功了。有了前面情况2的理解。这种情况也很好解释。两个函数不是同一个事务了。
关于Transactional注解的使用就说这么些。最后做几点总结 要知道Transactional注解里面每个属性的含义。Transactional注解属性就是来控制事务属性的。通过这些属性来生成事务。 要明确我们添加的Transactional注解会不会起作用。Transactional注解在外部调用的函数上才有效果内部调用的函数添加无效要切记。这是由AOP的特性决定的。 要明确事务的作用范围有Transactional的函数调用有Transactional的函数的时候进入第二个函数的时候是新的事务还是沿用之前的事务。稍不注意就会抛UnexpectedRollbackException异常。