江门整站优化,微信公众管理平台,python做网站缺点,公司网站域名更改怎么做概念
事务定义
事务#xff0c;就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行#xff0c;我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行#xff0c;我们称该事务被提交。…概念
事务定义
事务就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行我们称该事务被提交。由于其中的一部分或多步执行失败导致没有步骤被提交则事务必须回滚到最初的系统状态。
事务特点
原子性一个事务中所有对数据库的操作是一个不可分割的操作序列要么全做要么全不做一致性数据不会因为事务的执行而遭到破坏隔离性一个事务的执行不受其他事务的干扰即并发执行的事务之间互不干扰持久性一个事务一旦提交它对数据库的改变就是永久的。
事务实现机制
Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。
编程式事务管理 编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理spring推荐使用TransactionTemplate。声明式事务管理 建立在AOP之上的。其本质是对方法前后进行拦截然后在目标方法开始之前创建或者加入一个事务在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务管理不需要入侵代码更快捷而且简单推荐使用。
声明式事务有两种方式
一种是在配置文件xml中做相关的事务规则声明因为很少用本文不讲解另一种是基于 Transactional 注解的方式。注释配置是目前流行的使用方式推荐使用。
在应用系统调用声明了 Transactional 的目标方法时Spring Framework 默认使用 AOP 代理在代码运行时生成一个代理对象根据 Transactional 的属性配置信息这个代理对象决定该声明 Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截在 TransactionInterceptor 拦截时会在目标方法开始执行之前创建并加入事务并执行目标方法的逻辑最后根据执行情况是否出现异常利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种以 CglibAopProxy 为例对于 CglibAopProxy需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy需要调用其 invoke 方法。
开启事务
注解Transactional的使用
注解Transactional常用配置
参 数 名 称功 能 描 述readOnly用于设置当前事务是否为只读事务设置为true表示只读false则表示可读写默认值为false。例如Transactional(readOnlytrue)rollbackFor用于设置需要进行回滚的异常类数组当方法中抛出指定异常数组中的异常时则进行事务回滚。例如指定单一异常类Transactional(rollbackForRuntimeException.class)指定多个异常类Transactional(rollbackFor{RuntimeException.class, Exception.class})transactionManager / value多个事务管理器托管在 Spring 容器中时指定事务管理器的 bean 名称rollbackForClassName用于设置需要进行回滚的异常类名称数组当方法中抛出指定异常名称数组中的异常时则进行事务回滚。例如指定单一异常类名称Transactional(rollbackForClassName”RuntimeException”)指定多个异常类名称Transactional(rollbackForClassName{“RuntimeException”,”Exception”})noRollbackFor用于设置不需要进行回滚的异常类数组当方法中抛出指定异常数组中的异常时不进行事务回滚。例如指定单一异常类Transactional(noRollbackForRuntimeException.class)指定多个异常类Transactional(noRollbackFor{RuntimeException.class, Exception.class})noRollbackForClassName用于设置不需要进行回滚的异常类名称数组当方法中抛出指定异常名称数组中的异常时不进行事务回滚。例如指定单一异常类名称Transactional(noRollbackForClassName”RuntimeException”)指定多个异常类名称Transactional(noRollbackForClassName{“RuntimeException”, ”Exception”})propagation用于设置事务的传播行为。例如Transactional(propagationPropagation.NOT_SUPPORTED, readOnlytrue)isolation用于设置底层数据库的事务隔离级别事务隔离级别用于处理多事务并发的情况通常使用数据库的默认隔离级别即可基本不需要进行设置timeout该属性用于设置事务的超时秒数默认值为-1表示永不超时 事物超时设置Transactional(timeout30) 设置为30秒
Propagation的属性事务的传播行为
例如Transactional(propagationPropagation.NOT_SUPPORTED,readOnlytrue)
Propagation属性含义REQUIRED默认值 在有transaction状态下执行如当前没有transaction则创建新的transactionSUPPORTS如当前有transaction则在transaction状态下执行如果当前没有transaction在无transaction状态下执行MANDATORY必须在有transaction状态下执行如果当前没有transaction则抛出异常IllegalTransactionStateExceptionREQUIRES_NEW创建新的transaction并执行如果当前已有transaction则将当前transaction挂起NOT_SUPPORTED在无transaction状态下执行如果当前已有transaction则将当前transaction挂起NEVER在无transaction状态下执行如果当前已有transaction则抛出异常IllegalTransactionStateException。
事务5种隔离级别
例如Transactional(isolation Isolation.READ_COMMITTED)
隔离级别含义DEFAULT这是一个PlatfromTransactionManager默认的隔离级别使用数据库默认的事务隔离级别另外四个与JDBC的隔离级别相对应READ_UNCOMMITTED最低的隔离级别。事实上我们不应该称其为隔离级别因为在事务完成前其他事务可以看到该事务所修改的数据。而在其他事务提交前该事务也可以看到其他事务所做的修改。可能导致脏幻不可重复读READ_COMMITTED大多数数据库的默认级别。在事务完成前其他事务无法看到该事务所修改的数据。遗憾的是在该事务提交后你就可以查看其他事务插入或更新的数据。这意味着在事务的不同点上如果其他事务修改了数据你就会看到不同的数据。可防止脏读但幻读和不可重复读仍可以发生。REPEATABLE_READ比ISOLATION_READ_COMMITTED更严格该隔离级别确保如果在事务中查询了某个数据集你至少还能再次查询到相同的数据集即使其他事务修改了所查询的数据。然而如果其他事务插入了新数据你就可以查询到该新插入的数据。可防止脏读不可重复读但幻读仍可能发生。SERIALIZABLE完全服从ACID的隔离级别确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。代价最大、可靠性最高的隔离级别所有的事务都是按顺序一个接一个地执行。避免所有不安全读取。
使用注意事项防止事务失效
在具体的类或类的方法上使用 Transactional 注解而不要使用在类所要实现的任何接口上。Transactional 注解应该只被应用在 public 修饰的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 该注解它也不会报错IDEA会有提示 但事务并没有生效。 被外部调用的公共方法A有两个进行了数据操作的子方法B和子方法C的事务注解说明 被外部调用的公共方法A未声明事务Transactional子方法B和C若是其他类的方法且各自声明事务则事务由子方法B和C各自控制被外部调用的公共方法A未声明事务Transactional子方法B和C若是本类的方法则无论子方法B和C是否声明事务事务均不会生效被外部调用的公共方法A声明事务Transactional无论子方法B和C是不是本类的方法无论子方法B和C是否声明事务事务均由公共方法A控制 被外部调用的公共方法A声明事务Transactional子方法运行异常但运行异常被子方法自己 try-catch 处理了则事务回滚是不会生效的 如果想要事务回滚生效需要将子方法的事务控制交给调用的方法来处理 方案1子方法中不用 try-catch 处理运行异常方案2子方法的catch里面将运行异常抛出【throw new RuntimeException();】默认情况下Spring会对unchecked异常进行事务回滚也就是默认对 RuntimeException() 异常或是其子类进行事务回滚。 如果是checked异常则不回滚例如空指针异常、算数异常等会被回滚文件读写、网络问题Spring就没法回滚。 若想对所有异常包括自定义异常都起作用注解上面需配置异常类型Transactional(rollbackFor Exception.class 数据库要支持事务如果是mysql要使用innodb引擎myisam不支持事务 事务Transactional由spring控制时它会在抛出异常的时候进行回滚。如果自己使用try-catch捕获处理了是不生效的。如果想事务生效可以进行手动回滚或者在catch里面将异常抛出【throw new RuntimeException();】 方案一手动抛出运行时异常缺陷是不能在catch代码块自定义返回值 try{.... }catch(Exception e){logger.error(,e);throw new RuntimeException(e);} 方案二手动进行回滚【 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 】 try{...}catch(Exception e){log.error(fail,e);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return false;} Transactional可以放在Controller下面直接起作用看到网上好多同学说要放到Component下面或者Service下面经过试验可以不用放在这两个下面也起作用。 Transactional引入包问题它有两个包 import javax.transaction.Transactional;
// 和
import org.springframework.transaction.annotation.Transactional; // 推荐 这两个都可以用对比了一下他们两个的方法和属性发现后面的比前面的强大。建议使用后面的。
使用场景
自动回滚
直接抛出不try/catch
Override
Transactional(rollbackFor Exception.class)
public Object submitOrder() throws Exception { success(); //假如exception这个操作数据库的方法会抛出异常方法success()对数据库的操作会回滚。 exception(); return ApiReturnUtil.success();
} 手动回滚
进行try/catch回滚并抛出
Override
Transactional(rollbackFor Exception.class)
public Object submitOrder (){ success(); try { exception(); } catch (Exception e) { e.printStackTrace(); // 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return ApiReturnUtil.error();} return ApiReturnUtil.success();
} 回滚部分异常
使用【Object savePoint TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 】设置回滚点。
使用【TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);】回滚到savePoint。
Override
Transactional(rollbackFor Exception.class)
public Object submitOrder (){ success(); //只回滚以下异常Object savePoint TransactionAspectSupport.currentTransactionStatus().createSavepoint();try { exception(); } catch (Exception e) { e.printStackTrace(); // 手工回滚事务TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);return ApiReturnUtil.error();} return ApiReturnUtil.success();
} 手动创建、提交、回滚事务
PlatformTransactionManager 这个接口中定义了三个方法 getTransaction创建事务commit 提交事务rollback 回滚事务。它的实现类是 AbstractPlatformTransactionManager。
Autowired
priDataSourceTransactionManager dataSourceTransactionManager;
Autowired
TransactionDefinition transactionDefinition;// 手动创建事务
TransactionStatus transactionStatus dataSourceTransactionManager.getTransaction(transactionDefinition);// 手动提交事务
dataSourceTransactionManager.commit(transactionStatus);// 手动回滚事务。最好是放在catch 里面防止程序异常而事务一直卡在哪里未提交
dataSourceTransactionManager.rollback(transactionStatus); 事务失效不回滚的原因及解决方案
异常被捕获导致事务失效
在spring boot 中使用事务非常简单直接在方法上面加入Transactional 就可以实现。
GetMapping(delete)
ResponseBody
Transactional
public void delete(RequestParam(id) int id) { try { //delete countrythis.repository.delete(id); if(id 1){ throw Exception(测试事务);} //delete citythis.repository.deleteByCountryId(id);}catch (Exception e){logger.error(delete false: e.getMessage()); }
}
发现事务不回滚即 this.repository.delete(id)成功把数据删除了。
原因
默认spring事务只在发生未被捕获的 RuntimeException 时才回滚。
spring aop 异常捕获原理被拦截的方法需显式抛出异常并不能经任何处理这样aop代理才能捕获到方法的异常才能进行回滚默认情况下aop只捕获 RuntimeException 的异常但可以通过配置来捕获特定的异常并回滚。
换句话说在service的方法中不使用 try catch 或者在 catch 中最后加上throw new RuntimeExcetpion()抛出运行异常这样程序异常时才能被aop捕获进而回滚。
解决方案 dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion4.3.2.RELEASE/version/dependency
dependencygroupIdorg.aspectj/groupIdartifactIdaspectjrt/artifactIdversion1.8.9/version
/dependencyplugin
groupIdorg.codehaus.mojo/groupId
artifactIdaspectj-maven-plugin/artifactId
version1.9/version
configurationshowWeaveInfotrue/showWeaveInfoaspectLibrariesaspectLibrarygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactId/aspectLibrary/aspectLibraries
/configurationexecutions
executiongoalsgoalcompile/goalgoaltest-compile/goal/goals
/execution
/executions
/plugin 解决方案
方案1、在类上或者最外层的公共方法加事务
Service
Slf4j
public class MyTransactional {// 最外层公共方法。自动回滚事务方式insertOrder()方法报错后事务回滚且线程中止后续逻辑无法执行Transactionalpublic void test1() {this.insertOrder();System.out.println(11111111111111111);}// 最外层公共方法。手动回滚事务方式insertOrder()方法报错后事务回滚可以继续执行后续逻辑Transactionalpublic void test2() {try { insertOrder();} catch (Exception e) { log.error(faild to ..., e);// 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();// 其他操作}// 其他操作}// 进行数据库操作的方法private 或 public 均可private void insertOrder() {//insert log info//insertOrder//updateAccount}
} 方案 2、使用AspectJ 取代 Spring AOP 代理
上面的两个问题Transactional 注解只应用到 public 方法和自调用问题是由于使用 Spring AOP 代理造成的。为解决这两个问题可以使用 AspectJ 取代 Spring AOP 代理。
需要将下面的 AspectJ 信息添加到 xml 配置信息中。
AspectJ 的 xml 配置信息
tx:annotation-driven modeaspectj /
bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdataSource /
/bean
/bean classorg.springframework.transaction.aspectj.AnnotationTransactionAspect factory-methodaspectOfproperty nametransactionManager reftransactionManager /
/bean
同时在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。
AspectJ 的 pom 配置信息 方案1例如service层处理事务那么service中的方法中不做异常捕获或者在catch语句中最后增加 throw new RuntimeException(); 语句以便让aop捕获异常再去回滚并且在service的上层要继续捕获这个异常。 方案2在service层方法的catch语句中进行手动回滚这样上层就无需去处理异常。 GetMapping(delete)
ResponseBody
Transactional
public Object delete(RequestParam(id) int id){ if (id 1){return new MessageBean(101,parameter wrong: id id) ; } try { //delete countrythis.countryRepository.delete(id);//delete citythis.cityRepository.deleteByCountryId(id);return new MessageBean(200,delete success);}catch (Exception e){logger.error(delete false: e.getMessage());// 手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();return new MessageBean(101,delete false);}
} 自调用导致事务失效 问题描述及原因 在 Spring 的 AOP 代理下只有目标方法由外部调用目标方法才由 Spring 生成的代理对象来管理否则会造成自调用问题。 若同一类中的 没有Transactional 注解的方法 内部调用 有Transactional 注解的方法有Transactional 注解的方法的事务被忽略不会发生回滚。见 示例代码展示。 自调用问题示例 Service
public class OrderService {private void insert() {insertOrder();}Transactionalpublic void insertOrder() {//insert log info//insertOrder//updateAccount}
}// insertOrder() 尽管有Transactional 注解但它被内部方法 insert()调用事务被忽略出现异常事务不会发生回滚并且会报错类似于org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope翻译没有Transaction无法回滚事务。自调用导致Transactional 失效。 自调用失效原因 spring里事务是用注解配置的当一个方法没有接口单单只是一个内部方法时事务的注解是不起作用的需要回滚时就会报错。出现这个问题的根本原因是 Transactional 的实现原理是AOPAOP的实现原理是动态代理而自调用时并不存在代理对象的调用也就不会产生基于AOP 的事务回滚操作 虽然可以直接从容器中获取代理对象但这样有侵入之嫌不推荐。
dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion4.3.2.RELEASE/version/dependency
dependencygroupIdorg.aspectj/groupIdartifactIdaspectjrt/artifactIdversion1.8.9/version
/dependencyplugin
groupIdorg.codehaus.mojo/groupId
artifactIdaspectj-maven-plugin/artifactId
version1.9/version
configurationshowWeaveInfotrue/showWeaveInfoaspectLibrariesaspectLibrarygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactId/aspectLibrary/aspectLibraries
/configurationexecutions
executiongoalsgoalcompile/goalgoaltest-compile/goal/goals
/execution
/executions
/plugin
其他
事务提交方式
默认情况下数据库处于自动提交模式。每一条语句处于一个单独的事务中在这条语句执行完毕时如果执行成功则隐式的提交事务如果执行失败则隐式的回滚事务。
对于正常的事务管理是一组相关的操作处于一个事务之中因此必须关闭数据库的自动提交模式。不过这个我们不用担心spring 会将底层连接的【自动提交特性】设置为 false 。也就是在使用 spring 进行事务管理的时候spring 会将【是否自动提交】设置为false等价于JDBC中的 connection.setAutoCommit(false); 在执行完之后在进行提交 connection.commit(); 。 事务回滚规则
指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常然后依据规则决定是否回滚抛出异常的事务。
默认配置下spring只有在抛出的异常为运行时unchecked异常时才回滚该事务也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚)而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务包括checked异常。也可以明确定义那些异常抛出时不回滚事务。 事务并发会产生的问题
术语含义脏读A事务读取到了B事务还未提交的数据如果B未提交的事务回滚了那么A事务读取的数据就是无效的这就是数据脏读不可重复读在同一个事务中多次读取同一数据返回的结果不一致这是由于读取事务在进行操作的过程中如果出现更新事务它必须等待更新事务执行成功提交完成后才能继续读取数据这就导致读取事务在前后读取的数据不一致的状况出现幻读A事务读取了几行记录后B事务插入了新数据并且提交了插入操作在后续操作中A事务就会多出几行原本不存在的数据就像A事务出现幻觉这就是幻读
第一类丢失更新
在没有事务隔离的情况下两个事务都同时更新一行数据但是第二个事务却中途失败退出 导致对数据的两个修改都失效了。
例如
张三的工资为5000事务A中获取工资为5000事务B获取工资为5000汇入100并提交数据库工资变为5100 随后事务A发生异常回滚了恢复张三的工资为5000这样就导致事务B的更新丢失了。脏读
脏读就是指当一个事务正在访问数据并且对数据进行了修改而这种修改还没有提交到数据库中这时另外一个事务也访问这个数据然后使用了这个数据。
例如
张三的工资为5000事务A中把他的工资改为8000但事务A尚未提交。 与此同时事务B正在读取张三的工资读取到张三的工资为8000。随后事务A发生异常回滚了事务张三的工资又回滚为5000。最后事务B读取到的张三工资为8000的数据即为脏数据事务B做了一次脏读。不可重复读
是指在一个事务内多次读同一数据。在这个事务还没有结束时另外一个事务也访问该同一数据。那么在第一个事务中的两次读数据之间由于第二个事务的修改那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的因此称为是不可重复读。
例如
在事务A中读取到张三的工资为5000操作没有完成事务还没提交。 与此同时事务B把张三的工资改为8000并提交了事务。 随后在事务A中再次读取张三的工资此时工资变为8000。在一个事务中前后两次读取的结果并不致导致了不可重复读。 第二类丢失更新
不可重复读的特例。
有两个并发事务同时读取同一行数据然后其中一个对它进行修改提交而另一个也进行了修改提交。这就会造成第一次写操作失效。
例如
在事务A中读取到张三的存款为5000操作没有完成事务还没提交。 与此同时事务B存入1000把张三的存款改为6000并提交了事务。 随后在事务A中存储500把张三的存款改为5500并提交了事务这样事务A的更新覆盖了事务B的更新。 幻读
是指当事务不是独立执行时发生的一种现象例如第一个事务对一个表中的数据进行了修改这种修改涉及到表中的全部数据行。同时第二个事务也修改这个表中的数据这种修改是向表中插入一行新数据。那么以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行就好象发生了幻觉一样。
例如
目前工资为5000的员工有10人事务A读取到所有的工资为5000的人数为10人。 此时事务B插入一条工资也为5000的记录。 这时事务A再次读取工资为5000的员工记录为11人。此时产生了幻读。 不可重复读和幻读的区别
不可重复读的重点是修改同样的条件你读取过的数据再次读取出来发现值不一样了
幻读的重点在于新增或者删除同样的条件第 1 次和第 2 次读出来的记录数不一样