2 Replies Latest reply on Apr 24, 2008 9:26 AM by jvojtek

    EntityManager remove does not remove?

    jvojtek

      In my application I need to persist data of plan modeled in memory to database. First data of previous instance of plan is deleted and then inserted the new one (I do not want to perform update for some reason). But if I perform checking before persisting new data, method findAll returns also data that has been just deleted by the same instance of EntityManager.

      I am not able to successfully call method PlannerFacadeEJB.savePlanVersion :-(

      OS: MS Windows XP
      JAVA: 1.6
      JBoss 4.2.2.GA (the same behaviour on 4.0.5)

      Situation:

      @Entity(name = "DBPlanVersion)
      @Table(name = "PLAN_VERSION")
      @NamedQueries({
      @NamedQuery(name = DBPlanVersion.findAll, query = "SELECT a FROM DBPlanVersion a"),
      @NamedQuery(name = "DBPlanVersion.findByPlanId, query = "SELECT a FROM DBPlanVersion a WHERE a.planId = :planId")
      })
      public static DBPlanVersion implements Serializable {
       public static final String findAll = "DBPlanVersion.findAll";
       public static final String findByPlanId = "DBPlanVersion.findByPlanId";
      
       private int planVersionId;
       private int planId;
       private String description;
      
       public DBPlanVersion() {
       }
      
      
       @Id
       @Column(name = "PLAN_VERSION_ID", nullable = false, length = 10)
       @SequenceGenerator(name = "PLAN_VERSION_SEQ", sequenceName = "PLAN_VERSION_SEQ", allocationSize = 1)
       @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PLAN_VERSION_SEQ")
       public int getPlanVersionId() {
       return planVersionId;
       }
      
       public void setPlanVersionId(int planVersionId) {
       this.planVersionId = planVersionId;
       }
      
       @Basic
       @Column(name = "PLAN_ID", nullable = false, length = 10)
       public int getPlanId() {
       return planId;
       }
      
       public void setPlanId(int planId) {
       this.planId = planId;
       }
      
       @Basic
       @Column(name = "DESCRIPTION", nullable = true, length = 50)
       public String getDescription() {
       return description;
       }
      
       public void setDescription(String description) {
       this.description = description;
       }
      
       public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
      
       DBPlanVersion that = (DBPlanVersion) o;
      
       if (planVersionId != that.planVersionId) return false;
      
       return true;
       }
      }
      
      


      @Stateless(name = "PlannerFacadeEJB")
      @Remote(PlannerFacade3.class)
      @Local(PlannerFacade3.class)
      public class PlannerFacadeBean implements PlannerFacade3 {
      
       @PersistenceContext(unitName = "xxx")
       EntityManager em;
      
       public void savePlanVersion(int planId, String description) {
       em.setFlushMode(FlushModeType.COMMIT);
       planVersionFacade3.deleteByPlanId(planId);
       planVersionFacade3.addPlanVersion(planId, description);
       }
      }
      



      @Stateless(name = "PlanVersionFacadeEJB")
      @Local(PlanVersionFacadeLocal)
      public class PlanVersionFacadeBean implements PlanVersionFacadeLocal {
      
       @PersistenceContext(unitName = "xxx");
       EntityManager em;
      
       public void deleteByPlanId(int planId) {
       // Lets find DBPlanVersion with specified planId
       DBPlanVersion dbPlanVersion = findByPlanId(planId);
      
       if (dbPlanVersion != null) {
       log.info("Delete: planId=" + dbPlanVersion.getPlanId() + ", planVersionId=" + dbPlanVersion.getPlanVersionId());
      
       // ... delete children records in another entity, code omitted
      
       // Delete plan versionId itself
       em.remove(planVersion3);
       }
       log.info("DeleteByPlanId: Instance of EntityManager is " + em);
       }
      
       public DBPlanVersion findByPlanId(int planId) {
       Query q = em.createNamedQuery(DBPlanVersion.findByPlanId);
       q.setParameter("planId", planId);
       try {
       return (DBPlanVersion) q.getSingleResult();
       } catch (NoResultException e) {
       return null;
       }
       }
      
       public Collection<DBPlanVersion> findAll() {
       return em.createNamedQuery(DBPlanVersion.findAll).getResultList();
       }
      
       public DBPlanVersion addPlanVersion(int planId, String description) {
       DBPlanVersion newDBPlanVersion = new DBPlanVersion();
       newDBPlanVersion.setPlanId(planId);
       newDBPlanVersion.setDescription(description);
       // Lets check whether plan version with the same plan id already exists
       for (DBPlanVersion pv : findAll()) {
       if ((pv.getPlanId() == newDBPlanVersion.getPlanId()) && (!pv.equals(newDBPlanVersion))) {
       throw new RuntimeException("Unable to insert plan version with the same planId = " + pv.getPlanId());
       }
       }
       em.persist(newDBPlanVersion);
       return newDBPlanVersion;
       }
      }
      


      Vital questions:
      . how is possible that EntityManager does not know about removed entity in findAll method?
      . is there any possibility to use caching?
      . what is difference between caching queries and EntityBeans?
      . if em.setFlushMode is set to AUTO, findAll does not return just deleted entity and new data can be persisted. But there are many SQL called (can see in JProfiler) - is this the only way of solution?

        • 1. Re: EntityManager remove does not remove?
          jaikiran

          Here's what i see in the javadocs of FlushModeType http://www.rhce.com/docs/manuals/jboss/jboss-eap-4.2/doc/hibernate/api/ejb-persistence-3.0-api/javax/persistence/FlushModeType.html


          Flush mode setting. When queries are executed within a transaction, if FlushModeType.AUTO is set on the Query object, or if the flush mode setting for the persistence context is AUTO (the default) and a flush mode setting has not been specified for the Query object, the persistence provider is responsible for ensuring that all updates to the state of all entities in the persistence context which could potentially affect the result of the query are visible to the processing of the query. The persistence provider implementation may achieve this by flushing those entities to the database or by some other means. If FlushModeType.COMMIT is set, the effect of updates made to entities in the persistence context upon queries is unspecified. If there is no transaction active, the persistence provider must not flush to the database.


          • 2. Re: EntityManager remove does not remove?
            jvojtek

            You are right. But in case of FlushModeType.AUTO there are enormous SQL queries performed.
            This behaviour you mentioned is defined also in Final Release of JSR-000220 Enterprise JavaBeans 3.0 http://jcp.org/aboutJava/communityprocess/final/jsr220/index.html - ejb-3_0-fr-spec-persistence.pdf

            3.2.3 Synchronization to the Database


            The EntityManager and Query setFlushMode methods can be used to control synchronization semantics. The effect of FlushModeType.AUTO is defined in section 3.6.2. If FlushModeType.COMMIT is specified, flushing will occur at transaction commit; the persistence provider is permitted, but not required, to perform to flush at other times. If there is no transaction active, the persistence provider must not flush to the database.

            and section 3.6.2 Queries and FlushMode

            When queries are executed within a transaction, if FlushModeType.AUTO is set on the Query object, or if the flush mode setting for the persistence context is AUTO (the default) and a flush mode setting has not been specified for the Query object, the persistence provider is responsible for ensuring that all updates to the state of all entities in the persistence context which could potentially affect the result of the query are visible to the processing of the query. The persistence provider implementation may achieve this by flushing those entities to the database or by some other means. If FlushMode-Type.COMMIT is set, the effect of updates made to entities in the persistence context upon queries is unspecified.


            Fortunately I found also in section 3.2.5 Managed Instances

            It is the responsibility of the application to insure that an instance is managed in only a single persistence context.
            ...
            The contains method returns false:
            . If the remove method has been called on the entity, or the remove operation has been cascaded to it.


            SOLUTION: Well my solution for this problem is to check using method EntityManager.contains(..) that returned object (by EJBQL) is contained in EntityManager instance.

            Changes in my code:
            if ((pv.getPlanId() == newDBPlanVersion.getPlanId()) && (!pv.equals(newDBPlanVersion)) && em.contains(pv)){
            public DBPlanVersion addPlanVersion(int planId, String description) {
             DBPlanVersion newDBPlanVersion = new DBPlanVersion();
             newDBPlanVersion.setPlanId(planId);
             newDBPlanVersion.setDescription(description);
             // Lets check whether plan version with the same plan id already exists
             for (DBPlanVersion pv : findAll()) {
             if ((pv.getPlanId() == newDBPlanVersion.getPlanId()) && (!pv.equals(newDBPlanVersion)) && em.contains(pv)) {
             throw new RuntimeException("Unable to insert plan version with the same planId = " + pv.getPlanId());
             }
             }
             em.persist(newDBPlanVersion);
             return newDBPlanVersion;
             }
            


            It is fully functional also with caching enabled on DBPlanVersion and caching on queries
            @Entity(name = "DBPlanVersion)
            @Table(name = "PLAN_VERSION")
            @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
            @NamedQueries({
            @NamedQuery(name = DBPlanVersion.findAll, query = "SELECT a FROM DBPlanVersion a", hints = {@QueryHint(name="org.hibernate.cacheable", value="true")}),
            @NamedQuery(name = "DBPlanVersion.findByPlanId, query = "SELECT a FROM DBPlanVersion a WHERE a.planId = :planId", hints = {@QueryHint(name="org.hibernate.cacheable", value="true")} )
            })
            public static DBPlanVersion implements Serializable {
             // ... the same code as previous post
            }
            


            EJB3/JBoss gurus, may I use this solution?