Different behavior between merge() and update() method with 'withModifiedFlag' attribute
ucieffe Jun 20, 2013 8:22 AMHello,
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.
-
test-envers.zip 7.8 KB