Guice Provider <entitymanager> vs EntityManager </entitymanager>

时间:2015-02-28 21:37:36

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

我试图使用持久性和servlet guice扩展使用简单的webapp在Jetty上使用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<EntityManager>而不是EntityManager,并且按预期工作。现在我真的不明白为什么,这可能是因为我不太了解提供商背后的想法。 在Guice wiki page上说:

  

请注意,如果您将MyService设为@Singleton,那么您应该注入Provider。

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

如果你能为我解决这个问题我真的很感激。

1 个答案:

答案 0 :(得分:12)

您需要Provider<EntityManager>,因为Guice的内置持久性和servlet扩展期望EntityManager是请求范围的。通过从单个servlet中保存的服务注入请求范围的EntityManager,您可以进行范围扩展注入,并且Guice不会从过时的,不匹配的EntityManager中存储数据

提供商

Provider是一个公开get()方法的单方法接口。如果您注入Provider<Foo>然后调用get(),它将返回一个实例,其创建方式与您直接注入Foo的方式相同。 但是,通过注入提供程序,您可以控制创建对象的数量以及创建对象的时间。 This can be useful in a few cases:

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

对于XProvider<X>@Provides X的绑定,Guice会自动允许您直接注入XProvider<X>。您可以在不调整任何绑定的情况下使用提供程序,并且提供程序可以正常使用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可能会阻止数据在第一次存储之后的任何时间。切换到提供程序允许您通过在每个请求上创建一个新的EntityManager来缩小范围。

(您也可以将PersonService作为提供者,这也可能解决问题,但我认为观察Guice的最佳实践并使EntityManager的范围明确缩小提供者。)