2 Replies Latest reply on Jul 1, 2013 5:43 AM by Davide Di Somma

    Different behavior between merge() and update() method with 'withModifiedFlag' attribute

    Davide Di Somma Newbie

      Hello,

      I am using Hibernate 4.2.2 final version. In these days I have tested Envers to audit my data,

      specifically the 'withModifiedFlag' attribute.

      I have noticed a different behaviour when a detached entity (annotated with @Audited) is modified

      and persisted by session.update() method or by session.merge() method.

      Specifically merge() method works correctly as expected:

      -if I modify a entity's property, the corresponding "property_MOD" field inserted into "entity_AUD" table is valued 'true'

      -whereas it is valued 'false' if the entity's property isn't modified.

       

       

      According to me update() (but also saveOrUpdate()) method works in wrong way:

      -the "property_MOD" field is valued 'true' if the corresponding entity's property is not null, indipendently if i have modified it or not.

      -otherwise is valued 'false' if the corrispondent entity's property is null, indipendently if i have modified it or not.

       

       

      I created a simple test.

      -There is only one entity, Address class, flagged with 'withModifiedFlag' attribute.

      -The test creates 2 Address instances. After that one of these is modified.

      -Then, the test checks that only one Address instance is modified at revision 2. If it uses merge() method, it will have green bar; if it uses update() method, it will have red bar

       

       

      @Entity

      @Audited(withModifiedFlag=true)

      public class Address {

       

                public Address() { }

       

                public Address(String streetName, Integer houseNumber) {

                          super();

                          this.streetName = streetName;

                          this.houseNumber = houseNumber;

                }

       

                @Id @GeneratedValue

          private int id;

          public int getId() { return id; }

          public void setId(int id) { this.id = id; }

       

          private String streetName;

          public String getStreetName() { return streetName; }

          public void setStreetName(String streetName) { this.streetName = streetName; }

         

          private Integer houseNumber;

          public Integer getHouseNumber() { return houseNumber; }

          public void setHouseNumber(Integer houseNumber) { this.houseNumber = houseNumber; }

       

       

          private Integer flatNumber;

          public Integer getFlatNumber() { return flatNumber; }

          public void setFlatNumber(Integer flatNumber) { this.flatNumber = flatNumber; }

       

      }

       

       

      public class UpdateAgainstMergeTestCase extends TestCase {

       

        private static final Log log = LoggerFactory.make( UpdateAgainstMergeTestCase.class.getName() );

       

                private Address privetDriveAddress;

       

                private Address grimmauldPlaceAddress;

       

                /**

                 * I have cleaned up the test class from utility methods to create Configuration, Session and SessionFactory.

                 * I have attached a maven project within it there is the complete test.

                 */

       

       

                @Test

                public void testMergeVersion() throws Exception {

       

               createAddresses();

               verifyCreationObjects();

       

                          Session session = openSession();

                          Transaction tx = session.beginTransaction();


               log.debug( "Now I'll change privet drive address!!" );

                          privetDriveAddress = (Address) session.merge( privetDriveAddress );

                          // Changing the address's house number and flat number

                          privetDriveAddress.setHouseNumber( 5 );

                          privetDriveAddress.setFlatNumber( null );

       

               tx.commit();

                          session.close();

       

                          verifyModificationPrivetDriveAddress();

               deleteAddresses();

                }

       

       

                @Test

                public void testUpdateVersion() throws Exception {

       

       

                          createAddresses();

                          verifyCreationObjects();

       

                          Session session = openSession();

                          Transaction tx = session.beginTransaction();

       

                          log.debug( "Now I'll change privet drive address!!" );

                          // Changing the address's house number and flat number

                          privetDriveAddress.setHouseNumber( 5 );

                          privetDriveAddress.setFlatNumber( null );

                          session.update( privetDriveAddress );

       

               tx.commit();

                          session.close();

       

               verifyModificationPrivetDriveAddress();

               deleteAddresses();

                }

       

       

                private void deleteAddresses() {

                          Session session;

                          Transaction tx;

                          // Now let's clean up everything

                          session = openSession();

                          tx = session.beginTransaction();

                          session.delete( grimmauldPlaceAddress );

                          session.delete( privetDriveAddress );

                          tx.commit();

                          session.close();

                }

       

       

                private void verifyModificationPrivetDriveAddress() {

                          // // let's assert that audit tables got updated correctly

                          Session session = openSession();

                          Transaction tx = session.beginTransaction();

                          AuditReader auditReader = AuditReaderFactory.get( session );

       

       

                          @SuppressWarnings("unchecked")

                          List<Address> houseNumberAndFlatNumberChangedAddressList = auditReader.createQuery()

                                              .forEntitiesAtRevision( Address.class, 2 ).add( AuditEntity.property( "houseNumber" ).hasChanged() )

                                              .add( AuditEntity.property( "flatNumber" ).hasChanged() )

                                              .add( AuditEntity.property( "streetName" ).hasNotChanged() ).getResultList();

                 //This assertion fails when update() method is used.

                          assertEquals( 1, houseNumberAndFlatNumberChangedAddressList.size() );

                          Address houseNumberChangedAtRevision2 = houseNumberAndFlatNumberChangedAddressList.get( 0 );

                          assertEquals( "Privet Drive", houseNumberChangedAtRevision2.getStreetName() );

                          assertEquals( 5, houseNumberChangedAtRevision2.getHouseNumber().intValue() );

                          assertNull( houseNumberChangedAtRevision2.getFlatNumber() );

       

       

                          tx.commit();

                          session.close();

                }

       

       

                private void verifyCreationObjects() {

                          // assert that everything got saved correctly

                          Session session = openSession();

                          Transaction tx = session.beginTransaction();

                          AuditReader auditReader = AuditReaderFactory.get( session );

                          Long addressObjectsAtRevision1 = (Long) auditReader.createQuery().forEntitiesAtRevision( Address.class, 1 )

                                              .addProjection( AuditEntity.id().count() ).getSingleResult();

                          assertEquals( 2L, addressObjectsAtRevision1.longValue() );

       

       

                          tx.commit();

                          session.close();

                }

       

       

                private void createAddresses() {

                          privetDriveAddress = new Address( "Privet Drive", 121 );

                          privetDriveAddress.setFlatNumber( 2 );

                          grimmauldPlaceAddress = new Address( "Grimmauld Place", 12 );

                          grimmauldPlaceAddress.setFlatNumber( 1 );

       

                          // save objects

                          Session session = openSession();

                          Transaction tx = session.beginTransaction();

                          session.save( privetDriveAddress );

                          session.save( grimmauldPlaceAddress );

                          tx.commit();

                          session.close();

                }

       

      }

       

       

       

       

      Here, some useful log messages (in bold log messages mismatch).

       

       

      merge() method version:

      ....

      ....

      DEBUG SQL:104 - update Address set flatNumber=?, houseNumber=?, streetName=? where id=?

      TRACE BasicBinder:72 - binding parameter [1] as [INTEGER] - <null>

      TRACE BasicBinder:84 - binding parameter [2] as [INTEGER] - 5

      TRACE BasicBinder:84 - binding parameter [3] as [VARCHAR] - Privet Drive

      TRACE BasicBinder:84 - binding parameter [4] as [INTEGER] - 1

      DEBUG SQL:104 - insert into REVINFO (REV, REVTSTMP) values (null, ?)

      TRACE BasicBinder:84 - binding parameter [1] as [BIGINT] - 1371723193160

      DEBUG SQL:104 - insert into Address_AUD (REVTYPE, flatNumber, flatNumber_MOD, houseNumber, houseNumber_MOD, streetName, streetName_MOD, id, REV) values (?, ?, ?, ?, ?, ?, ?, ?, ?)

      TRACE BasicBinder:84 - binding parameter [1] as [INTEGER] - 1

      TRACE BasicBinder:72 - binding parameter [2] as [INTEGER] - <null>

      TRACE BasicBinder:84 - binding parameter [3] as [BOOLEAN] - true

      TRACE BasicBinder:84 - binding parameter [4] as [INTEGER] - 5

      TRACE BasicBinder:84 - binding parameter [5] as [BOOLEAN] - true

      TRACE BasicBinder:84 - binding parameter [6] as [VARCHAR] - Privet Drive

      TRACE BasicBinder:84 - binding parameter [7] as [BOOLEAN] - false

      TRACE BasicBinder:84 - binding parameter [8] as [INTEGER] - 1

      TRACE BasicBinder:84 - binding parameter [9] as [INTEGER] - 2

      ....

      ....

       

       

      And now update() method version:

      ....

      ....

      DEBUG SQL:104 - update Address set flatNumber=?, houseNumber=?, streetName=? where id=?

      TRACE BasicBinder:72 - binding parameter [1] as [INTEGER] - <null>

      TRACE BasicBinder:84 - binding parameter [2] as [INTEGER] - 5

      TRACE BasicBinder:84 - binding parameter [3] as [VARCHAR] - Privet Drive

      TRACE BasicBinder:84 - binding parameter [4] as [INTEGER] - 1

      DEBUG SQL:104 - insert into REVINFO (REV, REVTSTMP) values (null, ?)

      TRACE BasicBinder:84 - binding parameter [1] as [BIGINT] - 1371723295810

      DEBUG SQL:104 - insert into Address_AUD (REVTYPE, flatNumber, flatNumber_MOD, houseNumber, houseNumber_MOD, streetName, streetName_MOD, id, REV) values (?, ?, ?, ?, ?, ?, ?, ?, ?)

      TRACE BasicBinder:84 - binding parameter [1] as [INTEGER] - 1

      TRACE BasicBinder:72 - binding parameter [2] as [INTEGER] - <null>

      TRACE BasicBinder:84 - binding parameter [3] as [BOOLEAN] - false

      TRACE BasicBinder:84 - binding parameter [4] as [INTEGER] - 5

      TRACE BasicBinder:84 - binding parameter [5] as [BOOLEAN] - true

      TRACE BasicBinder:84 - binding parameter [6] as [VARCHAR] - Privet Drive

      TRACE BasicBinder:84 - binding parameter [7] as [BOOLEAN] - true

      TRACE BasicBinder:84 - binding parameter [8] as [INTEGER] - 1

      TRACE BasicBinder:84 - binding parameter [9] as [INTEGER] - 2

      ....

      ....

       

       

      Am I making a mistake somewhere? Or not?

      Thanks in advance.

      Davide.