【问题标题】:Guice Provider<EntityManager> vs EntityManagerGuice Provider<EntityManager> 与 EntityManager
【发布时间】:2015-05-01 11:22:02
【问题描述】:

我试图使用持久性和 servlet guice 扩展在 Jetty 上让简单的 webapp 与 Guice 和 JPA 一起工作。

我写了这个Service实现类:

public class PersonServiceImpl implements PersonService {

private EntityManager em;

@Inject
public PersonServiceImpl(EntityManager em) {
    this.em = em;
}

@Override
@Transactional
public void savePerson(Person p) {
    em.persist(p);
}

@Override
public Person findPerson(long id) {
    return em.find(Person.class, id);
}

@Override
@Transactional
public void deletePerson(Person p) {
    em.remove(p);
}

}

这是我的 servlet(带有 @Singleton 注释):

@Inject
PersonService personService;

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String name = req.getParameter("name");
    String password = req.getParameter("password");
    String email = req.getParameter("email");
    int age = Integer.valueOf(req.getParameter("age"));


    Person p = new Person();
    p.setAge(age);
    p.setName(name);
    p.setEmail(email);
    p.setPassword(password.toCharArray());

    logger.info("saving person");

    personService.savePerson(p);
    logger.info("saved person");

    logger.info("extracting person");
    Person person = personService.findPerson(p.getId());
    resp.getWriter().print("Hello " + person.getName());
}

当我运行它时,它可以工作,并且我将名称发送到客户端,但是当我查看日志时,我看到没有为插入生成 DML,并且从 postgresql 选择不返回任何结果,这意味着它并没有真正坚持下去。

我调试了代码,发现JpaLocalTxnInterceptor 调用了txn.commit()。

然后我对PersonServiceImpl 进行了更改并使用Provider&lt;EntityManager&gt; 而不仅仅是EntityManager,它按预期工作。现在我真的不明白为什么,这可能是因为我并不真正理解 Provider 背后的想法。 在Guice wiki page 上面写着:

请注意,如果您将 MyService 设为 @Singleton,则应改为注入 Provider。

但是,我的 PersonServiceImpl 不是 @Singleton,所以我不确定它为什么适用,也许是因为 Servlet?

如果您能帮我解决这个问题,我将不胜感激。

【问题讨论】:

    标签: java jpa guice guice-servlet guice-persist


    【解决方案1】:

    您需要Provider&lt;EntityManager&gt;,因为 Guice 的内置持久性和 servlet 扩展期望 EntityManager 是请求范围的。通过从单例 servlet 中保存的服务注入请求范围的 EntityManager,您正在进行范围扩大的注入,Guice 不会存储来自陈旧、不匹配的 EntityManager 的数据。

    提供者

    Provider 是一个单一方法接口,它公开了一个get() 方法。如果你注入一个Provider&lt;Foo&gt;,然后调用get(),它将返回一个创建的实例,就像你直接注入Foo一样。 但是,注入 Provider 可以让您控制创建对象的数量以及创建时间。 This can be useful in a few cases:

    • 仅在实际需要时才创建实例,尤其是在创建需要大量时间或内存时
    • 在同一个组件中创建两个或多个单独的实例
    • 将创建推迟到初始化方法或单独的线程
    • 混合范围,如下所述

    对于X、Provider&lt;X&gt; 或@Provides X 的绑定,Guice 将自动允许您直接注入X 或Provider&lt;X&gt;。您可以在不调整任何绑定的情况下使用 Providers,并且 Providers 可以很好地与 binding annotations 配合使用。

    作用域和扩大作用域的注入

    广义上讲,scopes 定义了对象的生命周期。默认情况下,Guice 会为每次注入创建一个新对象;通过标记一个对象@Singleton,您可以指示 Guice 为每次注入注入相同的实例。 Guice 的 servlet 扩展还支持 @RequestScoped 和 @SessionScoped 注入,这会导致在一个请求(或会话)中始终注入相同的对象,但会为不同的请求(或会话)注入一个新对象。 Guice 还允许您定义自定义范围,例如 thread scope(每个线程一个实例,但在同一线程中的注入中使用相同的实例)。

    @Singleton public class YourClass {
      @Inject HttpServletRequest request;  // BAD IDEA
    }
    

    如果你直接从@Singleton 组件中注入一个请求范围的对象会发生什么?创建单例时,它会尝试注入与当前请求相关的实例。请注意,当前可能没有 个请求,但如果有,该实例将保存到单例中的一个字段中。当请求来来去去时,永远不会重新创建单例,并且永远不会重新分配字段——所以在第一次请求之后,您的组件就会停止正常工作。

    将窄范围对象 (@RequestScoped) 注入宽范围 (@Singleton) 称为范围扩大注入。并非所有扩大范围的注入都会立即显示症状,但所有可能会在以后引入挥之不去的错误。

    提供者如何提供帮助

    PersonService 没有使用@Singleton 进行注释,但是因为您在@Singleton servlet 中注入和存储了一个实例,所以它本身也可能是一个单例。这意味着 EntityManager 也有单例行为,出于同样的原因。

    根据the page you quoted,EntityManager 是短暂的,仅存在于会话或请求中。这允许 Guice 在会话或请求结束时自动提交事务,但重用同一个 EntityManager 可能会在第一次之后的任何时间阻止数据存储。切换到 Provider 允许您通过在每个请求上创建一个新的 EntityManager 来缩小范围。

    (您也可以将 PersonService 设为 Provider,这也可能解决问题,但我认为最好遵循 Guice 的最佳实践,并使用 Provider 明确缩小 EntityManager 的范围。)

    【讨论】:

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