异常后继续进行交易 - JPA

时间:2023-01-11 08:41:00

I am using JPA with Spring. I am trying to do batch import. If there is problem with batch import then I would like to insert individually, and if this fails also then I would like to save to duplicates table. I wrote a logic for this but I get this error everytime:

我正在使用JPA和Spring。我正在尝试批量导入。如果批量导入有问题,那么我想单独插入,如果这也失败,那么我想保存到重复表。我为此写了一个逻辑,但每次都会出现这个错误:

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

Mine setting for JPA are like this:

JPA的矿山设置如下:

@Bean(name = "dataSource", destroyMethod = "")
  public DataSource getDataSource() {
    return new JndiDataSourceLookup().getDataSource(props.getDbJndiName());
  }

  @Bean
  public JpaVendorAdapter getHibernateJpaVendorAdapter() {
    return new HibernateJpaVendorAdapter();
  }

  @Bean
  public LocalContainerEntityManagerFactoryBean getEntityManagerFactoryBean() {
    LocalContainerEntityManagerFactoryBean lcemfb = new LocalContainerEntityManagerFactoryBean();
    lcemfb.setDataSource(getDataSource());
    lcemfb.setPersistenceUnitName("MyPU");
    lcemfb.setPackagesToScan("com.project");
    lcemfb.setJpaVendorAdapter(getHibernateJpaVendorAdapter());
    lcemfb.setJpaProperties(getHibernateProperties());
    return lcemfb;
  }

  @Bean
  public Properties getHibernateProperties() {
    Properties jpaProperties = new Properties();
    jpaProperties.put(DIALECT, "org.hibernate.dialect.Oracle10gDialect");
    jpaProperties.put(SHOW_SQL, true);
    jpaProperties.put(AUTOCOMMIT, true);
    jpaProperties.put(FORMAT_SQL, true);
    jpaProperties.put(USE_SQL_COMMENTS, true);
    jpaProperties.put(STATEMENT_BATCH_SIZE, 20);
    jpaProperties.put(ORDER_INSERTS, true);
    jpaProperties.put("hibernate.ejb.entitymanager_factory_name", "MyEM");
    return jpaProperties;
  }

  @Bean
  public JpaTransactionManager getTransactionManager() {
    return new JpaTransactionManager(getEntityManagerFactoryBean().getObject());
  }

  @Bean
  public PersistenceExceptionTranslationPostProcessor getPersistenceExceptionTranslationPostProcessor() {
    return new PersistenceExceptionTranslationPostProcessor();
  }

I get entity manager like this

我得到像这样的实体经理

@PersistenceContext(unitName = "MyPU")
  private EntityManager em;

  protected EntityManager em() {
    return em;
  }

my import method is:

我的导入方法是:

  @Override
  @Transactional
  public void importBusinessFile(MultipartFile file)
      throws GeneralException, IOException {
    // process file

    //save batch
    dealsRepository.saveBatch(deals);
  }

and saveBatch method from repository:

和存储库中的saveBatch方法:

  public void saveBatch(List<Deal> list) {
    for (Deal deal : list) {
      em().persist(deal);
    }

    try {
      em().flush();
      em().clear();
    } catch (Exception e) {
      log.info("Duplicates detected, save individually.", e);

      for (Deal deal : list) {
        try {
          save(deal);
        } catch (Exception ex) {
          log.error("Problem saving individual deal", e);
          // TODO write to duplicates
        }
      }
    }
  }

I tried setting dontRollbackOn but I can't get past this exception. I found some other similar threads but none helped me.

我尝试设置dontRollbackOn但我无法通过此异常。我找到了一些其他类似的线程,但没有人帮助我。

2 个解决方案

#1


2  

In case if you method has @Transactional annotation, occurrence of any exception inside your method marks the surrounding transaction as roll-back, even if you catch exception.

如果您的方法具有@Transactional注释,则方法中出现的任何异常都会将周围的事务标记为回滚,即使您捕获异常也是如此。

You can add an attribute for @Transactional annotation to prevent it of rolling back like : @Transactional(noRollbackFor=Exception.class). Spring rollback transaction for all sub type of runtime exceptions.

您可以为@Transactional注释添加属性,以防止它回滚,如:@Transactional(noRollbackFor = Exception.class)。所有子类型的运行时异常的Spring回滚事务。

If you want to do something when you catch you should try to do it in new transaction.But remeber that self invocation in spring not supported , you can't just call transactional method2 from method1 , you should get from spring context current service and call method2.

如果你想抓住你想做的事情,你应该尝试在新的事务中做。但是请记住,不支持spring中的自调用,你不能只从method1调用transactional method2,你应该从spring context current service和call方法2。

PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks allow an inner transaction scope to trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This setting is typically mapped onto JDBC savepoints, so will only work with JDBC resource transactions. See Spring’s DataSourceTransactionManager.

PROPAGATION_NESTED使用具有多个保存点的单个物理事务,它可以回滚到该事务。这种部分回滚允许内部事务作用域触发其作用域的回滚,外部事务能够继续物理事务,尽管已经回滚了一些操作。此设置通常映射到JDBC保存点,因此仅适用于JDBC资源事务。请参阅Spring的DataSourceTransactionManager。


simple variant :

简单变体:

  @Autowired
  private ApplicationContext context.

  @Override
  @Transactional
  public void importBusinessFile(MultipartFile file)
      throws GeneralException, IOException {
    // process file

    try{
       dealsRepository.saveBatch(deals);
       //in  case fail-transaction for saveBatch is rollback main transactio is active
    }catch(Exception e){
        context.getBean(curent serivce).tryReSaveBatch(deals);
       //in  case fail - transaction for tryReSaveBatchis rollback , 
main transactio is active
    }
     // main transaction commited
  }

@Transactional(propagation = NESTED)
public void saveBatch(List<Deal> list) {
    for (Deal deal : list) {
      em().persist(deal);
    }
  }

@Transactional(propagation = NESTED)
public void tryReSaveBatch(List<Deal> list) {
for (Deal deal : list) {
        try {
          save(deal);
        } catch (Exception ex) {
          log.error("Problem saving individual deal", e);
          // TODO write to duplicates
        }
      }
  }

#2


0  

I only managed to fix this by creating another bean containing batch import method. So after that Spring can intercept the call from this bean and start a new transaction.

我只是通过创建另一个包含批量导入方法的bean来解决这个问题。所以Spring之后可以拦截来自这个bean的调用并开始一个新的事务。

#1


2  

In case if you method has @Transactional annotation, occurrence of any exception inside your method marks the surrounding transaction as roll-back, even if you catch exception.

如果您的方法具有@Transactional注释,则方法中出现的任何异常都会将周围的事务标记为回滚,即使您捕获异常也是如此。

You can add an attribute for @Transactional annotation to prevent it of rolling back like : @Transactional(noRollbackFor=Exception.class). Spring rollback transaction for all sub type of runtime exceptions.

您可以为@Transactional注释添加属性,以防止它回滚,如:@Transactional(noRollbackFor = Exception.class)。所有子类型的运行时异常的Spring回滚事务。

If you want to do something when you catch you should try to do it in new transaction.But remeber that self invocation in spring not supported , you can't just call transactional method2 from method1 , you should get from spring context current service and call method2.

如果你想抓住你想做的事情,你应该尝试在新的事务中做。但是请记住,不支持spring中的自调用,你不能只从method1调用transactional method2,你应该从spring context current service和call方法2。

PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks allow an inner transaction scope to trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This setting is typically mapped onto JDBC savepoints, so will only work with JDBC resource transactions. See Spring’s DataSourceTransactionManager.

PROPAGATION_NESTED使用具有多个保存点的单个物理事务,它可以回滚到该事务。这种部分回滚允许内部事务作用域触发其作用域的回滚,外部事务能够继续物理事务,尽管已经回滚了一些操作。此设置通常映射到JDBC保存点,因此仅适用于JDBC资源事务。请参阅Spring的DataSourceTransactionManager。


simple variant :

简单变体:

  @Autowired
  private ApplicationContext context.

  @Override
  @Transactional
  public void importBusinessFile(MultipartFile file)
      throws GeneralException, IOException {
    // process file

    try{
       dealsRepository.saveBatch(deals);
       //in  case fail-transaction for saveBatch is rollback main transactio is active
    }catch(Exception e){
        context.getBean(curent serivce).tryReSaveBatch(deals);
       //in  case fail - transaction for tryReSaveBatchis rollback , 
main transactio is active
    }
     // main transaction commited
  }

@Transactional(propagation = NESTED)
public void saveBatch(List<Deal> list) {
    for (Deal deal : list) {
      em().persist(deal);
    }
  }

@Transactional(propagation = NESTED)
public void tryReSaveBatch(List<Deal> list) {
for (Deal deal : list) {
        try {
          save(deal);
        } catch (Exception ex) {
          log.error("Problem saving individual deal", e);
          // TODO write to duplicates
        }
      }
  }

#2


0  

I only managed to fix this by creating another bean containing batch import method. So after that Spring can intercept the call from this bean and start a new transaction.

我只是通过创建另一个包含批量导入方法的bean来解决这个问题。所以Spring之后可以拦截来自这个bean的调用并开始一个新的事务。