5 Replies Latest reply on Apr 25, 2006 10:24 AM by ejb3workshop

    Entity comparison

    d11w9t

      Hi

      Before I merge an updated entity I need to be able to check if some of the fields have changed. I need to execute some code in my entity listener depending on whether or not a particular field has changed.

      e.g. I have a field userID which links to a user entity. In my preUpdate method I need to send an email to the user if the field has changed. I wouldn?t want to send the email every time the entity is updated.

        • 1. Re: Entity comparison
          wolfc

          Note that the PreUpdate callback can be called anytime within your transaction (EJB3 PFD 3.4.2).

          Try the following:

          @PostLoad
          protected void clearBits() {
           userIdChanged = false;
          }
          
          @PostPersist
          protected void possiblyNotifyUser() {
           if(userIdChanged) notifyUser();
          }
          
          void setUserId(long oid) {
           if(this.oid != oid) userIdChanged = true;
           this.oid = oid;
          }


          • 2. Re: Entity comparison
            d11w9t

            This code does work, but I am looking for something more generic as I will need to perform various different tasks for different fields on different entities.

            To determine whether any field has changed on any entity, I would potentially have to add code to all my set methods to flag the field as dirty.

            I managed to get around the problem for now by creating 2 new instances of the entity. One is used to store the current state of the entity and the other is used to store the state of the entity after it has been refreshed using the entity manager. I can re populate the persistent entity with the first new instance I created. I now have the persistent entity and the original entity before any changes I made.

            Entity copy code:

            public Object getOriginalEntity(Object persistentEntity) throws Exception {
             Object current = persistentEntity.getClass().newInstance();
             Object original = persistentEntity.getClass().newInstance();
            
             this.copyEntity(persistentEntity, current);
             this.em.refresh(persistentEntity);
             this.copyEntity(persistentEntity, original);
             this.copyEntity(current, persistentEntity);
            
             return original;
            }
            
            private void copyEntity(Object getEntity, Object setEntity) {
             Class entityClass = getEntity.getClass();
            
             try {
             Method[] methods = entityClass.getMethods();
            
             for (Method method : methods) {
             if (method.getReturnType() != Void.class &&
             !method.isAnnotationPresent(Transient.class)) {
            
             Method setMethod;
            
             try {
             setMethod = entityClass.getMethod(
             "set" + method.getName().substring(3),
             method.getReturnType()
             );
            
             setMethod.invoke(setEntity, method.invoke(getEntity));
             } catch(NoSuchMethodException nsme) {
             /* Ignore this for now. */
             }
             }
             }
             } catch (Exception e) {
             e.printStackTrace();
             }
            }
            


            Surely there must be a more efficient way of getting the original entity.

            • 3. Re: Entity comparison
              wolfc

              How about:

              @AroundInvoke
               public Object myBeanInterceptor(InvocationContext ctx) throws Exception
               {
               if (ctx.getMethod().getName().startsWith("set"))
               {
               // FIXME: set that field to dirty
               // Don't forget to compare
               }
              
               return ctx.proceed();
               }

              You could even create a class specificly for managing the dirtieness of an entity.
              (prototypical)
              class HouseKeep {
               Object entity; // probably not needed
               Map dirtyFields;
               void evaluateInvocationContext(InvocationContext ctx);
               boolean isDirtyField(String name);
              ...
              }
              


              • 4. Re: Entity comparison
                d11w9t

                I don't think it is possible to use @AroundInvoke within an entity. I started to write an AOP interceptor which can intercept all set methods for a class annotated with @Entity. This would allow me to do something similar to your example. However I think that this would have a bigger overhead than copying the values from the entity at update time as the map would automatically be populated at load time.

                I think I will write a base entity listener, that copies the entities values into a map, refreshes the entity, copies the refreshed values into a map, and replaces the original values. I only have to do this if I need to determine whether a value has changed or not.

                Thanks for your ideas.

                • 5. Re: Entity comparison
                  ejb3workshop

                  You might want to have a look at using versioning:


                  @Version
                  public int getVersion()
                  {
                   return version;
                  }
                  public void setVersion(int version)
                  {
                   this.version=version;
                  }
                  


                  Depending on what you need to do you could use the version to see if there have been updates made. If it's not useful, please ignore the suggestion.