网站经营性备案需要什么资料,怎么提高网站的收录,做网站托管,建设工程执业注册中心网站概念
事务定义
事务#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则创建新的 transaction
SUPPORTS
如当前有 transaction则在 transaction 状态下执行如果当前没有 transaction在无 transaction 状态下执行
MANDATORY
必须在有 transaction 状态下执行如果当前没有 transaction则抛出异常 IllegalTransactionStateException
REQUIRES_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);PlatformTransactionManager 手动提交事务设置隔离级别 Autowired
private PlatformTransactionManager transactionManager;public void test() {DefaultTransactionDefinition defaultTransactionDefinition new DefaultTransactionDefinition();defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus transactionStatus transactionManager.getTransaction(defaultTransactionDefinition);try {// 数据库操作 //提交事务transactionManager.commit(transactionStatus);} catch (Exception e) {log.error(xxxx, e);//回滚事务transactionManager.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 /
/bean
/bean classorg.springframework.transaction.aspectj.AnnotationTransactionAspect factory-methodaspectOfproperty /
/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 的事务回滚操作 虽然可以直接从容器中获取代理对象但这样有侵入之嫌不推荐。 org.springframework spring-aspects 4.3.2.RELEASE org.aspectj aspectjrt 1.8.9 org.codehaus.mojo aspectj-maven-plugin 1.9 true org.springframework spring-aspects compile test-compile
其他
事务提交方式
默认情况下数据库处于自动提交模式。每一条语句处于一个单独的事务中在这条语句执行完毕时如果执行成功则隐式的提交事务如果执行失败则隐式的回滚事务。
对于正常的事务管理是一组相关的操作处于一个事务之中因此必须关闭数据库的自动提交模式。不过这个我们不用担心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
与此同时事务B正在读取张三的工资读取到张三的工资为8000。随后事务A发生异常回滚了事务张三的工资又回滚为5000。最后事务B读取到的张三工资为8000的数据即为脏数据事务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 次读出来的记录数不一样