Can I inject an JPA EntityManager using CDI and @PersistenceContext, like with Spring?
Asked Answered
E

2

6

In Spring, I can inject an javax.persistence.EntityManager into a Spring bean using the annotation @javax.persistence.PersistenceContext, like this:

@Service
public class MyRepository {
    @PersistenceContext
    private EntityManager entityManager;
}

This is documented in the Spring docs in chapter 20.5.2 Implementing DAOs based on plain JPA.

Is there a way to do this using CDI (specifically, Weld) if I am not using a Java EE container?

In particular, is it possible to reuse the annotation @PersistenceContext for CDI (because existing code uses it with Spring) ?

As far as I understand: When using a Java EE container, the container will interpret the annotation and inject an EntityManager. Is that correct? And is there a way to get this to work using Weld, but without a Java EE container?


I tried to inject the class above into another class using Weld (in Tomcat, without Java EE). The injection takes place, so Weld correctly creates an instance of MyRepository, however the field MyRepository.entityManager is null, as if the annotation @PersistenceContext was ignored.

What is happening (or rather, not happening) here?

Estellaestelle answered 7/11, 2016 at 15:46 Comment(4)
I am particularly interested to know if CDI / Weld can use the annotation @PersistenceContext. I clarified my question.Estellaestelle
I do not have an answer ready, but the way I would choose to go is with a CDI extension (see also this example). It is not as intimidating as it seems at first, though a bit of research and experimentation would be required :)Lubet
@NikosParaskevopoulos: Thank you for the pointer to CDI extensions. That looks like it could work. However, in this case I don't think it makes sense to create a custom solution. If there is no simple, supported way to use @PersistenceContext with CDI, I'll probably just use the standard way everyone recommends (@Inject plus Producer).Estellaestelle
That is great! (For the sake of completeness: the extension way would be more appropriate if you were reusing/sharing code between the server and some client. If you are in full control of the code, then the Producer is much simpler.)Lubet
R
5

While MilkMaid's answer is a sound way to go, I would just add a few more behind-the-scenes hints.

Is there a way to do this using CDI (specifically, Weld) if I am not using a Java EE container?

Short answer - there is. Weld can allow for injection of next to any object you wish to have injectable, however you need to mind who owns/manages this object. In your case, it is EntityManager which is stuff from JPA, so guess what - JPA manages the lifecycle of such object. Therefore you need to create a "wrapper" (in reality its a proxy) for such object which will be handled by CDI/Weld. And that what you need producer for.

as if the annotation @PersistenceContext was ignored.

Indeed, it is ignored. Here is a simplification of what happens. Normally, that is in EE container, Weld would make the injection happen but the real magic behind that is not a Weld core code, but rather an integration on the EE server side (who adopts Weld SPI to handle such annotations and turn them into beans which it then injects).

Generally speaking, trying to handle 'EE stuff' outside of EE containers might get tricky as you come across a lot of integration which originally happens inside the container, but you now need to handle that yourself.

Technically one could probably make the @PersistenceContext annotation work, maybe by writing a CDI extension. However, that is a nasty hack rather than anything else - one would be bending the EE-only annotation for SE usage. I would advise against it, but if you still want to go that way, the idea would be to basically make CDI think the @PersistanceContext is yet another @Inject plus provide the producer. This would mean a LOT of manual work, there is no in-built mechanism in CDI to handle that for you - this is normally the EE server's responsibility.

Redaredact answered 8/11, 2016 at 7:45 Comment(5)
Thanks for the background information. Just to be clear: Is there a way to make CDI / Weld understand the annotation @PersistenceContext? That would allow using existing classes unchanged. Or is there simply no mechanism in CDI that supports that annotation (because it is a JPA annotation)?Estellaestelle
Hm, not really sure here. I would say it could be possible with some extension tricks. But it really sounds like a nasty hack rather than anything else - you would be bending the EE-only annotation for SE usage which just sounds bad to me. I would advise against it, but if you still want to go that way, the idea would be to basically make CDI think the @PersistanceContext is yet another @Inject plus provide the producer. This would mean a LOT of manual work, there is no in-built mechanism in CDI to handle that for you. As I said this is EE SERVER responsibility to make such things work.Redaredact
Thank you for the perspective. I was hoping for a simple, out-of-the-box solution like available with Spring. If it is such a pain to make @PersistcnceContext work, I'll probably try a different, more standard approach. Usually it's a good idea to use the common, tested method after all.Estellaestelle
I took the liberty of editing your comment into the answer. Feel free to edit if you disagree.Estellaestelle
I am fine with the edit. And +1 for choosing more standard approach.Redaredact
A
8

You can do it this way: create Entity Manager Factory Producer

public class EntityManagerFactoryProducer {

    @Produces
    @ApplicationScoped
    public EntityManagerFactory create() {
        return Persistence.createEntityManagerFactory("PU");
    }

    public void destroy(@Disposes EntityManagerFactory factory) {
        factory.close();
    }

}

and create Entity Manager Producer

public class EntityManagerProducer {

    @Inject
    transient EntityManagerFactory emf;

    @Produces
    @RequestScoped
    public EntityManager create() {
        return emf.createEntityManager();
    }

    public void destroy(@Disposes EntityManager em) {
        em.close();
    }
}

then you can use dependency injection (CDI will create one em for each request)

@Inject
EntityManager entityManager;

You have to start the CDI context in your main method

public static void main(String[] args) throws IOException {
    Weld weld = new Weld();
    WeldContainer container = weld.initialize();
    Application application = container.instance().select(Application.class).get();
    application.run();
    weld.shutdown();
}

ps. If you have more then one PU use @Qualifier

Appendectomy answered 8/11, 2016 at 5:41 Comment(1)
You should not use @Disposes for cleanup: Unfortunately, many people do use a @Disposes method to close the container-managed entity manager. (...). more here: #19431923Melpomene
R
5

While MilkMaid's answer is a sound way to go, I would just add a few more behind-the-scenes hints.

Is there a way to do this using CDI (specifically, Weld) if I am not using a Java EE container?

Short answer - there is. Weld can allow for injection of next to any object you wish to have injectable, however you need to mind who owns/manages this object. In your case, it is EntityManager which is stuff from JPA, so guess what - JPA manages the lifecycle of such object. Therefore you need to create a "wrapper" (in reality its a proxy) for such object which will be handled by CDI/Weld. And that what you need producer for.

as if the annotation @PersistenceContext was ignored.

Indeed, it is ignored. Here is a simplification of what happens. Normally, that is in EE container, Weld would make the injection happen but the real magic behind that is not a Weld core code, but rather an integration on the EE server side (who adopts Weld SPI to handle such annotations and turn them into beans which it then injects).

Generally speaking, trying to handle 'EE stuff' outside of EE containers might get tricky as you come across a lot of integration which originally happens inside the container, but you now need to handle that yourself.

Technically one could probably make the @PersistenceContext annotation work, maybe by writing a CDI extension. However, that is a nasty hack rather than anything else - one would be bending the EE-only annotation for SE usage. I would advise against it, but if you still want to go that way, the idea would be to basically make CDI think the @PersistanceContext is yet another @Inject plus provide the producer. This would mean a LOT of manual work, there is no in-built mechanism in CDI to handle that for you - this is normally the EE server's responsibility.

Redaredact answered 8/11, 2016 at 7:45 Comment(5)
Thanks for the background information. Just to be clear: Is there a way to make CDI / Weld understand the annotation @PersistenceContext? That would allow using existing classes unchanged. Or is there simply no mechanism in CDI that supports that annotation (because it is a JPA annotation)?Estellaestelle
Hm, not really sure here. I would say it could be possible with some extension tricks. But it really sounds like a nasty hack rather than anything else - you would be bending the EE-only annotation for SE usage which just sounds bad to me. I would advise against it, but if you still want to go that way, the idea would be to basically make CDI think the @PersistanceContext is yet another @Inject plus provide the producer. This would mean a LOT of manual work, there is no in-built mechanism in CDI to handle that for you. As I said this is EE SERVER responsibility to make such things work.Redaredact
Thank you for the perspective. I was hoping for a simple, out-of-the-box solution like available with Spring. If it is such a pain to make @PersistcnceContext work, I'll probably try a different, more standard approach. Usually it's a good idea to use the common, tested method after all.Estellaestelle
I took the liberty of editing your comment into the answer. Feel free to edit if you disagree.Estellaestelle
I am fine with the edit. And +1 for choosing more standard approach.Redaredact

© 2022 - 2024 — McMap. All rights reserved.