How to handle persistence context (EntityManager)

2020-07-17 06:26发布

问题:

I am using jax-rs restful web service in my application with sub-resource locators. However after passing entityManager to sub-resource I cannot persist any new objects in this sub-resource.

The entityManager lets me however to query it for data.

This is my main resource:

@Path("/registrations")
@Stateless
public class RegistrationsResource {

    @Context
    private UriInfo context;

    @PersistenceContext(unitName="pctx")
    private EntityManager em;

    public RegistrationsResource() {
    }

    //POST method ommited

    @Path("{regKey}")
    public RegistrationResource getRegistrationResource(@PathParam("regKey")
    String regKey) {
        return RegistrationResource.getInstance(regKey, em);
    }

}

And this is my sub-resource:

public class RegistrationResource {

    private String regKey;
    private EntityManager em;

    private RegistrationResource(String regKey, EntityManager em) {
        this.regKey = regKey;
        this.em = em;
    }

    @Path("securityQuestion")
    @GET
    public String getQuestion() {
        return "iamahuman"+regKey;
    }

    @Path("securityQuestion")
    @POST
    public void postSecurityAnswer(String answer) {
        if(!answer.equals("iamahuman"+regKey)){
            throw new WebApplicationException(Status.BAD_REQUEST);
        }

        //Getting this information works properly
        List<RegistrationEntity> result = em.createNamedQuery("getRegistrationByKey")
            .setParameter("regKey", regKey).getResultList();

        switch(result.size()){
            case 0 :
                throw new WebApplicationException(Status.NOT_FOUND);
            case 1:
                break;
            default:
                throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
            }

            RegistrationEntity reg = result.get(0);
            UserEntity newUser = new UserEntity();

            newUser.setHashedPassword(reg.getPwHash(), reg.getSalt());
            newUser.setUsername(reg.getUsername());
            newUser.setName(reg.getName());
            newUser.setSurname(reg.getSurname());

            //CRASHES HERE
            em.persist(newUser);
    }
}

As you can see it takes registration object from database, creates new user for registration and tries to persist it. However, em.persist(newUser) throws TransactionRequiredException.

My question is: how should I pass EntityManager to sub-resource so it can properly persist new objects?

回答1:

Sorry for digging this up again, but I suggest the following:

  • Annotate also the sub-resource as a @Stateless EJB
  • Place @EJB injection member fields into the parent resource class, like so:
        @EJB private RegistrationResource registrationResource;
    
  • in "getRegistrationResource()", do not call the constructor of the sub-resource, but return the injected EJB reference:
    public RegistrationResource getRegistrationResource() {
        return this.registrationResource;
    }

For this to work, however, you cannot pass the "@PathParam" as a constructor parameter. You would have to access it seperately in the subresource via the "@Context" or another @Path declaration.
This enables you to inject the EntityManager in the sub resource in exactly the same way as in the parent resource, you don't need to pass it on.



回答2:

Probably too late but anyway... When you return your sub resource you 'leave' the stateless bean. As the container manages the transaction the transaction is committed when you return from RegistrationsResource.

Jersey will then construct your sub resource but its not a stateless bean so you wont have a container managed transaction. Hence the exception.

I would advice you to put your business logic in a new class which you then make a stateless bean. Here you do all your database stuff which then is always handled in a container managed transaction.