当前位置: 首页 > news >正文

黄冈网站推广收费标准古田路9号设计网站

黄冈网站推广收费标准,古田路9号设计网站,wordpress等级,创办个人网站文章目录一. 事务概述1.1. MySQL 数据库事务1.2 spring的事务支持:1.2.1 编程式事务#xff1a;1.2.2 声明式事务1.2.3 事务传播行为#xff1a;1.2.4 事务隔离级别1.2.5 事务的超时时间1.2.6 事务的只读属性1.2.7 事务的回滚策略二. spring事务#xff08;注解 Transaction… 文章目录一. 事务概述1.1. MySQL 数据库事务1.2 spring的事务支持:1.2.1 编程式事务1.2.2 声明式事务1.2.3 事务传播行为1.2.4 事务隔离级别1.2.5 事务的超时时间1.2.6 事务的只读属性1.2.7 事务的回滚策略二. spring事务注解 Transactional 失效的12种场景2.1 事务不生效【七种】2.1.1 访问权限问题 (只有public方法会生效)2.1.2 方法用final修饰不会生效2.1.3同一个类中的方法直接内部调用会导致事务失效2.1.4.(类本身) 未被spring管理2.1.5 多线程调用2.1.6 6.(存储引擎)表不支持事务2.1.7 未开启事务三、事务不回滚【五种】3.1 错误的传播特性3.2 自己吞了异常3.3 手动抛了别的异常3.4 自定义了回滚异常3.5 嵌套事务回滚多了四、大事务问题一. 事务概述 事务在逻辑上是一组操作要么执行要不都不执行。主要是针对数据库而言的比如说 MySQL。 1.1. MySQL 数据库事务 MYSQL 数据库ACID 的 4 个重要特性 特性描述原子性Atomicity一个事务中的所有操作要么全部完成要么全部不完成不会结束在中间某个环节。事务在执行过程中发生错误会被回滚Rollback到事务开始前的状态就像这个事务从来没有执行过一样一致性Consistency在事务开始之前和事务结束以后数据库的完整性没有被破坏事务隔离Isolation数据库允许多个并发事务同时对其数据进行读写和修改隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致持久性Durability事务处理结束后对数据的修改就是永久的即便系统故障也不会丢失 MYSQL 数据库事务隔离级别 隔离级别描述未提交读Read uncommitted最低的隔离级别允许“脏读”dirty reads事务可以看到其他事务“尚未提交”的修改。如果另一个事务回滚那么当前事务读到的数据就是脏数据提交读read committed一个事务可能会遇到不可重复读Non Repeatable Read的问题。不可重复读是指在一个事务内多次读同一数据在这个事务还没有结束时如果另一个事务恰好修改了这个数据那么在第一个事务中两次读取的数据就可能不一致可重复读repeatable read一个事务可能会遇到幻读Phantom Read的问题。幻读是指在一个事务中第一次查询某条记录发现没有但是当试图更新这条不存在的记录时竟然能成功并且再次读取同一条记录它就神奇地出现了串行化Serializable最严格的隔离级别所有事务按照次序依次执行因此脏读、不可重复读、幻读都不会出现。虽然 Serializable 隔离级别下的事务具有最高的安全性但是由于事务是串行执行所以效率会大大下降应用程序的性能会急剧降低。如果没有特别重要的情景一般都不会使用 Serializable 隔离级别 1.2 spring的事务支持: spring 支持两种事务方式编程式事务 和 声明式事务。 /*** 模拟转账*/ Transactional public void handle() {// 转账transfer(double money);// 减自己的钱Reduce(double money); }1.2.1 编程式事务 编程式事务是指将事务管理代码嵌入嵌入到业务代码中来控制事务的提交和回滚。 方式一使用 TransactionTemplate 来管理事务 Autowired private TransactionTemplate transactionTemplate; public void testTransaction() {transactionTemplate.execute(new TransactionCallbackWithoutResult() {Overrideprotected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {try {// .... 业务代码} catch (Exception e){//回滚transactionStatus.setRollbackOnly();}}}); } 方式二使用 TransactionManager 来管理事务 Autowired private PlatformTransactionManager transactionManager;public void testTransaction() {TransactionStatus status transactionManager.getTransaction(new DefaultTransactionDefinition());try {// .... 业务代码transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);} } 注意就编程式事务管理而言Spring 更推荐使用 TransactionTemplate。 在编程式事务中必须在每个业务操作中包含额外的事务管理代码就导致代码看起来非常的臃肿但对理解 Spring 的事务管理模型非常有帮助。 1.2.2 声明式事务 声明式事务将事务管理代码从业务方法中抽离了出来以声明式的方式来实现事务管理对于开发者来说声明式事务显然比编程式事务更易用、更好用。 当然了要想实现事务管理和业务代码的抽离就必须得用到 Spring 当中的AOP其本质是对方法前后进行拦截然后在目标方法开始之前创建或者加入一个事务执行完目标方法之后根据执行的情况提交或者回滚。 声明式事务虽然优于编程式事务但也有不足声明式事务管理的粒度是方法级别而编程式事务是可以精确到代码块级别的。 事务管理模型 Spring 将事务管理的核心抽象为一个事务管理器TransactionManager它的源码只有一个简单的接口定义属于一个标记接口 public interface TransactionManager {} 该接口有两个子接口分别是编程式事务接口 ReactiveTransactionManager 和声明式事务接口 PlatformTransactionManager。我们来重点说说 PlatformTransactionManager该接口定义了 3 个接口方法 interface PlatformTransactionManager extends TransactionManager{// 根据事务定义获取事务状态TransactionStatus getTransaction(TransactionDefinition definition)throws TransactionException;// 提交事务void commit(TransactionStatus status) throws TransactionException;// 事务回滚void rollback(TransactionStatus status) throws TransactionException; } 通过 PlatformTransactionManager 这个接口Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器但是具体的实现就是各个平台自己的事情了。 参数 TransactionDefinition 和 Transactional 注解是对应的比如说 Transactional 注解中定义的事务传播行为、隔离级别、事务超时时间、事务是否只读等属性在 TransactionDefinition 都可以找得到。 返回类型 TransactionStatus 主要用来存储当前事务的一些状态和数据比如说事务资源connection、回滚状态等。 TransactionDefinition如下 public interface TransactionDefinition {// 事务的传播行为default int getPropagationBehavior() {return PROPAGATION_REQUIRED;}// 事务的隔离级别default int getIsolationLevel() {return ISOLATION_DEFAULT;}// 事务超时时间default int getTimeout() {return TIMEOUT_DEFAULT;}// 事务是否只读default boolean isReadOnly() {return false;} } Transactional注解如下 Target({ElementType.TYPE, ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) Inherited Documented public interface Transactional {Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;boolean readOnly() default false;} Transactional 注解中的 propagation 对应 TransactionDefinition 中的 getPropagationBehavior默认值为 Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。 Transactional 注解中的 isolation 对应 TransactionDefinition 中的 getIsolationLevel默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。 Transactional 注解中的 timeout 对应 TransactionDefinition 中的 getTimeout默认值为TransactionDefinition.TIMEOUT_DEFAULT。 Transactional 注解中的 readOnly 对应 TransactionDefinition 中的 isReadOnly默认值为 false。 说到这我们来详细地说明一下 Spring 事务的传播行为、事务的隔离级别、事务的超时时间、事务的只读属性以及事务的回滚规则。 说到这我们来详细地说明一下 Spring 事务的传播行为、事务的隔离级别、事务的超时时间、事务的只读属性以及事务的回滚规则。 1.2.3 事务传播行为 当事务方法被另外一个事务方法调用时必须指定事务应该如何传播例如方法可能继续在当前事务中执行也可以开启一个新的事务在自己的事务中执行。 声明式事务的传播行为可以通过 Transactional 注解中的 propagation 属性来定义比如说 Transactional(propagation Propagation.REQUIRED) public void savePosts(PostsParam postsParam) { } TransactionDefinition 一共定义了 7 种事务传播行为其中PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW 两种传播行为是比较常用的。 1. PROPAGATION_REQUIRED 这也是 Transactional 默认的事务传播行为指的是如果当前存在事务则加入该事务如果当前没有事务则创建一个新的事务。更确切地意思是 如果外部方法没有开启事务的话Propagation.REQUIRED 修饰的内部方法会开启自己的事务且开启的事务相互独立互不干扰。 如果外部方法开启事务并且是 Propagation.REQUIRED 的话所有 Propagation.REQUIRED 修饰的内部方法和外部方法均属于同一事务 只要一个方法回滚整个事务都需要回滚。 也就是说如果a方法和b方法都添加了注解在默认传播模式下a方法内部调用b方法会把两个方法的事务合并为一个事务。 2. PROPAGATION_REQUIRES_NEW 创建一个新的事务如果当前存在事务则把当前事务挂起。也就是说不管外部方法是否开启事务Propagation.REQUIRES_NEW 修饰的内部方法都会开启自己的事务且开启的事务与外部的事务相互独立互不干扰。 当类A中的 a 方法用默认 Propagation.REQUIRED模式类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式然后在 a 方法中调用 b方法操作数据库然而 a方法抛出异常后b方法并没有进行回滚因为Propagation.REQUIRES_NEW会暂停 a方法的事务 总结就是a不影响bb影响a 3. PROPAGATION_NESTED 如果当前存在事务就在当前事务内执行否则就执行与 PROPAGATION_REQUIRED 类似的操作。 当类A中的 a 方法用默认 Propagation.REQUIRED模式类B中的 b方法加上采用 Propagation.NESTED模式然后在 在a 方法里调用 b方法操作数据库然而 b方法抛出异常后a方法是不的回滚 总结就是b不影响aa影响b。 4. PROPAGATION_SUPPORTS 如果当前存在事务则加入该事务如果当前没有事务则以非事务的方式继续运行。 5. PROPAGATION_NOT_SUPPORTED 以非事务方式运行如果当前存在事务则把当前事务挂起。 6. PROPAGATION_MANDATORY 如果当前存在事务则加入该事务如果当前没有事务则抛出异常。 7. PROPAGATION_NEVER 以非事务方式运行如果当前存在事务则抛出异常。 1.2.4 事务隔离级别 前面我们已经了解了数据库的事务隔离级别再来理解 Spring 的事务隔离级别就容易多了。 TransactionDefinition 中一共定义了 5 种事务隔离级别 隔离级别描述ISOLATION_DEFAULT使用数据库默认的隔离级别MySql 默认采用的是 REPEATABLE_READ也就是可重复读。ISOLATION_READ_UNCOMMITTED最低的隔离级别可能会出现脏读、幻读或者不可重复读ISOLATION_READ_COMMITTED允许读取并发事务提交的数据可以防止脏读但幻读和不可重复读仍然有可能发生。ISOLATION_REPEATABLE_READ对同一字段的多次读取结果都是一致的除非数据是被自身事务所修改的可以阻止脏读和不可重复读但幻读仍有可能发生。ISOLATION_SERIALIZABLE最高的隔离级别虽然可以阻止脏读、幻读和不可重复读但会严重影响程序性能 通常情况下我们采用默认的隔离级别 ISOLATION_DEFAULT 就可以了也就是交给数据库来决定。 1.2.5 事务的超时时间 事务超时**timeout **也就是指一个事务所允许执行的最长时间如果在超时时间内还没有完成的话就自动回滚。 假如事务的执行时间格外的长由于事务涉及到对数据库的锁定就会导致长时间运行的事务占用数据库资源。 1.2.6 事务的只读属性 事务的只读属性readOnly 如果一个事务只是对数据库执行读操作那么该数据库就可以利用事务的只读属性采取优化措施适用于多条数据库查询操作中。 为什么一个查询操作还要启用事务支持呢 这是因为 MySqlinnodb默认对每一个连接都启用了 autocommit 模式在该模式下每一个发送到 MySql 服务器的 SQL 语句都会在一个单独的事务中进行处理执行结束后会自动提交事务。 那如果我们给方法加上了 Transactional 注解那这个方法中所有的 SQL 都会放在一个事务里。否则每条 SQL 都会单独开启一个事务中间被其他事务修改了数据都会实时读取到。 有些情况下当一次执行多条查询语句时需要保证数据一致性时就需要启用事务支持。否则上一条 SQL 查询后被其他用户改变了数据那么下一个 SQL 查询可能就会出现不一致的状态。 1.2.7 事务的回滚策略 **回滚策略rollbackFor **用于指定能够触发事务回滚的异常类型可以指定多个异常类型。默认情况下事务只在出现运行时异常Runtime Exception时回滚以及 Error出现检查异常checked exception需要主动捕获处理或者向上抛出时不回滚。 如果你想要回滚特定的异常类型的话可以这样设置 Transactional(rollbackFor MyException.class) 事务的不回滚策略 **不回滚策略noRollbackFor **用于指定不触发事务回滚的异常类型可以指定多个异常类型。 二. spring事务注解 Transactional 失效的12种场景 在某些业务场景下如果一个请求中需要同时写入多张表的数据或者执行多条sql。为了保证操作的原子性要么同时成功要么同时失败避免数据不一致的情况我们一般都会用到spring事务。 2.1 事务不生效【七种】 2.1.1 访问权限问题 (只有public方法会生效) 众所周知java的访问权限主要有四种private、default、protected、public它们的权限从左到右依次变大。 但如果我们在开发过程中把有某些事务方法定义了错误的访问权限就会导致事务功能出问题例如 Service public class UserService {Transactionalprivate void add(UserModel userModel) {saveData(userModel);updateData(userModel);} } 我们可以看到add方法的访问权限被定义成了private这样会导致事务失效spring要求被代理方法必须得是public的。 说白了在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断如果目标方法不是public则TransactionAttribute返回null即不支持事务。 protected TransactionAttribute computeTransactionAttribute(Method method, Nullable Class? targetClass) {// Dont allow no-public methods as required.可以看到 这里不支持public类型的方法if (allowPublicMethodsOnly() !Modifier.isPublic(method.getModifiers())) {return null;}// The method may be on an interface, but we need attributes from the target class.// If the target class is null, the method will be unchanged.Method specificMethod AopUtils.getMostSpecificMethod(method, targetClass);// First try is the method in the target class.TransactionAttribute txAttr findTransactionAttribute(specificMethod);if (txAttr ! null) {return txAttr;}// Second try is the transaction attribute on the target class.txAttr findTransactionAttribute(specificMethod.getDeclaringClass());if (txAttr ! null ClassUtils.isUserLevelMethod(method)) {return txAttr;}if (specificMethod ! method) {// Fallback is to look at the original method.txAttr findTransactionAttribute(method);if (txAttr ! null) {return txAttr;}// Last fallback is the class of the original method.txAttr findTransactionAttribute(method.getDeclaringClass());if (txAttr ! null ClassUtils.isUserLevelMethod(method)) {return txAttr;}}return null;} 也就是说如果我们自定义的事务方法即目标方法它的访问权限不是public而是private、default或protected的话spring则不会提供事务功能。 2.1.2 方法用final修饰不会生效 有时候某个方法不想被子类重新这时可以将该方法定义成final的。普通方法这样定义是没问题的但如果将事务方法定义成final例如 Service public class UserService {Transactionalpublic final void add(UserModel userModel){saveData(userModel);updateData(userModel);} } 我们可以看到add方法被定义成了final的这样会导致事务失效。 为什么 如果你看过spring事务的源码可能会知道spring事务底层使用了aop也就是通过jdk动态代理或者cglib帮我们生成了代理类在代理类中实现的事务功能。但如果某个方法用final修饰了那么在它的代理类中就无法重写该方法而添加事务功能。 注意如果某个方法是static的同样无法通过动态代理变成事务方法。 2.1.3同一个类中的方法直接内部调用会导致事务失效 有时候我们需要在某个Service类的某个方法中调用另外一个事务方法比如 Service public class UserService {Autowiredprivate UserMapper userMapper;public void add(UserModel userModel) {userMapper.insertUser(userModel);updateStatus(userModel);}Transactionalpublic void updateStatus(UserModel userModel) {doSameThing();} } 我们看到在事务方法add中直接调用事务方法updateStatus。从前面介绍的内容可以知道updateStatus方法拥有事务的能力是因为spring aop生成代理了对象但是这种方法直接调用了this对象的方法所以updateStatus方法不会生成事务。 参考参考1 参考2 由此可见在同一个类中的方法直接内部调用会导致事务失效。 那么问题来了如果有些场景确实想在同一个类的某个方法中调用它自己的另外一个方法该怎么办呢 方法1 新加一个Service方法 这个方法非常简单只需要新加一个Service方法把Transactional注解加到新Service方法上把需要事务执行的代码移到新方法中。具体代码如下 Servcie public class ServiceA {Autowiredprvate ServiceB serviceB;public void save(User user) {queryData1();queryData2();serviceB.doSave(user);}}Servciepublic class ServiceB {Transactional(rollbackForException.class)public void doSave(User user) {addData1();updateData2();}} 方法2在该Service类中注入自己 如果不想再新加一个Service类在该Service类中注入自己也是一种选择。具体代码如下 Servcie public class ServiceA {Autowiredprvate ServiceA serviceA;public void save(User user) {queryData1();queryData2();serviceA.doSave(user);}Transactional(rollbackForException.class)public void doSave(User user) {addData1();updateData2();}} 可能有些人可能会有这样的疑问这种做法会不会出现循环依赖问题 答案不会。 其实spring ioc内部的三级缓存保证了它不会出现循环依赖问题。 方法3通过AopContent类 在该Service类中使用AopContext.currentProxy()获取代理对象 上面的方法2确实可以解决问题但是代码看起来并不直观还可以通过在该Service类中使用AOPProxy获取代理对象实现相同的功能。具体代码如下 Servcie public class ServiceA {public void save(User user) {queryData1();queryData2();((ServiceA)AopContext.currentProxy()).doSave(user);}Transactional(rollbackForException.class)public void doSave(User user) {addData1();updateData2();}} 注意此方法我在实际使用的过程中会报错 Cannot find current proxy: Set ‘exposeProxy’ property on Advised to ‘true’ to 报错解决方法 参考 在讲述Spring事务失效的原因及解决方案之前我们先回顾一下代理模式 我们知道, spring的声明式事务是基于代理模式的。那么说事务之前我们还是大致的介绍一下代理模式吧。 其实代理模式相当简单, 就是将另一个类包裹在我们的类外面, 在调用我们创建的方法之前, 先经过外面的方法, 进行一些处理, 返回之前, 再进行一些操作。比如: ...public User getUserByName(String name) {return userDao.getUserByName(name);}... } 那么如果配置了事务, 就相当于又创建了一个类java public class UserServiceProxy extends UserService{private UserService userService;...public User getUserByName(String name){User user null;try{// 在这里开启事务user userService.getUserByName(name);// 在这里提交事务}catch(Exception e){// 在这里回滚事务// 这块应该需要向外抛异常, 否则我们就无法获取异常信息了. // 至于方法声明没有添加异常声明, 是因为覆写方法, 异常必须和父类声明的异常兼容. // 这块应该是利用的java虚拟机并不区分普通异常和运行时异常的特点.throw e;}return user;}... } 然后我们使用的是 UserServiceProxy 类, 所以就可以”免费”得到事务的支持:java Autowired private UserService userService; // 这里spring注入的实际上是UserServiceProxy的对象 private void test(){// 由于userService是UserServiceProxy的对象, 所以拥有了事务管理的能力userService.getUserByName(aa); } ***Spring事务失效的原因*** 通过对Spring事务代理模式的分析我们不难发现Spring事务失效的原因有以下几种情况 1. private、static、final的使用 、 2. 通过this.xxx()调用当前类的方法 3. 使用默认的事务处理方式 4. 线程Thread中声明式事务不起作用 ***Spring事务失效的解决方案***1. private、static、final的使用 这一原因的解决方案很简单我们只需要不在类和方法上使用此类关键字即可。 2. 通过this.xxx()调用当前类的方法 这一原因的解决方案如下java Service public class TaskService {Autowired private TaskManageDAO taskManageDAO; Transactional public void test1(){try { this.test2();//这里调用会使事务失效两条数据都会被保存/* 原因是JDK的动态代理。在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象只有被动态代理直接调用的才会产生事务。这里的this是TaskService真实对象而不是代理对象*///解决方法TaskService proxy (TaskService) AopContext.currentProxy();proxy.test2();}catch (Exception e){e.printStackTrace();}Task task new Task();task.setCompleteBy(wjl练习1);task.setCompleteTime(new Date());taskManageDAO.save(task); } Transactional(propagation Propagation.REQUIRES_NEW) // 这个事务的意思是如果前面方法有事务存在会将前面事务挂起再重启一个新事务 public void test2(){Task task new Task();task.setCompleteBy(wjl练习2);task.setCompleteTime(new Date());taskManageDAO.save(task);throw new RuntimeException(); } } 我们仔细看上面的代码会发现我们使用AopContext.currentProxy()生成了一个当前类的代理类解决事务失效的问题。 如果使用上述方案报如下异常Cannot find current proxy: Set exposeProxy property on Advised to true to make it available可以采用下面的方案java Service public class TaskService {Autowiredprivate TaskManageDAO taskManageDAO;Transactionalpublic void test1(){try { this.test2();//这里调用会使事务失效两条数据都会被保存/* 原因是JDK的动态代理。在SpringIoC容器中返回的调用的对象是代理对象而不是真实的对象只有被动态代理直接调用的才会产生事务。这里的this是TaskService真实对象而不是代理对象*///解决方法getService().test2();}catch (Exception e){e.printStackTrace();}Task task new Task();task.setCompleteBy(wjl练习1);task.setCompleteTime(new Date());taskManageDAO.save(task);}Transactional(propagation Propagation.REQUIRES_NEW)// 这个事务的意思是如果前面方法有事务存在会将前面事务挂起再重启一个新事务public void test2(){Task task new Task();task.setCompleteBy(wjl练习2);task.setCompleteTime(new Date());taskManageDAO.save(task);throw new RuntimeException();}//解决事务失效private TaskService getService(){return SpringUtil.getBean(this.getClass()); //SpringUtil工具类见下面代码} } SpringUtil工具类java Component public class SpringUtil implements ApplicationContextAware {private static ApplicationContext applicationContext null;Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {SpringUtil.applicationContext applicationContext;}public static T T getBean(ClassT cla) {return applicationContext.getBean(cla);}public static T T getBean(String name, ClassT cal) {return applicationContext.getBean(name, cal);}public static Object getBean(String name){return applicationContext.getBean(name);}public static String getProperty(String key) {return applicationContext.getBean(Environment.class).getProperty(key);} } 3. 使用默认的事务处理方式 spring的事务默认是对RuntimeException进行回滚而不继承RuntimeException的不回滚。因为在java的设计中它认为不继承RuntimeException的异常是”checkException”或普通异常如IOException这些异常在java语法中是要求强制处理的。对于这些普通异常spring默认它们都已经处理所以默认不回滚。可以添加rollbackforException.class来表示所有的Exception都回滚4. 线程Thread中声明式事务不起作用java Overridepublic void run() {DefaultTransactionDefinition def new DefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);PlatformTransactionManager txManager ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class);TransactionStatus status txManager.getTransaction(def);try {testDao.save(entity);txManager.commit(status); // 提交事务} catch (Exception e) {System.out.println(异常信息 e.toString());txManager.rollback(status); // 回滚事务} } 2.1.4.(类本身) 未被spring管理 在我们平时开发过程中有个细节很容易被忽略。即使用spring事务的前提是对象要被spring管理需要创建bean实例。 通常情况下我们通过Controller、Service、Component、Repository等注解可以自动实现bean实例化和依赖注入的功能。当然创建bean实例的方法还有很多不一一说了。有兴趣的小伙伴可以参考这篇文章Autowired的这些骚操作你都知道吗 如下所示, 开发了一个Service类但忘了加Service注解比如 //Service public class UserService {Transactionalpublic void add(UserModel userModel) {saveData(userModel);updateData(userModel);} } 从上面的例子我们可以看到UserService类没有加Service注解那么该类不会交给spring管理所以它的add方法也不会生成事务。 2.1.5 多线程调用 在实际项目开发中多线程的使用场景还是挺多的。如果spring事务用在多线程场景中会有问题吗 Slf4j Service public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() - {roleService.doOtherThing();}).start();} }Service public class RoleService {Transactionalpublic void doOtherThing() {System.out.println(保存role表数据);} } 从上面的例子中我们可以看到事务方法add中调用了事务方法doOtherThing但是事务方法doOtherThing是在另外一个线程中调用的。 这样会导致两个方法不在同一个线程中获取到的数据库连接不一样从而是两个不同的事务。如果想doOtherThing方法中抛了异常add方法也回滚是不可能的。 如果看过spring事务源码的朋友可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个mapkey是数据源value是数据库连接。 private static final ThreadLocalMapObject, Object resources new NamedThreadLocal(Transactional resources); 我们说的同一个事务其实是指同一个数据库连接只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程拿到的数据库连接肯定是不一样的所以是不同的事务。 2.1.6 6.(存储引擎)表不支持事务 周所周知在mysql5之前默认的数据库引擎是myisam。 它的好处就不用多说了索引文件和数据文件是分开存储的对于查多写少的单表操作性能比innodb更好。 有些老项目中可能还在用它。 在创建表的时候只需要把ENGINE参数设置成MyISAM即可 CREATE TABLE category (id bigint NOT NULL AUTO_INCREMENT,one_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,two_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,three_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,four_category varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,PRIMARY KEY (id) ) ENGINEMyISAM AUTO_INCREMENT4 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin myisam好用但有个很致命的问题是不支持事务。 如果只是单表操作还好不会出现太大的问题。但如果需要跨多张表操作由于其不支持事务数据极有可能会出现不完整的情况。 此外myisam还不支持行锁和外键。 所以在实际业务场景中myisam使用的并不多。在mysql5以后myisam已经逐渐退出了历史的舞台取而代之的是innodb。 有时候我们在开发的过程中发现某张表的事务一直都没有生效那不一定是spring事务的锅最好确认一下你使用的那张表是否支持事务。 2.1.7 未开启事务 有时候事务没有生效的根本原因是没有开启事务。 你看到这句话可能会觉得好笑。 开启事务不是一个项目中最最最基本的功能吗 为什么还会没有开启事务 没错如果项目已经搭建好了事务功能肯定是有的。 但如果你是在搭建项目demo的时候只有一张表而这张表的事务没有生效。那么会是什么原因造成的呢 当然原因有很多但没有开启事务这个原因极其容易被忽略。 如果你使用的是springboot项目那么你很幸运。因为springboot通过DataSourceTransactionManagerAutoConfiguration类已经默默的帮你开启了事务。 你所要做的事情很简单只需要配置spring.datasource相关参数即可。 但如果你使用的还是传统的spring项目则需要在applicationContext.xml文件中手动配置事务相关参数。如果忘了配置事务肯定是不会生效的。 具体配置如下信息 !-- 配置事务管理器 -- bean classorg.springframework.jdbc.datasource.DataSourceTransactionManager idtransactionManager property namedataSource refdataSource/property /bean tx:advice idadvice transaction-managertransactionManager tx:attributes tx:method name* propagationREQUIRED//tx:attributes /tx:advice !-- 用切点把事务切进去 -- aop:config aop:pointcut expressionexecution(* com.susan.*.*(..)) idpointcut/ aop:advisor advice-refadvice pointcut-refpointcut/ /aop:config 默默的说一句如果在pointcut标签中的切入点匹配规则配错了的话有些类的事务也不会生效。 三、事务不回滚【五种】 3.1 错误的传播特性 其实我们在使用Transactional注解时是可以指定propagation参数的。 该参数的作用是指定事务的传播特性spring目前支持7种传播特性 REQUIRED 如果当前上下文中存在事务那么加入该事务如果不存在事务创建一个事务这是默认的传播属性值。 SUPPORTS 如果当前上下文存在事务则支持事务加入事务如果不存在事务则使用非事务的方式执行。 MANDATORY 如果当前上下文中存在事务否则抛出异常。 REQUIRES_NEW 每次都会新建一个事务并且同时将上下文中的事务挂起执行当前新建事务完成以后上下文事务恢复再执行。 NOT_SUPPORTED 如果当前上下文中存在事务则挂起当前事务然后新的方法在没有事务的环境中执行。 NEVER 如果当前上下文中存在事务则抛出异常否则在无事务环境上执行代码。 NESTED 如果当前上下文中存在事务则嵌套事务执行如果不存在事务则新建事务。 如果我们在手动设置propagation参数的时候把传播特性设置错了比如 Service public class UserService {Transactional(propagation Propagation.NEVER)public void add(UserModel userModel) {saveData(userModel);updateData(userModel);} } 我们可以看到add方法的事务传播特性定义成了Propagation.NEVER这种类型的传播特性不支持事务如果有事务则会抛异常。 目前只有这三种传播特性才会创建新事务REQUIREDREQUIRES_NEWNESTED。 3.2 自己吞了异常 事务不会回滚最常见的问题是开发者在代码中手动try…catch了异常。比如 Slf4j Service public class UserService {Transactionalpublic void add(UserModel userModel) {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);}} } 这种情况下spring事务当然不会回滚因为开发者自己捕获了异常又没有手动抛出换句话说就是把异常吞掉了。 如果想要spring事务能够正常回滚必须抛出它能够处理的异常。如果没有抛异常则spring认为程序是正常的。 3.3 手动抛了别的异常 即使开发者没有手动捕获异常但如果抛的异常不正确spring事务也不会回滚。 Slf4j Service public class UserService {Transactionalpublic void add(UserModel userModel) throws Exception {try {saveData(userModel);updateData(userModel);} catch (Exception e) {log.error(e.getMessage(), e);throw new Exception(e);}} } 上面的这种情况开发人员自己捕获了异常又手动抛出了异常Exception事务同样不会回滚。 因为spring事务默认情况下只会回滚RuntimeException运行时异常和Error错误对于普通的Exception非运行时异常它不会回滚。比如常见的IOExeption和SQLException 3.4 自定义了回滚异常 在使用Transactional注解声明事务时有时我们想自定义回滚的异常spring也是支持的。可以通过设置rollbackFor参数来完成这个功能。 但如果这个参数的值设置错了就会引出一些莫名其妙的问题例如 Slf4j Service public class UserService {Transactional(rollbackFor BusinessException.class)public void add(UserModel userModel) throws Exception {saveData(userModel);updateData(userModel);} } 如果在执行上面这段代码保存和更新数据时程序报错了抛了SqlException、DuplicateKeyException等异常。而BusinessException是我们自定义的异常报错的异常不属于BusinessException所以事务也不会回滚。 即使rollbackFor有默认值但阿里巴巴开发者规范中还是要求开发者重新指定该参数。 这是为什么呢 因为如果使用默认值一旦程序抛出了Exception事务不会回滚这会出现很大的bug。所以建议一般情况下将该参数设置成Exception或Throwable。 3.5 嵌套事务回滚多了 public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);roleService.doOtherThing();} }Service public class RoleService {Transactional(propagation Propagation.NESTED)public void doOtherThing() {System.out.println(保存role表数据);} } 这种情况使用了嵌套的内部事务原本是希望调用roleService.doOtherThing方法时如果出现了异常只回滚doOtherThing方法里的内容不回滚 userMapper.insertUser里的内容即回滚保存点。但事实是insertUser也回滚了。 why? 因为doOtherThing方法出现了异常没有手动捕获会继续往上抛到外层add方法的代理方法中捕获了异常。所以这种情况是直接回滚了整个事务不只回滚单个保存点。 怎么样才能只回滚保存点呢 Slf4j Service public class UserService {Autowiredprivate UserMapper userMapper;Autowiredprivate RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);try {roleService.doOtherThing();} catch (Exception e) {log.error(e.getMessage(), e);}} } 可以将内部嵌套事务放在try/catch中并且不继续往上抛异常。这样就能保证如果内部嵌套事务中出现异常只回滚内部事务而不影响外部事务。 四、大事务问题 在使用spring事务时有个让人非常头疼的问题就是大事务问题。 关于大事务可参考参考 通常情况下我们会在方法上Transactional注解填加事务功能比如 Service public class UserService {Autowired private RoleService roleService;Transactionalpublic void add(UserModel userModel) throws Exception {query1();query2();query3();roleService.save(userModel);update(userModel);} }Service public class RoleService {Autowired private RoleService roleService;Transactionalpublic void save(UserModel userModel) throws Exception {query4();query5();query6();saveData(userModel);} } 但Transactional注解如果被加到方法上有个缺点就是整个方法都包含在事务当中了。 上面的这个例子中在UserService类中其实只有这两行才需要事务 roleService.save(userModel); update(userModel); 在RoleService类中只有这一行需要事务 saveData(userModel);现在的这种写法会导致所有的query方法也被包含在同一个事务当中。 如果query方法非常多调用层级很深而且有部分查询方法比较耗时的话会造成整个事务非常耗时而从造成大事务问题。
http://www.w-s-a.com/news/653838/

相关文章:

  • 做网站推广和头条推广wordpress 验证密码错误
  • 淘宝联盟网站怎么做深圳市创想三维科技有限公司
  • 校园网站建设招标公告php网站开发什么
  • 06628 网页制作与网站开发陕西省交通建设网站
  • 做wish如何利用数据网站暗红色网站
  • 企业 网站备案 法人长春建站模板搭建
  • 网站做快照网站改版 升级的目的
  • 自己做一个网站要多少钱海外推广什么意思
  • 郑州做网站哪家专业网络基础知识大全
  • 济南制作网站企业php 调试网站
  • 互联网站管理工作细则做网站通栏模糊
  • 徐州手机网站开发公司电话青岛有名的互联网公司
  • 如何在手机做网站wordpress 网站搬迁
  • 网站透明导航代码国外卖货平台有哪些
  • 张家界网站建设方案中国网页设计师
  • 淮南网站建设服务东莞营销型手机网站建设
  • 常德做网站专业公司河南高端网站建设
  • 网站服务器建设的三种方法会展设计ppt
  • 如何把自己做的网站放到内网seo优化网络
  • 北京网站建设net2006厦门优化公司
  • 制作网页前为什么要建立站点菏泽百度网站建设
  • 做影视网站引流网页美工设计课程教案
  • 响应式网站开发流程图网站优化seo教程
  • 做汽车团购网站百度官网平台
  • 网站增加关键字建设旅游网站的功能定位
  • 怎么搭建源码网站义乌网络
  • 定远规划建设局网站wordpress云主机安装
  • 慈溪市网站开发软件开发文档国家标准
  • 本地佛山顺德网站设计公司的网站如何建设
  • 网站建设前十名网站建设 招标书