Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bean injection inside a JPA @Entity

Is it possible to inject beans to a JPA @Entity using Spring's dependency injection?

I attempted to @Autowire ServletContext but, while the server did start successfully, I received a NullPointerException when trying to access the bean property.

@Autowired @Transient ServletContext servletContext; 
like image 794
theblang Avatar asked May 09 '13 21:05

theblang


People also ask

How do you inject one bean into another in Spring?

For a class annotated with @Component , specifying them is required for the autowired constructor but in a @Bean declaration you don't need to provide a parameter to specify the MyObject dependency to use (while it will work) if that is accessible in the current class, which is your case.

Is @entity a Spring bean?

Classes annotated with @Entity are JPA entities - they usually represent rows in a database. These are not Spring-managed beans, so you cannot inject them.

How do you inject beans?

In order to use the beans you create, you inject them into yet another bean that can then be used by an application, such as a JavaServer Faces application. For example, you might create a bean called Printer into which you would inject one of the Greeting beans: import javax.

Why field injection is not recommended?

The reasons why field injection is frowned upon are as follows: You cannot create immutable objects, as you can with constructor injection. Your classes have tight coupling with your DI container and cannot be used outside of it. Your classes cannot be instantiated (for example in unit tests) without reflection.


2 Answers

You can inject dependencies into objects not managed by the Spring container using @Configurable as explained here: http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-atconfigurable.

As you've realized by now, unless using the @Configurable and appropriate AspectJ weaving configuration, Spring does not inject dependencies into objects created using the new operator. In fact, it doesn't inject dependencies into objects unless you've retrieved them from the ApplicationContext, for the simple reason that it simply doesn't know about their existence. Even if you annotate your entity with @Component, instantiation of that entity will still be performed by a new operation, either by you or a framework such as Hibernate. Remember, annotations are just metadata: if no one interprets that metadata, it does not add any behaviour or have any impact on a running program.

All that being said, I strongly advise against injecting a ServletContext into an entity. Entities are part of your domain model and should be decoupled from any delivery mechanism, such as a Servlet-based web delivery layer. How will you use that entity when it's accessed by a command-line client or something else not involving a ServletContext? You should extract the necessary data from that ServletContext and pass it through traditional method arguments to your entity. You will achieve a much better design through this approach.

like image 179
Spiff Avatar answered Sep 23 '22 01:09

Spiff


Yes, of course you can. You just need to make sure the entity is also registered as a Spring managed bean either declaratively using <bean> tags (in some spring-context.xml) or through annotations as shown below.

Using annotations, you can either mark your entities with @Component (or a more specific stereotype @Repository which enables automatic exception translation for DAOs and may or may not interfere with JPA).

@Entity @Component public class MyJAPEntity {    @Autowired   @Transient   ServletContext servletContext;   ... } 

Once you've done that for your entities you need to configure their package (or some ancestor package) for being scanned by Spring so that the entities get picked up as beans and their dependencies get auto wired.

<beans ... xmlns:context="..." >   ...   <context:component-scan base-package="pkg.of.your.jpa.entities" /> <beans> 

EDIT : (what finally worked and why)

  • Making the ServletContext static. (remove @Autowired)

    @Transient private static ServletContext servletContext; 

Since, JPA is creating a separate entity instance i.e. not using the Spring managed bean, it's required for the context to be shared.

  • Adding a @PostConstruct init() method.

    @PostConstruct public void init() {     log.info("Initializing ServletContext as [" +                 MyJPAEntity.servletContext + "]"); } 

This fires init() once the Entity has been instantiated and by referencing ServletContext inside, it forces the injection on the static property if not injected already.

  • Moving @Autowired to an instance method but setting the static field inside.

    @Autowired public void setServletContext(ServletContext servletContext) {     MyJPAEntity.servletContext = servletContext; } 

Quoting my last comment below to answer why do we have to employ these shenanigans:

There's no pretty way of doing what you want since JPA doesn't use the Spring container to instantiate its entities. Think of JPA as a separate ORM container that instantiates and manages the lifecycle of entities (completely separate from Spring) and does DI based on entity relationships only.

like image 36
Ravi K Thapliyal Avatar answered Sep 20 '22 01:09

Ravi K Thapliyal