前言

Github:https://github.com/HealerJean

博客:http://blog.healerjean.com

再myfalse github中有spring控制的独立事物,这里是springBoot我们用下注解采用的独立事物

1、属性解释

1.1、REQUIRED

注意,这是默认值,也即不进行该参数配置等于配置成REQUIRED。

REQUIRED的含义是,支持当前已经存在的事务,如果还没有事务,就创建一个新事务。

1.2、REQUIRES_NEW

REQUIRES_NEW的含义是,挂起当前事务,创建一个新事务,如果还没有事务,就简单地创建一个新事务。

1.3、NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。不启用事物

1.4、SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

1.5、MANDATORY (必须有事务,否则抛出异常)

支持当前事务,如果当前没有事务,就抛出异常。

1.6、NEVER (不能有事务,抛出异常)

以非事务方式执行,如果当前存在事务,则抛出异常。

1.7、NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。

2、方法中事物的使用

2.1、事务的配置

2.1.1、项目中全局事物的配置

可以观察到下面只要是service中都使用了事物,其实这种不建议采用,正常我们写方法的时候,再指定其实是最好的。因为写一个具体的方法我们才知道是不是要开启一个事物以及开启事物的时间

    <aop:config proxy-target-class="true">
        <aop:advisor pointcut="execution(* com.duodian.youhui.admin..service..*.*(..))" advice-ref="txAdvice" />
    </aop:config>
    
    <!--开启事物 ,如果是controller中查询其实是不需要使用事物的,所以controller中添加或者删除不可以,但是是可以查询的-->
    <!--  transaction start  -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
            <tx:method name="query*" propagation="SUPPORTS" rollback-for="Exception" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="query*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>
    

2.1.2、使用注解@Transactional开始事物(建议使用)

2.1.2.1、readOnly

readOnly =true,默认是false,只读事物true__保证数据的一致性

没有事务的时候,在执行一条sql语句看到执行前点的数据状态,不能保证一致性

只读事务,在执行多条sql语句看到执行前点的数据状态,保证数据一致性

2.1.2.2、rollbackFor

如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class可以让事物在遇到非运行时异常(比如:我们自己抛出的异常)时也回滚


@Transactional(transactionManager = "duodianTM", 
               propagation = Propagation.REQUIRED, 
               rollbackFor = Exception.class)
@Override
public void process(UserOrderData data) {
    process(data, null);
}

2.2、事务的使用建议

**一般情况下,我们的事物开启的时间不要太早,不然,如果数据量大,开启事物的时间过长,很容易卡死 **

所以当我们在进行增删改查的时候再讲数据开启(所以用了全局配置,那么关于一些不使用事物的方法,写到controll方法的前面)

尽量使用注解Transactional,这样选择开启的时间,因为比较灵活

3、事务是否生效的问题

3.1、类之间调用问题

1、不同类之间的方法调用如类A的方法a()调用类B的方法b(),这种情况事务是正常起作用的。只要方法a()b()配置了事务,运行中就会开启事务,产生代理。若两个方法都配置了事务,两个事务具体以何种方式传播,取决于设置的事务传播特性。

2、同一个类内方法调用:重点来了,同一个类内的方法调用就没那么简单了,假定类A的方法a()调用方法b(),同一类内方法调用,无论被调用的b()方法是否配置了事务,此事务在被调用时都将不生效,都是使用的a()的事务。

3.1.1、方法a没有使用事物,方法b使用了事物

如果baseDao.saveObject(detail)异常,baseDao.saveObject(detail)没有保存成功,但是baseDao.saveObject(user)保存成功了) ,因为没有使用事务,也就是方法B不能全部回滚

public class MyEntry implements IBaseService{
	public String A(String jsonStr) throws Exception{
		UserInfo user = null;
		UserDetail userDetail = null;
		this.getUserMsg(user,userDetail ,jsonStr);
		if(null!= user){
			this.Buser,userDetail;
		}
		return "";
	}
	//此处需要事务
	private String B(UserInfo user, UserDetail detail) throws DBException{
		baseDao.saveObject(user);
		baseDao.saveObject(detail);
	}
}

3.1.2、方法a和方法b都使用了事物

方法a()配置了事务,此时b()的事务不生效,但a()的事务生效,对于b()中抛出的异常也会回滚。也就是说b的事物没生效,其实b使用的是a的事物。

3.1.3、原理分析

有两方法,一个有@Transational注解,一个没有,spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。

当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction

如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。


@Service
public class PersonServiceImpl implements PersonService {

    @Autowired
    PersonDao personDao;

    @Override
    @Transactional
    public boolean addPerson(Person person) {
        boolean result = personDao.insertPerson(person)>0 ? true : false;
        return result;
    }

    @Override
    public boolean updatePersonByPhoneNo(Person person) {
        boolean result = personDao.updatePersonByPhoneNo(person)>0 ? true : false;
        addPerson(person); 
        return result;
    }
}

3.2、其他事务不生效的场景

3.2.1、@Transactional 应用在非 public 修饰的方法上

3.2.2、@Transactional 注解属性 rollbackFor 设置错误

rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。

//事务回滚,数据没有插入
@Transactional(rollbackFor = Exception.class)
@Override
public DemoDTO mybatisPlusInsert(DemoDTO demoDTO) throws Exception {
  DemoEntity demoEntity = BeanUtils.dtoToDemo(demoDTO);
  demoEntity.setStatus(StatusEnum.生效.code);
  demoEntityMapper.insert(demoEntity);
  demoDTO.setId(demoEntity.getId());

  throw new Exception("exception");
}
//事务不回滚,数据插入成功
@Transactional
@Override
public DemoDTO mybatisPlusInsert(DemoDTO demoDTO) throws Exception {
  DemoEntity demoEntity = BeanUtils.dtoToDemo(demoDTO);
  demoEntity.setStatus(StatusEnum.生效.code);
  demoEntityMapper.insert(demoEntity);
  demoDTO.setId(demoEntity.getId());

  throw new Exception("exception");
}

3.2.3、异常被你的 catch“吃了”导致@Transactional失效

4、事务管理器

4.1、开启事务管理器

Spring Boot 使用事务非常简单,首先使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 便可。

下面的方法可以查看使用的是哪个事务管理器

@EnableTransactionManagement
@SpringBootApplication
public class HljClientApplication implements CommandLineRunner {

    @Resource
    private PlatformTransactionManager platformTransactionManager ;

    public static void main(String[] args) {
        SpringApplication.run(HljClientApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println(platformTransactionManager.getClass().getName());
    }
}

4.2、默认提供的事务管理器

4.2.1、 JPAJDBC 事务的实现方式

不管是 JPA 还是JDBC等都实现自接口 PlatformTransactionManager

如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。

如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 ` JpaTransactionManager` 实例

JpaTransactionManager
public class JpaTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, BeanFactoryAware, InitializingBean {
    @Nullable
    private EntityManagerFactory entityManagerFactory;
    @Nullable
    private String persistenceUnitName;
    private final Map<String, Object> jpaPropertyMap;
    @Nullable
    private DataSource dataSource;
    private JpaDialect jpaDialect;


    
    
DataSourceTransactionManager
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager, InitializingBean {
    @Nullable
    private DataSource dataSource;
    private boolean enforceReadOnly;






public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {


4.2.2、配置事务管理器


@EnableTransactionManagement //开始事务管理器支持
@SpringBootApplication
public class HljClientApplication  {

    public static void main(String[] args) {
        SpringApplication.run(HljClientApplication.class, args);
    }

}

4.2.3、jpaTransactionManagerDataSourceTransactionManage

#####

@Configuration
//jpa 扫描配置
//EnableJpaRepositories 
//1、entityManagerFactoryRef 有默认值entityManagerFactory ,写了EntityScan不写该参数,而如果不写该参数就必须写EntityScan了,因为实体必须要扫描的
//2、EnableJpaRepositories 中 transactionManagerRef   有默认值 transactionManager,所以可以不写
@EnableJpaRepositories(
        entityManagerFactoryRef="localEntityManagerFactoryBean",
        transactionManagerRef="jpaTransactionManager",
        basePackages= { "com.hlj.proj.data.dao.db" })
@PropertySource("classpath:db.properties")
public class DatasourceConfig {

    @Value("${hlj.datasource.url}")
    private String admoreUrl;
    @Value("${hlj.datasource.username}")
    private String admoreUsername;
    @Value("${hlj.datasource.password}")
    private String admorePassword;


    @Bean(name = "dataSource")
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(admoreUrl);
        druidDataSource.setUsername(admoreUsername);
        druidDataSource.setPassword(admorePassword);
        druidDataSource.setMaxActive(150);
        druidDataSource.setInitialSize(10);
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setMaxWait(3000);
        druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
        druidDataSource.setMinEvictableIdleTimeMillis(300000);

        return druidDataSource;
    }

    /**
     * 配置实体扫描 和 事务管理器,
     * 1、不配置配置entityManagerFactoryBean, 可以直接使用注解 @EntityScan(basePackages = {"com.hlj.proj.data.pojo"})
     * 2、可以不配置下面的 Sping-data-jpa 默认的事务管理器就是 JpaTransactionManager
     */
    @Bean("localEntityManagerFactoryBean")
    public LocalContainerEntityManagerFactoryBean localEntityManagerFactoryBean (EntityManagerFactoryBuilder builder, @Qualifier("dataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.hlj.proj.data.pojo")
                //任意
                .persistenceUnit("hlj")
                .build();
    }
    
    
    //事务管理器的配置 使用下面随便哪个事务管理器都可以
    //jpaTransactionManager
    @Bean("jpaTransactionManager")
    public PlatformTransactionManager jpaTransactionManager(@Qualifier("localEntityManagerFactoryBean") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean ) {
        return new JpaTransactionManager(entityManagerFactoryBean.getObject());
    }
    
    
    
/*  dataSourceTransactionManager  
    @Bean("dataSourceTransactionManager")
    public PlatformTransactionManager jdbcTransactionManager(@Qualifier("dataSource") DataSource dataSource ) {
            return  new DataSourceTransactionManager(dataSource);
        }
 */
    

}

5、事务问题

5.1、关于使用事物中嵌套 REQUIRES_NEW 事物中出现的异常

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void keyTransactional(CouponItemGood couponItemGood) {

}

比如使用了上面的注解出现了空指针异常,但是因为上面注解的关系异常会抛出 TransactionSystemException

正常情况下,如果 REQUIRES_NEW 中出现了异常,自己的事物中可以捕获,而且包含它的大事物中也会进行二次捕获。也就是说会捕获两次异常

Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnlyException 


外面的大事物中进行捕获为TransactionSystemException
try {
    keyTransactional.keyTransactional(couponItemGood);
    log.info(couponItemGood.getId().toString()+"已经检测");
}catch (AppException e){
    log.info(e.getMessage())
    continue;
}catch (TransactionSystemException e){
    log.info("小当优惠券中没有优惠券信息导致的,无须处理-多个事物开启了异常");
}catch (Exception e){
}

5.2、异步线程事务是怎么回事的

异步线程,事务传播失效,也就是说不会使用事务,即使使用独立事务,即使后面有异常信息,照样会入库,但是需要等线程方法方法全部走完才能入库,而不是像之前的独立事务一样直接在数据库中显示了

@Override
public DemoEntity addDemoEntity(DemoEntity demoEntity) {
    new Thread(()->{ 
        //异步线程,事务传播失效,也就是说不会使用事务
        //即使使用独立事务,即使后面有异常信息,照样会入库,但是需要等方法全部走完,而不是像之前的独立事务一样直接在数据库中显示了
        demoEntityRepository.save(new DemoEntity().setName(demoEntity+"1"));
//      demoNewTransactional.addDemoEntity(new DemoEntity().setName(demoEntity+"1")) ;
        int i = 1/0 ;
    }).start();
    demoEntityRepository.save(demoEntity) ;
    int i = 1/0 ;
    return demoEntity;
}

 

ContactAuthor