[英]Guice Provider<EntityManager> vs EntityManager
我试图使用持久性和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维基页面上,它说:
请注意,如果您将MyService设为@Singleton,那么您应该注入Provider。
但是,我的PersonServiceImpl不是@Singleton所以我不确定它为什么适用,也许是因为Servlet?
如果你能为我清楚这一点,我将非常感激。
您需要Provider<EntityManager>
因为Guice的内置持久性和servlet扩展期望EntityManager是请求范围的。 通过从单个servlet中保存的服务注入请求范围的EntityManager,您将进行范围扩大注入 ,并且Guice不会存储来自陈旧,不匹配的EntityManager的数据。
Provider是一个公开get()
方法的单方法接口。 如果您注入Provider<Foo>
然后调用get()
,它将返回一个创建的实例,就像您直接注入Foo一样。 但是,注入Provider允许您控制创建的对象数量以及创建时间。 这在以下几种情况下很有用:
对于X
, Provider<X>
或@Provides X
,Guice将自动允许您直接注入X
或Provider<X>
。 您可以在不调整任何绑定的情况下使用提供程序,并且提供程序可以正常使用绑定注释 。
从广义上讲, 范围定义了对象的生命周期。 默认情况下,Guice为每次注入创建一个新对象; 通过标记对象@Singleton,您可以指示Guice为每次注入注入相同的实例。 Guice的servlet扩展还支持@RequestScoped和@SessionScoped注入,这会导致在一个请求(或会话)中一致地注入相同的对象,但是为不同的请求(或会话)注入新对象。 Guice还允许您定义自定义作用域,例如线程作用域 (每个线程一个实例,但在同一个线程中的注入中使用相同的实例)。
@Singleton public class YourClass {
@Inject HttpServletRequest request; // BAD IDEA
}
如果直接从@Singleton组件中注入请求范围的对象会发生什么? 创建单例时,它会尝试注入与当前请求相关的实例。 请注意,有可能不是一个当前请求,但如果有一个实例将在单被保存到一个字段。 随着请求的来来去去,单例永远不会被重新创建,并且永远不会重新分配字段 - 所以在第一次请求后,组件就会停止正常工作。
将窄范围对象(@RequestScoped)注入宽范围(@Singleton)称为范围扩大注入 。 并非所有扩大范围的注射都会立即出现症状,但所有这些都可能会在以后引入挥之不去的错误。
PersonService没有用@Singleton注释,但是因为你在@Singleton servlet中注入和存储一个实例,所以它本身也可能是一个单例。 这意味着EntityManager也有单例行为,原因相同。
根据您引用的页面 ,EntityManager意味着短暂存在,仅存在于会话或请求中。 这允许Guice在会话或请求结束时自动提交事务,但重用相同的EntityManager可能会阻止数据在第一次存储之后的任何时间。 切换到提供程序允许您通过在每个请求上创建新的EntityManager来缩小范围。
(您也可以将PersonService设置为提供者,这也可能解决问题,但我认为最好观察Guice的最佳实践并使用提供者明确缩小EntityManager的范围。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.