@PersistenceContext is not applicable to parameters. How to inject EntityManager through a constructor?
Asked Answered
G

3

7

It is a best practice to use constructor injection. However I can't acheive this with @PersistenceContext.

I would like to have the following constructor:

private final EntityManager entityManager;

@Autowired
public MyService(@PersistenceContext EntityManager entityManager) {
    this.entityManager = entityManager;
}

But I can't since @PersistenceContext is only applicable to TYPE, METHOD and FIELD.

Q: How do I inject a container-managed EntityManager through constructor injection?

Goosegog answered 1/2, 2019 at 11:51 Comment(3)
I'm not quite sure what you mean, doesn't dropping the @PersistenceContext work for you? Injecting an EntityManager via @Autowired should work just as wellCovalence
@Covalence will it inject the same entity manager that is injected with @PersistenceContext? I can't find any documentation proving this.Goosegog
if you're using spring boot : probably yes because spring is your container. If you're using spring MVC within some application server : probably not because @Autowired searches for any matching bean, meaning you might very well catch the container-managed EntityManager. You can make sure to get your own if you specify a custom EntityManagerFactoryArchaeopteryx
A
4

You seem to be using spring so your solution will be rather easy :

@Component
@Scope("prototype")
public class MyPersistenceContainer
{
@PersistenceContext
private EntityManager em;

public EntityManager getEntityManager()
{
return em;
}
}

And now you can simply inject an instance of this class in your constructor, it will always hold a valid EntityManager (because of the bean scope). Mind you : in a web environment you probably should use @SessionScope or even @RequestScope instead of prototype, this will save resources


But there is something to consider :

When using singleton-scoped beans that have dependencies on beans that are scoped as prototypes, please be aware that dependencies are resolved at instantiation time. This means that if you dependency inject a prototype-scoped bean into a singleton-scoped bean, a brand new prototype bean will be instantiated and then dependency injected into the singleton bean... but that is all. That exact same prototype instance will be the sole instance that is ever supplied to the singleton-scoped bean, which is fine if that is what you want.

However, sometimes what you actually want is for the singleton-scoped bean to be able to acquire a brand new instance of the prototype-scoped bean again and again and again at runtime. In that case it is no use just dependency injecting a prototype-scoped bean into your singleton bean, because as explained above, that only happens once when the Spring container is instantiating the singleton bean and resolving and injecting its dependencies. If you are in the scenario where you need to get a brand new instance of a (prototype) bean again and again and again at runtime, you are referred to the section entitled Section 4.3.7, “Method Injection”

So if you want to inject your "entity manager container-bean" into singleton beans (which is the default scope), have a look at https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-method-injection


Its rather important to set your scopes correctly, otherwise you might have (and will have) database inconsistencies, deadlocks or worse

Archaeopteryx answered 1/2, 2019 at 12:2 Comment(5)
Please, notice that the question is "How do I inject a container-managed EntityManager through constructor injection?", Not how create a request scope component. , here are some advantages Using constructors instead of field dep injectionDinar
I'm sorry, I understood it now. You talked about session scope but aren't all spring components Singleton by default? I work with web apps and had no issues on that, because these components are stateless, EntityManager and JdbTemplate have some state but they use ThreadLocal for thisDinar
thats exactly the problem - singleton beans can hold a state and webapps should be stateless - this will cause the possible re-use of a cached EntityManager if you dont select your scopes correctly. Re-using entitymanagers generally is a bad idea, it will cause lost updates and concurrency issues. Also : most containers will close the entitymanager after the DB-session has ended, in which case it wont even be possible to use it anymoreArchaeopteryx
Thank you for the answer, but in your solution you still inject entity manager through field injection which is exactly want I want to get rid of in my code.Goosegog
you can exchange the field injection for method injection or even do a programmatic container lookup but i dont get the point - CDI is extremely stable, proven and efficient nowadays, it will be really hard to beat it; even spring CDI is pretty much as good as it getsArchaeopteryx
O
0

It sould be possible using Spring Data. But if you would not like to use Spring Data in your project for some reason (e.g. you're just making a legacy project a bit better), you can create the following FactoryBean to make EntityManager injectable via constructor injection:

/**
 * Makes the {@link EntityManager} injectable via <i>@Autowired</i>,
 * so it can be injected with constructor injection too.
 * (<i>@PersistenceContext</i> cannot be used for constructor injection.)
 */
public static class EntityManagerInjectionFactory extends AbstractFactoryBean<EntityManager> {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Class<?> getObjectType() {
        return EntityManager.class;
    }

    @Override
    protected EntityManager createInstance() {
        return entityManager;
    }

}

Please note, that because we use the @PersistenceContext annotation internally, the returned EntityManager will be a proper thread-safe proxy, as it would have been injected directly at the place of usage with field injection.

Octagon answered 6/2, 2021 at 10:6 Comment(0)
G
0

Spring documentation is pretty clear on this:

@PersistenceContext-style shared EntityManager reference is not available for regular dependency injection out of the box. In order to make it available for type-based matching as required by @Autowired, consider defining a SharedEntityManagerBean as a companion for your EntityManagerFactory definition:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
</bean>

<bean id="em" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
    <property name="entityManagerFactory" ref="emf"/>
</bean>

Alternatively, you may define an @Bean method based on SharedEntityManagerCreator:

@Bean("em")
public static EntityManager sharedEntityManager(EntityManagerFactory emf) {
    return SharedEntityManagerCreator.createSharedEntityManager(emf);
}

After this kind of configuration, following should work

private final EntityManager entityManager;

@Autowired
public MyService(EntityManager entityManager) {
    this.entityManager = entityManager;
}

If there are multiple EntityManagers you can discern between them using @Qualifier.

Goodhen answered 27/6 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.