0

I have a logging service within a JEE environment which uses TransactionManagementType.BEAN because the logging should be independent from JTA transactions, so I have to deal with transactions by myself.

Currently this logging service looks like this:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class LoggingService
{
    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;


    @PersistenceUnit(unitName = "MyLoggingUnit")
    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory)
    {
        this.entityManagerFactory = entityManagerFactory;
    }

    @PostConstruct
    private void init()
    {
        entityManager = entityManagerFactory.createEntityManager();
    }

    private Logger logger = Logger.getLogger(LoggingService.class.getSimpleName());

    public void log(LogLevel logLevel, String userId, String message)
    {
        LogEntry logEntry = new LogEntry(userId, message, logLevel);
        try
        {
            entityManager.getTransaction().begin();
            entityManager.persist(logEntry);
            entityManager.getTransaction().commit();
        }
        catch (Exception e)
        {
            entityManager.getTransaction().rollback();
            logger.log(Level.ERROR, e.getMessage(), e);
        }
        finally
        {
            entityManager.close();
        }
    }

    public LastVisit getLastVisit(String userId)
    {
        LastVisit result = null;
        try
        {
            entityManager.getTransaction().begin();
            result = entityManager.find(LastVisit.class, userId);
            entityManager.getTransaction().commit();
        }
        catch (Exception e)
        {
            entityManager.getTransaction().rollback();
            logger.log(Level.ERROR, e.getMessage(), e);
        }
        finally
        {
            entityManager.close();
        }

        return result;
    }

    public void setLastVisit(LastVisit lastVisit)
    {
        try
        {
            entityManager.getTransaction().begin();
            entityManager.persist(lastVisit);
            entityManager.getTransaction().commit();
        }
        catch (Exception e)
        {
            entityManager.getTransaction().rollback();
            logger.log(Level.ERROR, e.getMessage(), e);
        }
        finally
        {
            entityManager.close();
        }
    }
}

Here is the endpoint which uses this service:

@Path("/recipe")
@Produces(MediaType.APPLICATION_JSON)
public class RecipeEndpoint
{
    private RecipeService recipeService;
    private LoggingService loggingService;

    @EJB
    public void setRecipeService(RecipeService recipeService)
    {
        this.recipeService = recipeService;
    }

    @EJB
    public void setLoggingService(LoggingService loggingService)
    {
        this.loggingService = loggingService;
    }

    @POST
    @Path("/add")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response addRecipe(RecipeBo recipeBo)
    {
        loggingService.log(LogLevel.OK, recipeBo.getUserId(), "addRecipe: " + recipeBo);

        try
        {
            recipeService.addRecipe(recipeBo);
            loggingService.log(LogLevel.OK, recipeBo.getUserId(), "recipe successfully added: " + recipeBo);
            return Response.ok().entity(new ResponseObject()).build();
        }
        catch (BadRequestException e)
        {
            ResponseObject responseObject = new ResponseObject();
            responseObject.registerException(RecipeService.SAFE_LOCK_TRIGGERED_TEXT);
            return Response.status(Status.BAD_REQUEST).entity(responseObject).build();
        }
        catch (Exception e)
        {
            loggingService.log(LogLevel.ERROR, recipeBo.getUserId(), "an error occured while adding a recipe: " + ExceptionToStringMapper.map(e));
            return Response.status(Status.INTERNAL_SERVER_ERROR).entity(new ResponseObject(e)).build();
        }
    }
}

When I add a recipe, the log method is invoked twice. I can assure that since I have two entries in database, so it works as I need it but when I read the doc of the entityManager.close() method:

Close an application-managed entity manager. After the close method has been invoked, all methods on the EntityManager instance and any Query and TypedQuery objects obtained from it will throw the IllegalStateException except for getProperties, getTransaction, and isOpen (which will return false). If this method is called when the entity manager is associated with an active transaction, the persistence context remains managed until the transaction completes.

Throws: IllegalStateException - if the entity manager is container-managed

...this actually must not work and throw an IllegalStateException since I use the same EntityManager twice. (the first time log is invoked, calls persist() and close(), then log is invoked again and calls persist() and close() again). I want to understand why it works and does not throw an exception to not get any bad surprises in future.

Bevor
  • 8,396
  • 15
  • 77
  • 141
  • 1
    You assume the same stateless-bean instance is used for the second request, but that does not have to be true. Try verifying by printing a hashcode or something like that. See also https://developer.jboss.org/thread/201535 – vanOekel Aug 30 '15 at 13:07
  • Thanks, although the topic of the thread is not exactly the same, because he talks about the whole bean. In my case I have one request of the `RecipeEndpoint` which executes `loggingService.log()` twice. Nevertheless, I still don't exactly know why this works, because the `LoggingService` gets injected at the request of `addRecipe()` and then log is invoked twice.But when I print the hash code of the `LoggingService` instance in log method, it's really not the same instance. I'm very confused about that because in my opinion the `LoggingService` instance should live as long as the method runs? – Bevor Aug 31 '15 at 18:20
  • 1
    That's the thing (and somewhat confirmed by the different hashcodes): you do not get an instance of the LoggingService, you get an instance of a proxy to a pool of implementation instances. Each time you invoke a method on the instance, a request goes out to the pool to fetch a "LoggingService implementation instance" and execute the method on the returned implementation instance. In all, a lot happens under water with the annotations you use. You should be able to see some of that when you step through the code in debugging mode. – vanOekel Aug 31 '15 at 21:24
  • Ok, one last question. Can I rely on getting always a new instance, because if I don't, it would cause an exception. – Bevor Sep 01 '15 at 06:44
  • 1
    No, the pool decides that based on how full the pool is, how many concurrent requests there are, validation/time-out of beans in the pool, etc.. To prevent the exception, remove the "PostConstruct" and follow [this idiom](https://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/transactions.html#transactions-demarcation-nonmanaged). – vanOekel Sep 01 '15 at 08:51

0 Answers0