hibernate 如何防止EntityManager.persist在手动刷新调用之前提交到数据库,并且不需要@Transactional?

mwecs4sa  于 2023-03-03  发布在  其他
关注(0)|答案(1)|浏览(167)

我对Hibernate如何处理一些事情有点困惑。我有一个仓库类,其中有一个方法使用了EntityManager中的persist,因此它被添加到托管状态和持久性上下文。然后我有一个单独的方法,带有@Transactional注解,仅调用flush来手动提交EntityManager中当前的所有内容。
我这样做是因为我正在处理非常大的数据集,并且在存储它们之前已经必须完全或部分地迭代它们几次,所以我想通过 “将它们添加到EntityManager” 来保存迭代,而我已经在迭代它们并且它们已经完全构建,因此,我不必在以后的repository类中再次遍历完整列表,以便对每个实体调用persist

@ApplicationScoped
public class DataRepository {
    private EntityManager entityManager;
    private int totalAmountOfCachedData = 0;

    @Inject
    public DataRepository(EntityManager entityManager) {
        this.entityManager = entityManager;
        // This line does not change anything, with or without the same result
        this.entityManager.setFlushMode(FlushModeType.COMMIT);
    }

    public int getTotalAmountOfCachedData() {
        return totalAmountOfCachedData;
    }

    public void add(Data data) {
        setReferences(data);
        entityManager.persist(data);
        totalAmountOfCachedData++;
    }

    @Transactional
    public void commitCurrentDataToDb() {
        entityManager.flush();
        totalAmountOfCachedData = 0;
    }

    private void setReferences(Data data) {
        //... running a few EntityManager.getReference here
    }
}

我的理解是persist实际上并不把实体保存到数据库中,只有当flush被调用的时候,而且,据我所知,@Transactional在方法周围 Package 了一个带有commit的自动事务,这是我不想在前一个中使用的。我在使用我的方法add()时遇到了一个异常,询问是否要添加一个事务,但是如果我添加了一个事务,那么每次调用时都会自动将它们提交到数据库中,对吗?

javax.persistence.TransactionRequiredException: Transaction is not active, consider adding @Transactional to your method to automatically activate one.
        at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.persist(TransactionScopedSession.java:142)
        at org.hibernate.engine.spi.SessionLazyDelegator.persist(SessionLazyDelegator.java:241)
        at org.hibernate.Session_5b93bee577ae2f8d76647de04cfab36afbf52958_Synthetic_ClientProxy.persist(Unknown Source)
        at com.test.entity.repository.DataRepository.add(DataRepository.java:36)

此外,我尝试将EntityManager的刷新模式设置为 “COMMIT” 而不是 “AUTO”,但没有效果。如何让add()方法在不执行插入的情况下将实体添加到EntityManager中,并且只在使用commitCurrentDataToDb()的单独调用中执行此操作?

tcomlyy6

tcomlyy61#

我理解你的问题:您可能希望填充数据集,然后偶尔根据不同的用户请求刷新该数据。
这导致了扩展的持久性上下文......这在2007年和完全服务器端的应用程序(如JSF)中是一件事,而现在人们在Spring或任何其他无状态微服务世界中通常不会这样做。
我所看到的是-您正在尝试混合服务层(应用程序SPI)和持久层(应用程序实现)。
我建议您重新组织代码:从而引入有状态服务。

@ApplicationScoped
public class DataService {

   private final DataRepository repository; 

   private List<Data> currentDataSet;

   private ReentrantLock lock = new ReentrantLock();

   @Inject
   public DataService(DataRepository dataRepository) {
      this.repository = repository;
      this.currentDataSet = new LinkedList<>();
   }

   public void add(Data data) {
       lock.lock();
       try {
          this.currentDataSet.add(data);
       } finally {
          lock.unlock();
       }
   }

   @Transactional
   public void flush() {
      lock.lock();

      final List<Data> dataToPersist;

      try {
         dataToPersist = new ArrayList<>(this.currentDataSet);
         dataToPersist.forEach(repository::save);
         dataToPersist.flush();
         this.currentDataSet = new LinkedList<>();
      } finally {
         lock.unlock()
      }
    }
}

并且您的DataRepository应该实现2个方法:

save(Data data) {
   setReferences(data);
   entityManager.perist(data);
}

flush() {
   entityManager.flush();
}

有了这个,您将使用DataService进行操作,持久层将隐藏在DataService客户端的某个位置。
但是这种写入方式会使你的应用程序无法横向扩展,那么你最好考虑将DataService中的数据写入一些共享的内存存储,比如Hazelcast或Redis,而不是当前jvm-memory中的currentDataSet列表,这样你就可以实现可扩展性。
还有一个提示-〉尽管在持久性、SPI和API中拥有相同的数据是一个很好的承诺--但在实践中,它在原始用例中效果很好,对于更严重的情况,您应该为API、服务和持久性层以及相应的Map使用单独的数据模型。是的,这就是EE Java --从长远来看,它是值得的。作为第一个开始,我建议只使用2:API(与服务共享)和持久层数据模型。

相关问题