【问题标题】:Saving the JPA entities in batches- TransactionRequiredException批量保存 JPA 实体 - TransactionRequiredException
【发布时间】:2019-02-02 04:31:48
【问题描述】:

我有以下类使用实体管理器批量保存实体:

@Repository
@Transactional
public class AbstractRepositoryAdapter
{
    private static final Logger logger  = LoggerFactory.getLogger(AbstractRepositoryAdapter.class);
    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public <O extends GenericEntity> void save(List<O> entities){
        entities.forEach(entity->{
            entityManager.persist(entity);          
        }); 
        System.out.println("saved from save(List<O> entities)");
    }
    @Transactional
    public  <O extends GenericEntity> void write(List<O> entities)
    {
        if (!CollectionUtils.isEmpty(entities))
        {
            List<List<O>> listOfList = CollectionHelper.split(entities, 1000);

            ExecutorService executorService = Executors.newFixedThreadPool(10);
            List<Future<?>> futures = new ArrayList<Future<?>>();

            for (List<O> subEntities : listOfList)
            {
                Future<?> future = executorService.submit(new BatchExecutor<O>(entityManager, subEntities));
                futures.add(future);
            }

            ObjectHelper.wait(futures.toArray(new Future<?>[0]));// wait until all tasks are finished

            executorService.shutdown();
        }
    }
    @Transactional
    private static class BatchExecutor<O extends GenericEntity> implements Runnable
    {
        private EntityManager em    = null;
        private List<O>         entities    = null;


        public BatchExecutor(EntityManager em, List<O> entities)
        {
            this.em=em;
            this.entities = entities;
        }

        @Override
        @Transactional
        public void run()
        {
            String sql = "";
            try 
            {
                entities.forEach(entity->{
                    em.persist(entity);
                });

            }
            catch (Exception e)
            {
                logger.debug("Unable to save the entities for -> {} ", sql);
            }

        }
    }
}

如果我尝试使用 write 方法来保存我的实体列表,则会出现以下异常:

javax.persistence.TransactionRequiredException: No transactional EntityManager available

但是如果我使用 save 方法来保存我的实体列表,它可以正常工作,并且实体会保存到数据库中

但是,批量保存实体的代码有什么问题。 Transactional 属性也存在于此。理想情况下它应该可以工作。

注意:GenericEntity 是我所有的实体类实现的接口。

我想写一个多线程的方法来保存通用的实体。但不知何故,它不起作用。如果我遍历原始列表并尝试保存它,那么它可以工作。

【问题讨论】:

    标签: java spring-boot jpa entitymanager


    【解决方案1】:

    您的 BatchExecutor 实例不是由 Spring 创建的,因此该方法上的 @Transactional 注释无效。

    【讨论】:

    • 感谢 Strelock 。还有其他方法可以分批吗?
    【解决方案2】:

    只需创建一个配置类

    @Configuration
    @EnableTransactionManagement
    public class PersistenceJPAConfig{
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
      //...}
    
    @Bean
    public PlatformTransactionManager transactionManager(){
      JpaTransactionManager transactionManager
        = new JpaTransactionManager();
      transactionManager.setEntityManagerFactory(
        entityManagerFactoryBean().getObject() );
      return transactionManager;}}
    

    【讨论】:

      【解决方案3】:

      您的问题是您的 BatchExecutor 不是 Spring bean,因此 Spring 无法尊重您在那里的 @Transactional 注释。最简单的可能是添加一个作为 BatchExecutor 工厂的 Spring bean,这样您就可以让每个 BatchExecutor 都可以是一个 Spring bean。比如:

      @Component
      public class BatchExecutorFactory {
          @Lookup <O extends GenericEntity> BatchExecutor<O> getBatchExecutor(EntityManager em, List<O> entities) {
              return new BatchExecutor<O>(em, entities);  // for non-Spring use?
           }
      
          private class BatchExecutor<O extends GenericEntity> implements Runnable {
              private EntityManager em    = null;
              private List<O>         entities    = null;
      
              public BatchExecutor(EntityManager em, List<O> entities)
              {
                  this.em=em;
                  this.entities = entities;
              }
      
              @Override
              @Transactional
              public void run()
              {
                  try 
                  {
                      entities.forEach(entity->{
                          em.persist(entity);
                      });
                  }
                  catch (Exception e)
                  {
                      System.out.println("Unable to save the entities for -> {} ");
                  }
      
              }
          }
      }
      

      Spring 会将其实例化为 bean,将 getBatchExecutor() 的实现替换为每次调用构造函数时都会创建一个新的 BatchExecutor 作为 Spring bean 的实现。

      您需要使您的 BatchExecutor 成为原型范围的 bean。它已经实现了 Runnable,因此 Spring 可以在代理您的实例时使用该接口。

      【讨论】:

      • 我喜欢这个编辑 - 使用模板方法来收紧工厂方法的签名。但是我通常会看到这些默认实现为返回 null - 对于在 Spring 之外使用的情况,它是否应该返回一个新实例(如此处所示)?
      • 谢谢你..它不工作。我正在等待编辑获得批准,以便我可以提出我的问题。我在 AbstractRepositoryAdapter 类中注入了 BatchExecutorFactory。但是在 getBatchExecutor 调用期间,它没有为 BatchExecutor 提供任何 bean。你能帮我解决这个问题吗?
      猜你喜欢
      • 2017-03-15
      • 1970-01-01
      • 2016-11-12
      • 1970-01-01
      • 2015-10-24
      • 1970-01-01
      • 2018-12-02
      • 2021-07-09
      • 1970-01-01
      相关资源
      最近更新 更多