大家好,我们又见面了。在大多数涉及数据库操作的项目中,事务控制和事务处理是无法回避的问题。例如,当SQL执行过程中需要对事务进行控制和处理时,整体的处理流程会是:首先启动事务,然后执行具体的SQL。如果执行异常,事务将被回滚;否则,交易将被提交。最后关闭交易,完成整个处理过程。根据这个过程的逻辑,编写相应的实现代码:publicvoidtestJdbcTransactional(DataSourcedataSource){Connectionconn=null;整数结果=0;try{//获取连接conn=dataSource.getConnection();//禁用自动事务提交,改为手动控制conn.setAutoCommit(false);//设置事务隔离级别conn.setTransactionIsolation(TransactionIoslationLevel.READ_COMMITTED.getLevel());//执行SQLPreparedStatementps=conn.prepareStatement("insertintouser(id,name)values(?,?)");ps.setString(1,"123456");ps.setString(2,"汤姆");结果=ps.executeUpdate();//执行成功,手动提交事务conn.commit();}catch(Exceptione){//发生异常,手动回滚事务if(conn!=null){try{conn.rollback();}catch(Exceptione){//写日志..}}}finally{//执行结束,无论成功还是失败,都必须释放资源,断开连接try{if(conn!=null&&!conn.isClosed()){conn.close();}}catch(Exceptione){//writelog...}}}不难发现,上面的代码逻辑并不复杂。对于业务来说,只是一个插入操作,但是混合的事务控制代码明显干扰了业务本身代码处理逻辑的阅读和理解。在常规项目的代码中,涉及到DB处理的场景非常多。如果每一个地方都有这样一段事务控制逻辑,那么整体代码的可维护性会比较差,想想都让人窒息。幸运的是,现在JAVA中的很多项目都是基于Spring框架构建的。得益于Spring框架的封装,业务代码中对事务控制的操作也非常简单,只需要加一个@Transactional注解即可,大大简化了业务代码的入侵。那么了解@Transactional事务注解是否足够全面呢?知道@Transactional注释可能无法按预期工作的任何情况吗?你知道如何使用@Transactional来最小化对性能的影响吗?让我们一起讨论这些问题。Spring的声明式事务处理机制为了简化业务开发场景的事务处理复杂度,让开发者更专注于业务本身的处理逻辑,Spring提供了对声明式事务能力的支持。Spring数据库事务协议处理的逻辑流程如下图所示。与前面例子中基于JDBC的事务处理相比,Spring的事务处理操作交给了Spring框架。开发者只需要实现自己的业务逻辑,大大简化了Transactional处理输入。基于Spring的事务机制,实现上面的DB操作事务控制代码,我们的代码会变得非常简洁:方法只需要添加一个@Transactional注解,只需要在代码中实现业务逻辑,实现了事务控制机制对业务代码的低侵入。Spring基于SpringAOP实现支持的声明式事务功能,即所谓声明式事务,使用@Transactional注解进行声明和标记,告诉Spring框架在何处启用数据库事务控制能力。@Transactional注释可以添加到类或方法中。如果添加到一个类中,则表示该类中所有的公共非静态方法都将启用事务控制能力。由于@Transactional注解承载了Spring框架对事务处理的相关能力,下面我们来看看该注解的一些可选配置和具体使用场景。@Transactional主要可选配置只读事务配置通过readonly参数指定当前事务是否为只读事务。设置为true表示该事务为只读事务,默认为false。@Transactional(readOnly=true)publicDomResponse 默认为{@linkPropagation#REQUIRED}。*@seeorg.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()*/Propagationpropagation()defaultPropagation.REQUIRED;事务超时设置您可以使用超时属性来设置事务的超时秒数。默认值为-1,表示永不超时。@Transactional失败场景避坑同类方法间调用Spring的事务实现原理是AOP,AOP的原理是动态代理。当类的内部方法相互调用时,本质上是类对象本身的调用,而不是使用代理对象来调用,不会触发AOP,所以Spring无法编织代码逻辑事务控制进入调用代码流中,所以这里的事务控制无法生效。publicvoidinsertUser(){writeDataIntoDb();}@TransactionalpublicvoidwriteDataIntoDb(){//...}所以当同一个类中的多个方法相互调用,而被调用的方法需要做事务控制时,需要特别支付关注这个问题。解决方法是创建两个不同的类,然后把方法放到两个类中,这样跨类调用时Spring的事务机制才能生效。添加到非public方法中如果在protected或者private修饰的方法中添加@Transactional注解,虽然代码不会报错,但注解实际上不会生效。@TransactionalprivatevoidwriteDataIntoDb(){//...}方法内部的TryCatch会吞掉相关的异常。这其实很好理解。所有的异常都在业务代码中被捕获并吞噬,相当于业务代码认为捕获到的异常是不需要触发回滚的。对于框架来说,因为异常被捕获,业务逻辑执行正常,所以不会触发异常回滚机制。//捕获可能的异常,DB操作失败时事务不会触发回滚@TransactionalpublicvoidinsertUser(){try{UserEntityuser=newUserEntity();user.setWorkId("123456");user.setUserName("王小二");userRepository.save(用户);}catch(Exceptione){log.error("创建用户失败");//直接吞掉异常,这样就不会触发事务回滚机制}}在业务处理逻辑中,如果确实需要知道并捕获相关的处理异常,进行一些额外的业务逻辑处理,如果要保证事务回滚机制生效,需要抛出RuntimeException,或者继承业务自身实现的RuntimeException定义异常。如下://捕获可能的异常,对外抛出RuntimeException或其子类,可以触发事务回滚@TransactionalpublicvoidinsertUser(){try{UserEntityuser=newUserEntity();user.setWorkId("123456");user.setUserName("王小二");userRepository.save(用户);}catch(Exceptione){log.error("创建用户失败");//@Transactional没有指定rollbackFor,所以throwRuntimeException或其子类可以触发事务回滚机制thrownewRuntimeException(e);}}当然,如果@Transactional注解将rollbackFor指定为具体的异常类型,最终还是要保证在异常发生时将异常类型抛出到外部,才能触发事务处理逻辑。如下://捕获指定的异常,对外抛出相应类型的异常,可以触发事务回滚@Transactional(rollbackFor=DemoException.class)publicvoidinsertUser(){try{UserEntityuser=newUserEntity();user.setWorkId("123456");user.setUserName("王小二");userRepository.save(用户);}catch(Exceptione){log.error("创建用户失败");//@Transactional指定了rollbackFor,抛出的异常必须和rollbackFor指定的异常类型一致thrownewDemoException();}}对应的数据库引擎类型不支持事务。对于MySQL数据库,常见的数据库引擎有InnoDB和Myisam,但MYISAM引擎类型不支持业务。所以如果在建表时设置的引擎类型设置为MYISAM,即使在代码中添加了@Transactional,最终的事务也不会生效。@Transactional使用策略因为事务处理会对性能有一定的影响,所以事务并不意味着可以在任何地方添加。对于一些性能敏感的场景,需要注意几点:只在必要时添加事务控制(1)不包含DB操作相关,不需要添加事务控制(2)单个查询语句,不需要添加事务控制(3)onlyquery对于多个SQL执行场景,可以添加只读事务控制(4)单个insert/update/delete语句不需要添加@Transactional事务处理,因为数据库有隐藏的事务控制单语句执行机制。失败是SQL错误,数据不会更新成功,自然不用回滚。从性能的角度考虑,尽量减少事务控制的代码段处理范围。事务机制类似于并发场景的加锁处理。范围越大,对性能的影响越明显。事务控制范围内的业务逻辑尽量简单,避免非事务相关的耗时处理逻辑,也是从性能层面考虑的。尽量将耗时逻辑放在事务控制之外执行,只将与DB操作相关的逻辑保留在事务中。总结了Spring中事务控制的相关使用,并对@Transactional使用过程中可能出现的一些失败场景进行了讨论。那么你自己对商业的理解是什么?或者你遇到过相关的问题吗?欢迎一起交流。我是启蒙,说的是技术,不只是技术~如果觉得有用请点击关注,也可以关注我的公众号【架构启蒙】获取更及时的更新。期待与您探讨,共同成长为更好的自己。
