程序化事务Spring中事务管理有两种方式,程序化事务和声明式事务。首先详细介绍一下两种事情的实际情况。配置类@Configuration@EnableTransactionManagement@ComponentScan("com.javashitang")publicclassAppConfig{@BeanpublicDruidDataSourcedataSource(){DruidDataSourceds=newDruidDataSource();ds.dbsetDriverClassName("c.mysql.mysql.mysql.Driver");ds.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=true");ds.setUsername("test");ds.setPassword("test");ds.setInitialSize(5);返回;}@BeanpublicDataSourceTransactionManagerdataSourceTransactionManager(){returnnewDataSourceTransactionManager(dataSource());}@BeanpublicJdbcTemplatejdbcTemplate(DataSourcedataSource){returnnewJdbcTemplate(dataSource);}@BeanpublicTransactionTemplatetransactionTemplate(){returnnewTransactionTemplate(dataSourceTransactionManager());}}publicinterfaceUserService{voidaddUser(Stringname,Stringlocation);defaultvoiddoAdd(Stringname){};}@ServicepublicclassUserServiceV1ImplimplementsUserService{@AutowiredprivateJdbcTemplatejdbcTemplate;@AutowiredprivateTransactionTemplatetransactionTemplate;@OverridepublicvoidaddUser(Stringname,Stringlocation){transactionTemplate.execute(newTransactionCallbackWithoutResult(){@OverrideprotectedvoiddoInTransactionWithoutResult(TransactionStatusstatus){try{Stringsql="insertintouserdb(`name)";valueslate)newObject[]{name});thrownewRuntimeException("Failedtosaveuserinformation");}catch(Exceptione){e.printStackTrace();status.setRollbackOnly();}}});}}可以看出程序化事务的方法不优雅,因为业务代码和事务代码耦合在一起。发生异常时,需要手动回滚事务(比使用JDBC方便,JDBC必须先关闭automaticauto-commit,再根据情况手动提交或回滚。rolltr??ansaction)如果你是问优化交易方式的执行?你会怎么做?》其实我们可以用AOP来优化这种代码,设置好切点,方法执行成功就提交事务,方法Transaction发生异常就回滚,这就是声明式事务的实现原理"使用AOP后,我们在调用事务方法时,会调用生成的代理对象,在声明式事务中加入了事务提交和回滚的逻辑Springaop动态代理有如下几种方法JDK动态代理实现(基于interface)(JdkDynamicAopProxy)CGLIB动态代理实现(动态子类生成方法)(CglibAopProxy)AspectJ适配实现springaop默认只会使用JDK和CGLIB生成代理对象@Transactional可以用在什么地方?@Transactional可用于类、方法和接口。此类的所有公共方法都有事务。用在方法上,方法有事务。当一个类和一个方法同时配置事务时,方法的属性将覆盖类的属性并在接口上使用。一般不推荐使用这个,因为只有基于接口的代理才会生效。SpringAOP如果使用cglib实现动态代理,会导致Transaction失败(因为注解无法继承)@Transactional失败场景@Transactional注解应用于非public方法(除非特殊配置,比如使用AspectJ静态织入实现AOP)self-invocation,因为@Transactional是基于动态代理实现异常,异常类型不正确,因为你试图在代码中自己捕获它。默认只支持RuntimeException和Error,不支持检查异常。事务传播配置不符合业务逻辑。@Transactional注释应用于非公共方法。会不会生效?”总理的JDK动态代理只能是public,因为接口的权限修饰符只能是public,cglib代理方法可以代理protected方法(private不好,子类不能访问private父类的方法)。如果支持protected,可能会导致在切换代理实现方法时性能不同,增加bug的可能性。所以统一一下。”如果你想让非public方法也生效,可以考虑使用AspectJ”自调用,因为@Transactional是基于动态代理实现的。自调用时,方法执行不会经过代理对象,所以会导致事务失败。比如下面这种方式调用addUser方法时,事务会失败。="insertintouser(`name`)values(?)";jdbcTemplate.update(sql,newObject[]{name});thrownewRuntimeException("Failedtosaveuser");}}你可以在下面解决@Autowired注入自己方式,如果是self,则通过self调用@AutowiredApplicationContext方法,通过getBean从ApplicationContext获取自身,然后调用//事务生效@ServicepublicclassUserServiceV2ImplimplementsUserService{@AutowiredprivateJdbcTemplatejdbcTemplate;@AutowiredprivateUserServiceuserService;@OverridepublicvoidaddUser(Stringname,Stringlocation){userService.doAdd(name);}@Override@TransactionAdddoalp(){Stringsql="insertintouser(`name`)values(?)";jdbcTemplate.update(sql,newObject[]{name});thrownewRuntimeException("Failedtosaveuser");}}代码中自己尝试异常catch这个逻辑后就是从源码看更清楚。只有执行事务抛出异常,才能进入completeTransactionAfterThrowing方法。该方法中有一个回滚逻辑。如果事务方法没有抛出异常,则只会正常提交();}catch(Throwableex){//targetinvocationexceptioncompleteTransactionAfterThrowing(txInfo,ex);throwex;}finally{cleanupTransactionInfo(txInfo);}异常类型不正确,默认只支持RuntimeException和Error,不支持checkedexception。异常系统图如下。当抛出检查异常时,spring事务不会回滚。如果抛出任何异常,它将被回滚。您可以将rollbackFor配置为Exception@Transactional(rollbackFor=Exception.class)。用户的地址信息存储在地址表中。当保存用户地址信息失败时,我们还要确保用户信息注册成功。publicinterfaceLocationService{voidaddLocation(Stringlocation);}@ServicepublicclassLocationServiceImplimplementsLocationService{@AutowiredprivateJdbcTemplatejdbcTemplate;@Override@TransactionalpublicvoidaddLocation(Stringlocation){Stringsql="insertintolocation(`name`)values(?)";jdbcTemplate.update[}){loc,c;thrownewRuntimeException("保存地址异常");}}@ServicepublicclassUserServiceV3ImplimplementsUserService{@AutowiredprivateJdbcTemplatejdbcTemplate;@AutowiredprivateLocationServicelocationService;@Override@TransactionalpublicvoidaddUser(Stringname,Stringlocation){Stringsql="insertintouser(`name`)values(?)Template(?)"jdupcatedbsql,newObject[]{name});locationService.addLocation(location);}}调用,发现user表和location表都没有插入数据,不符合我们的预期。你可能会说抛出异常,事务当然回滚了。好的,让我们添加尝试catch@ServicepublicclassUserServiceV3ImplimplementsUserService{@AutowiredprivateJdbcTemplatejdbcTemplate;@AutowiredprivateLocationServicelocationService;@Override@TransactionalpublicvoidaddUser(Stringname,Stringlocation){Stringsql="insertintouser(`name`)values(?)";jdbcTemplate。sql,newObject[]{name});try{locationService.addLocation(location);}catch(Exceptione){e.printStackTrace();}}}调用发现user表和location表还是没有插入数据.这是因为事务在LocationServiceImpl中已经被标记为回滚,所以事务最终会被回滚。如果要最终解决,就不得不提到Spring的事务传播行为。不清楚的可以看《面试官:Spring事务的传播行为有几种?》Transactional的事务传播行为默认为Propagation.REQUIRED。“如果当前存在交易,请加入该交易。如果当前没有事务,则创建一个新事务。”此时,我们将LocationServiceImpl中Transactional的事务传播行为改为Propagation.REQUIRES_NEW,以“创建一个新事务。如果有事务,则暂停当前事务。?)”;jdbcTemplate.update(sql,newObject[]{name);try{locationService.addLocation(位置);}catch(Exceptione){e.printStackTrace();}}}@ServicepublicclassLocationServiceImplimplementsLocationService{@AutowiredprivateJdbcTemplatejdbcTemplate;@Override@Transactional(propagation=Propagation.REQUIRES_NEW)publicvoidaddLocation("insertintolocation(`name`)values(?)";jdbcTemplate.update(sql,newObject[]{l??ocation});thrownewRuntimeException("保存地址异常");}}@ServicepublicclassLocationServiceImplimplementsLocationService{@AutowiredprivateJdbcTemplatejdbcTemplate;@Override@Transactional(propagation=Propagation.REQUIRES_NEW)publicvoidaddLocation(Stringlocation){Stringsql="insertintolocation(`name`)values(?)";jdbcTemplate.update(sql,newObject[]{l??ocation});thrownewRuntimeException("保存地址异常");}}
