Guice 提供者<EntityManager> 与 EntityManager
Guice Provider<EntityManager> vs EntityManager
我试图使用持久性和 servlet guice 扩展,让简单的 webapp 在 Jetty 上与 Guice 和 JPA 一起工作。
我写了这个服务实现class:
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());
}
当我 运行 它起作用时,我得到了发送给客户端的名称,但是当我查看日志时,我发现没有为插入和从 postgresql 选择的 DML 生成return 任何结果,这意味着它并没有真正持久化。
我调试了代码,我看到 JpaLocalTxnInterceptor
调用了 txn.commit()
。
然后我对 PersonServiceImpl
进行了更改并使用 Provider<EntityManager>
而不是 EntityManager
并且它按预期工作。现在我真的不明白为什么,这可能是因为我不太理解 Provider 背后的想法。
在 Guice wiki page 上面写着:
Note that if you make MyService a @Singleton, then you should inject Provider instead.
但是,我的 PersonServiceImpl 不是 @Singleton,所以我不确定它为什么适用,也许是因为 Servlet?
如果你能帮我解决这个问题,我将不胜感激。
您需要 Provider<EntityManager>
,因为 Guice 的内置持久性和 servlet 扩展期望 EntityManager 具有请求范围。通过从保存在单例 servlet 中的服务中注入请求范围的 EntityManager,您将进行 范围扩大注入 ,并且 Guice 不会存储来自陈旧的、不匹配的 EntityManager 的数据。
提供商
Provider 是一个公开 get()
方法的单方法接口。如果你注入一个 Provider<Foo>
然后调用 get()
,它将 return 一个实例的创建方式与你直接注入 Foo 的方式相同。 但是,注入 Provider 允许您控制创建对象的数量以及创建时间。 This can be useful in a few cases:
- 仅在实际需要时才创建实例,尤其是在创建需要大量时间或内存的情况下
- 从同一个组件中创建两个或多个单独的实例
- 将创建推迟到初始化方法或单独的线程
- 混合作用域,如下所述
对于 X
、Provider<X>
或 @Provides X
的绑定,Guice 将自动允许您直接注入 X
或 Provider<X>
。您可以在不调整任何绑定的情况下使用 Providers,并且 Providers 在 binding annotations.
下工作正常
范围和范围扩大注入
从广义上讲,scopes define the lifetime of the object. By default, Guice creates a new object for every injection; by marking an object @Singleton, you instruct Guice to inject the same instance for every injection. Guice's servlet extensions also support @RequestScoped and @SessionScoped injections, which cause the same object to be injected within one request (or session) consistently but for a new object to be injected for a different request (or session). Guice lets you define custom scopes as well, such as 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 设为 Provider,这也可能会解决问题,但我认为最好遵循 Guice 的最佳实践并使用 Provider 显式缩小 EntityManager 的范围。)
我试图使用持久性和 servlet guice 扩展,让简单的 webapp 在 Jetty 上与 Guice 和 JPA 一起工作。
我写了这个服务实现class:
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());
}
当我 运行 它起作用时,我得到了发送给客户端的名称,但是当我查看日志时,我发现没有为插入和从 postgresql 选择的 DML 生成return 任何结果,这意味着它并没有真正持久化。
我调试了代码,我看到 JpaLocalTxnInterceptor
调用了 txn.commit()
。
然后我对 PersonServiceImpl
进行了更改并使用 Provider<EntityManager>
而不是 EntityManager
并且它按预期工作。现在我真的不明白为什么,这可能是因为我不太理解 Provider 背后的想法。
在 Guice wiki page 上面写着:
Note that if you make MyService a @Singleton, then you should inject Provider instead.
但是,我的 PersonServiceImpl 不是 @Singleton,所以我不确定它为什么适用,也许是因为 Servlet?
如果你能帮我解决这个问题,我将不胜感激。
您需要 Provider<EntityManager>
,因为 Guice 的内置持久性和 servlet 扩展期望 EntityManager 具有请求范围。通过从保存在单例 servlet 中的服务中注入请求范围的 EntityManager,您将进行 范围扩大注入 ,并且 Guice 不会存储来自陈旧的、不匹配的 EntityManager 的数据。
提供商
Provider 是一个公开 get()
方法的单方法接口。如果你注入一个 Provider<Foo>
然后调用 get()
,它将 return 一个实例的创建方式与你直接注入 Foo 的方式相同。 但是,注入 Provider 允许您控制创建对象的数量以及创建时间。 This can be useful in a few cases:
- 仅在实际需要时才创建实例,尤其是在创建需要大量时间或内存的情况下
- 从同一个组件中创建两个或多个单独的实例
- 将创建推迟到初始化方法或单独的线程
- 混合作用域,如下所述
对于 X
、Provider<X>
或 @Provides X
的绑定,Guice 将自动允许您直接注入 X
或 Provider<X>
。您可以在不调整任何绑定的情况下使用 Providers,并且 Providers 在 binding annotations.
范围和范围扩大注入
从广义上讲,scopes define the lifetime of the object. By default, Guice creates a new object for every injection; by marking an object @Singleton, you instruct Guice to inject the same instance for every injection. Guice's servlet extensions also support @RequestScoped and @SessionScoped injections, which cause the same object to be injected within one request (or session) consistently but for a new object to be injected for a different request (or session). Guice lets you define custom scopes as well, such as 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 设为 Provider,这也可能会解决问题,但我认为最好遵循 Guice 的最佳实践并使用 Provider 显式缩小 EntityManager 的范围。)