Skip to content

Commit

Permalink
HHH-5908 - Avoid unnecessary updates on detached un-modified entities…
Browse files Browse the repository at this point in the history
… with SelectBeforeUpdate.

HHH-11056 - Envers audits unchanged objects for a certain use case
  • Loading branch information
Naros authored and vladmihalcea committed Sep 21, 2016
1 parent 348f92b commit 20ae492
Show file tree
Hide file tree
Showing 4 changed files with 468 additions and 7 deletions.
Expand Up @@ -4172,6 +4172,7 @@ public int[] findModified(Object[] old, Object[] current, Object entity, SharedS
current,
old,
propertyColumnUpdateable,
getPropertyUpdateability(),
hasUninitializedLazyProperties( entity ),
session
);
Expand Down
17 changes: 10 additions & 7 deletions hibernate-core/src/main/java/org/hibernate/type/TypeHelper.java
Expand Up @@ -7,6 +7,7 @@
package org.hibernate.type;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Map;

import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
Expand Down Expand Up @@ -323,6 +324,7 @@ public static int[] findDirty(
* @param currentState The current state of the entity
* @param previousState The baseline state of the entity
* @param includeColumns Columns to be included in the mod checking, per property
* @param includeProperties Array of property indices that identify which properties participate in check
* @param anyUninitializedProperties Does the entity currently hold any uninitialized property values?
* @param session The session from which the dirty check request originated.
*
Expand All @@ -333,30 +335,31 @@ public static int[] findModified(
final Object[] currentState,
final Object[] previousState,
final boolean[][] includeColumns,
final boolean[] includeProperties,
final boolean anyUninitializedProperties,
final SharedSessionContractImplementor session) {
int[] results = null;
int count = 0;
int span = properties.length;

for ( int i = 0; i < span; i++ ) {
final boolean modified = currentState[i]!=LazyPropertyInitializer.UNFETCHED_PROPERTY
&& properties[i].isDirtyCheckable(anyUninitializedProperties)
&& properties[i].getType().isModified( previousState[i], currentState[i], includeColumns[i], session );

final boolean modified = currentState[ i ] != LazyPropertyInitializer.UNFETCHED_PROPERTY
&& includeProperties[ i ]
&& properties[ i ].isDirtyCheckable( anyUninitializedProperties )
&& properties[ i ].getType().isModified( previousState[ i ], currentState[ i ], includeColumns[ i ], session );
if ( modified ) {
if ( results == null ) {
results = new int[span];
results = new int[ span ];
}
results[count++] = i;
results[ count++ ] = i;
}
}

if ( count == 0 ) {
return null;
}
else {
int[] trimmed = new int[count];
int[] trimmed = new int[ count ];
System.arraycopy( results, 0, trimmed, 0, count );
return trimmed;
}
Expand Down
@@ -0,0 +1,235 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.annotations.selectbeforeupdate;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Version;

import org.hibernate.annotations.SelectBeforeUpdate;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
* @author Chris Cranford
*/
public class UpdateDetachedTest extends BaseCoreFunctionalTestCase{

@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Foo.class, Bar.class };
}

@Test
@TestForIssue(jiraKey = "HHH-5908")
public void testUpdateDetachedUnchanged() {
final Bar bar = new Bar( 1, "Bar" );
final Foo foo = new Foo( 1, "Foo", bar );

// this should generate versions
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
session.save( bar );
session.save( foo );
} );

// this shouldn't generate a new version.
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
session.update( foo );
} );

assertEquals( Integer.valueOf( 0 ), bar.getVersion() );
assertEquals( Integer.valueOf( 0 ), foo.getVersion() );

// this should generate a new version
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
foo.setName( "FooChanged" );
session.update( foo );
} );

assertEquals( Integer.valueOf( 0 ), bar.getVersion() );
assertEquals( Integer.valueOf( 1 ), foo.getVersion() );
}

@Test
@TestForIssue(jiraKey = "HHH-5908")
public void testUpdateDetachedChanged() {
final Bar bar = new Bar( 2, "Bar" );
final Foo foo = new Foo( 2, "Foo", bar );

// this should generate versions
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
session.save( bar );
session.save( foo );
} );

// this should generate a new version
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
foo.setName( "FooChanged" );
session.update( foo );
} );

assertEquals( Integer.valueOf( 0 ), bar.getVersion() );
assertEquals( Integer.valueOf( 1 ), foo.getVersion() );
}

@Test
@TestForIssue(jiraKey = "HHH-5908")
public void testUpdateDetachedUnchangedAndChanged() {
final Bar bar = new Bar( 3, "Bar" );
final Foo foo = new Foo( 3, "Foo", bar );

// this should generate versions
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
session.save( bar );
session.save( foo );
} );

// this shouldn't generate a new version.
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
session.update( foo );
} );

// this should generate a new version
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
foo.setName( "FooChanged" );
session.update( foo );
} );

assertEquals( Integer.valueOf( 0 ), bar.getVersion() );
assertEquals( Integer.valueOf( 1 ), foo.getVersion() );
}

@Test
@TestForIssue(jiraKey = "HHH-5908")
public void testUpdateDetachedChangedAndUnchanged() {
final Bar bar = new Bar( 4, "Bar" );
final Foo foo = new Foo( 4, "Foo", bar );

// this should generate versions
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
session.save( bar );
session.save( foo );
} );

// this should generate a new version
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
foo.setName( "FooChanged" );
session.update( foo );
} );

// this shouldn't generate a new version.
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
session.update( foo );
} );

assertEquals( Integer.valueOf( 0 ), bar.getVersion() );
assertEquals( Integer.valueOf( 1 ), foo.getVersion() );
}

@Entity(name = "Foo")
@SelectBeforeUpdate
public static class Foo {
@Id
private Integer id;
private String name;
@Version
private Integer version;
@ManyToOne
@JoinColumn(updatable = false)
private Bar bar;

Foo() {

}

Foo(Integer id, String name, Bar bar) {
this.id = id;
this.name = name;
this.bar = bar;
}

public Integer getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Bar getBar() {
return bar;
}

public void setBar(Bar bar) {
this.bar = bar;
}

public Integer getVersion() {
return version;
}

public void setVersion(Integer version) {
this.version = version;
}
}

@Entity(name = "Bar")
public static class Bar {
@Id
private Integer id;
private String name;
@Version
private Integer version;

Bar() {

}

Bar(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getVersion() {
return version;
}

public void setVersion(Integer version) {
this.version = version;
}
}
}

0 comments on commit 20ae492

Please sign in to comment.