Search code examples
javahibernatejpaflush

hibernate/jpa complaining "flush during cascade"


My application is crashing with the error below:

org.hibernate.HibernateException: Flush during cascade is dangerous

I am not flushing unless hibernate is doing it on my behalf.

Specs:

  • webapp on tomcat
  • hibernate/jpa for persistence (application managed entity manager)

This is the code of my util class to manage entity manager:

private static EntityManagerFactory emFactory = Persistence.createEntityManagerFactory("returnit");

private static EntityManager entityManager;

public static EntityManager getEntityManager(){

    return entityManager;
}

public static EntityManager initEntityManager(){
  if (emFactory == null) {
      emFactory = Persistence.createEntityManagerFactory( "returnit" );
  }

  entityManager = emFactory.createEntityManager();
  return entityManager;
}

And this is the method that triggers the error:

@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response post(@HeaderParam(HttpHeaders.AUTHORIZATION) String authHeader, MasterCrossDock mcd) {

    EntityManager em = Utils.initEntityManager();
    em.getTransaction().begin();

    MasterCrossDockDAO.save(mcd);

    em.getTransaction().commit();
    em.close();

    return Response.ok(mcd.getId()).build();
}

public static void save(MasterCrossDock new_mcd) {  

    List<Receptacle> receptacles = new_mcd.getReceptacles();

    List<Long> ids = new ArrayList<Long>();

    for (Receptacle r: receptacles) {
        ids.add(r.getId());
    }

    new_mcd.getReceptacles().clear();

    EntityManager em = Utils.getEntityManager();

    new_mcd.getCountryDestination())

    em.createQuery("UPDATE receptacle r"
            + " SET r.masterCrossDock.id = :mcd_id"
            + " WHERE r.id IN :ids")
    .setParameter("ids", ids)
    .setParameter("mcd_id", new_mcd.getId())
    .executeUpdate();

    new_mcd.getEreturns());
}

Why am I getting the error above and how to fix it?


Solution

  • Entity manager is not thread safe. Using EntityManager in container managed transatcions is fine, but here you are managing both the EntityManager and the transaction yourself. Also the entity manager is static so you are effectivly re-using it over the different requests you may get from the controller. Incoming call would execute the update query which would invoke a flush.

    I noticed that during your initEntityManager you are swapping the static instance of the entityManager with a new one. What about the old reference that may be in use by another thread ?

    Do the following:

    1. Delete entirly your method initEntityManager
    2. Delete private static EntityManager entityManager;
    3. Make you method Utils.getEntityManager(); to aways create a new EntityManager

    Alternative solution should be to make Spring or your container if you use container manage your transactions. Make a service, annotate it with @Transaction attribute and make Spring/Container inject the EntutyManager in it, or just use spring-data repositories.