Continue with transaction after exception - JPA

ぃ、小莉子 提交于 2021-02-07 12:40:39

问题


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:

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

Mine setting for JPA are like this:

@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:

  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.


回答1:


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

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.

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.

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.


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:


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.



来源:https://stackoverflow.com/questions/44887675/continue-with-transaction-after-exception-jpa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!