【问题标题】:Does Guice Persist provide transaction scoped or application managed EntityManager?Guice Persist 是否提供事务范围或应用程序管理的 EntityManager?
【发布时间】:2013-08-08 17:13:17
【问题描述】:

我们使用Guice Persist在我们的项目中注入EntityManager。

例如

public class MyDao{
   @Inject
   EntityManager em;

   public void someMethod(){
       //uses em instance
   }
}

但我们不清楚EntityManager 的注入实例将如何使用。

  1. 这是什么类型的EntityManager?(参见例如:types of entity managers)Guice Persist 通过EntityManagerFactory.createEntityManager() 实例化它,所以我会说它是应用程序管理的实体管理器。但是在official Wiki 中,他们写了seesion-per-transaction 策略,这表明 EntityManager 是(伪)事务范围的。
  2. 我们是否应该手动调用 close() ?还是 Guice 会处理它?
  3. first level cache 的范围是什么? 仅单个事务(如在事务范围的实体管理器中)或只要我使用 EntityManager 的相同注入实例(如在应用程序管理的实体管理器中) ?

【问题讨论】:

    标签: jpa guice entitymanager guice-persist


    【解决方案1】:

    尽管 Piotr 完美回答了这个问题,但我想就如何使用 guice-persist 添加一些实用建议。

    我遇到了一些很难调试的问题。在我的应用程序中,某些线程会显示过时的数据,有时EntityManager 实例会留下旧的死数据库连接。根本原因在于我使用@Transactional 注释的方式(我只将它们用于执行更新/插入/删除的方法,而不是用于只读方法)。只要您在注入的Provider<EntityManager> 实例上调用get(),guice-persist 就会将EntityManager 实例存储在ThreadLocal 中(我是为只读方法执行的)。但是,仅当您还调用 UnitOfWork.end() 时才会删除此 ThreadLocal(如果 @Transactional 在方法上,则通常由拦截器完成)。不这样做会将 EM 实例留在 ThreadLocal 中,因此最终线程池中的每个线程都会有一个旧的 EM 实例,其中包含过时的缓存实体。

    所以,只要你遵守以下简单规则,guice-persist 的使用就很简单了:

    1. 始终注入 Provider<EntityManager> 而不是直接注入 EntityManager
    2. 对于事务范围的语义:始终使用@Transactional 注释每个方法(即使是只读方法)。这样,JpaLocalTxnInterceptor 将拦截对带注释的方法的调用,确保不仅可以启动和提交事务,还可以在 ThreadLocal 中设置和取消设置 EM 实例。
    3. 对于请求范围的语义:使用 guice-persist 附带的 PersistFilter servlet 过滤器。它会在请求完成前后为您调用UnitOfWork 上的begin()end(),从而填充和清理ThreadLocal。

    希望这会有所帮助!

    【讨论】:

    • 很好的建议 RE 使用事务。我正在做完全相同的事情,所以你刚刚解决了我的问题。
    • 同上 - 浪费了很多时间。 @Transactional 为我解决了这个问题。
    • 这是 Guice-persist 的一个巨大设计缺陷!即使是一个小错误(在 @Transactional 之外使用 EntityManager)也会使您的整个项目因难以解决的错误而损坏……例如;由于挂起/陈旧的网络连接,我的 Google App Engine 项目在八小时后开始生成 JDBC 连接超时。原因(经过长时间调试)是一个“pEntityManagerProvider.get()”在实用程序类中没有@Transactional ...
    【解决方案2】:

    我对 Guice-persist 的源代码进行了一些研究,并阅读了 Guice-persist wiki 页面下的 cmets,这些是我需要的答案:

    1 .如果通过@Inject EntityManager 注入,EntityManager 的生命周期管理就会被破坏。正如 Wiki 上的其中一个 cmets 所述:

    我确认直接注入 EntityManager 而不是提供程序 可能很危险。如果您不在 UnitOfWork 或方法中 用@Transaction注解,第一次注入一个EntityManager 在一个线程中将创建一个新的 EntityManager,从不销毁它,并且 总是为这个线程使用这个特定的 EntityManager(EM 被存储 线程本地)。这可能会导致可怕的问题,例如注入 死entityManager(连接关闭等)所以我的建议是 总是注入一个提供者,或者至少注入 仅在打开的 UnitOfWork 中直接使用 EntityManager。

    所以我的问题中的例子不是最正确的用法。它创建 EntityManager 的单例实例(每个线程),并将这个实例注入到任何地方:-(.

    但是,如果我已经注入 Provider 并在 @Transactional 方法中使用它,那么 EntityManager 的实例将是每个事务的。所以这个问题的答案是:如果注入和使用正确,实体管理器是事务范围的

    2 。 如果注入和使用正确,那么我不需要手动关闭实体管理器(guice-persist 会为我处理)。如果使用不正确,手动关闭将是一个非常糟糕的主意(当我 @Inject EntityManager 时,EntityManager 的关闭实例将被注入到每个地方)

    3 . 如果注入和使用正确,则 L1 缓存的范围是单个事务。如果使用不当,L1缓存的范围就是应用的生命周期(EntityManager是单例的)

    【讨论】:

      【解决方案3】:

      1。这取决于您的模块配置。有一些基本的绑定:

      JpaPersistanceService

      public class JpaPersistanceService implements Provider<EntityManager> {
      
        private EntityManagerFactory factory;
      
        public JpaPersistanceService(EntityManagerFactory factory) {
          this.factory = factory;
        }
      
        @Override
        public EntityManager get() {
          return factory.createEntityManager();
        }
      }
      

      模块绑定

      EntityManagerFactory factory = Persistence.createEntityManagerFactory(getEnvironment(stage));
      bind(EntityManager.class).annotatedWith(Names.named("request")).toProvider(new JpaPersistanceService(factory)).in(RequestScoped.class);
      bind(EntityManager.class).annotatedWith(Names.named("session")).toProvider(new JpaPersistanceService(factory)).in(SessionScoped.class);
      bind(EntityManager.class).annotatedWith(Names.named("app")).toProvider(new JpaPersistanceService(factory)).asEagerSingleton;
      

      用法

      @Inject @Named("request")
      private EntityManager em; //inject a new EntityManager class every request
      
      @Inject @Named("session")
      private Provider<EntityManager> emProvider; //inject a new EntityManager class each session
      //This is little bit tricky, cuz EntityManager is stored in session. In Stage.PRODUCTION are all injection created eagerly and there is no session at injection time. Session binding should be done in lazy way - inject provider and call emProvider.get() when em is needed;
      
      @Inject @Named("application")
      private EntityManager em; //inject singleton
      

      2。是的,你应该或者你会使用JpaPersistModule[javadoc]

      3。好吧,这是关于 persistence.xml 和 EntityManager 范围内的 JPA 配置

      【讨论】:

        【解决方案4】:

        我正在注入提供程序....但我怀疑有问题。 当我尝试重新部署应用程序时,我必须重新启动服务器,因为 JPA 类已被缓存。

        它发生了以下伪错误

        https://bugs.eclipse.org/bugs/show_bug.cgi?id=326552

        理论上,通过注入 Provider 并获取 EntityManager 的实例,您不应该关闭任何东西 ....

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-02
          • 1970-01-01
          相关资源
          最近更新 更多