JPA @Entity 中的 Bean 注入
Posted
技术标签:
【中文标题】JPA @Entity 中的 Bean 注入【英文标题】:Bean injection inside a JPA @Entity 【发布时间】:2013-05-04 12:15:51 【问题描述】:是否可以使用 Spring 的依赖注入将 bean 注入到 JPA @Entity
?
我尝试@Autowire ServletContext,但是,虽然服务器确实启动成功,但我在尝试访问 bean 属性时收到 NullPointerException。
@Autowired
@Transient
ServletContext servletContext;
【问题讨论】:
我在将 EntityManager 注入 EntityListener 时也遇到了同样的问题,并找到了解决方案,并在另一篇帖子 ***.com/questions/22171221/… 上回答了它 【参考方案1】:是的,当然可以。您只需要确保实体也注册为 Spring 托管 bean,或者使用 <bean>
标记(在某些 spring-context.xml 中)以声明方式或通过如下所示的注释来注册。
使用注释,您可以使用@Component
(或更具体的构造型@Repository
标记您的实体,它可以为DAO 启用自动异常转换,并且可能会或可能不会干扰JPA)。
@Entity
@Component
public class MyJAPEntity
@Autowired
@Transient
ServletContext servletContext;
...
为实体完成此操作后,您需要配置它们的包(或某些祖先包)以供 Spring 扫描,以便实体作为 bean 被拾取,并且它们的依赖关系自动连接。
<beans ... xmlns:context="..." >
...
<context:component-scan base-package="pkg.of.your.jpa.entities" />
<beans>
编辑:(最终的工作原理和原因)
制作ServletContext
静态。 (删除 @Autowired)
@Transient
private static ServletContext servletContext;
由于 JPA 正在创建一个单独的实体实例,即不使用 Spring 托管 bean,因此需要共享 context。
添加一个 @PostConstruct init()
方法。
@PostConstruct
public void init()
log.info("Initializing ServletContext as [" +
MyJPAEntity.servletContext + "]");
一旦实体被实例化并通过在内部引用ServletContext
,它会触发init()
,如果尚未注入,它会强制注入静态属性。
将 @Autowired
移动到 instance 方法,但在其中设置 static 字段。
@Autowired
public void setServletContext(ServletContext servletContext)
MyJPAEntity.servletContext = servletContext;
在下面引用我的最后一条评论来回答为什么我们必须使用这些恶作剧:
由于 JPA 不使用 Spring 容器来实例化其实体,因此没有什么好方法可以做你想做的事。将 JPA 视为一个单独的 ORM 容器,它实例化和管理实体的生命周期(完全独立于 Spring),并且仅基于实体关系进行 DI。
【讨论】:
很遗憾,将@Component
添加到实体并将<context:component-scan>
添加到app-config.xml
后,servletContext
仍然为空。
使其成为静态的。 private static ServletContext servletContext;
如果它仍然是 null
通过定义 @PostConstruct public void init() log.info("Initializing ServletContext as [" + MyJPAEntity.servletContext + "]");
强制注入希望这会有所帮助。
static
后仍然为空。我想我对 @PostConstruct
的作用感到困惑。
@PostConstruct
一旦实体被实例化并通过在其中引用 servletContext 就会触发 init() ,它会强制注入所创建实例的(现在是静态的)属性。添加static
是为了确保它是共享的,因为 JPA 正在创建一个单独的实体实例,即不使用 Spring 托管 bean。
当您必须在 JPA 实体中注入其他 bean 时,我认为这是一种代码异味,我认为最好重新考虑重新设计您的实体及其周围的(包私有)类,而不是注入其他实体内的 bean。【参考方案2】:
您可以使用 @Configurable
将依赖项注入到不受 Spring 容器管理的对象中,如下所述:http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-atconfigurable。
您现在已经意识到,除非使用 @Configurable
和适当的 AspectJ 编织配置,否则 Spring 不会将依赖项注入使用 new
运算符创建的对象中。实际上,除非您从ApplicationContext
检索到它们,否则它不会将依赖项注入对象,原因很简单,它根本不知道它们的存在。即使您使用@Component
注释您的实体,该实体的实例化仍将由您或Hibernate 等框架通过new
操作执行。请记住,注释只是元数据:如果没有人解释该元数据,它不会添加任何行为或对正在运行的程序产生任何影响。
话虽如此,我强烈建议不要将ServletContext
注入实体。实体是域模型的一部分,应该与任何交付机制分离,例如基于 Servlet 的 Web 交付层。当命令行客户端或其他不涉及 ServletContext 的东西访问该实体时,您将如何使用该实体?您应该从该 ServletContext 中提取必要的数据,并通过传统方法参数将其传递给您的实体。通过这种方法,您将获得更好的设计。
【讨论】:
这个问题的两个答案都是有用的 SO 线程的完美示例(我都赞成)。虽然下面@RaviThapliyal 的回答提供了一种实现结果的方法,但您的回答提供了上下文背景(对我来说,这是一种“啊哈”体验,有助于改变我在过去几周读过的有关 Spring DI 的所有理论信息转化为实际应用的知识)。在我看来,SO 上这样的帖子还不够多,因为它们实际上教一些东西(而且通常不会为你赢得太多声望。) @sthzg 当我感到沮丧并需要重拾信心时,我会回到这里阅读您的评论。谢谢。 很好的答案。我只是想注入 gson,所以我有一个跨应用程序的实例。如果这意味着我必须更好地使用/学习 AspectJ,因为我的项目的重点是开发尽可能多的功能并尽可能多地学习,同时努力提高效率和优雅。不确定 AspectJ 是否过分杀伤力,并且仅实例化新 gson 会降低性能,但值得考虑所有事情【参考方案3】:很长一段时间后,我偶然发现了this SO answer,这让我想到了一个优雅的解决方案:
将您需要的所有@Transient @Autowired 字段添加到您的实体中 使用此自动装配字段创建 @Repository DAO:
@Autowired private AutowireCapableBeanFactory autowirer;
在您的 DAO 中,从 DB 获取实体后,调用此自动装配代码:
String beanName = fetchedEntity.getClass().getSimpleName();
autowirer.autowireBean(fetchedEntity);
fetchedEntity = (FetchedEntity) autowirer.initializeBean(fetchedEntity, beanName);
您的实体将能够像任何@Component 一样访问自动装配的字段。
【讨论】:
以上是关于JPA @Entity 中的 Bean 注入的主要内容,如果未能解决你的问题,请参考以下文章
Spring JPA/Hibernate EmptyInterceptor 不注入 Entitymanager/Spring bean
使用 JPA 时,@Entity 和 @Table 中的 name 参数有啥区别?
Spring data jpa-未定义名为“entityManagerFactory”的bean;注入自动装配的依赖项失败