HHH-7610 Add an option to initialize empty components when all the properties are null
Note that this commit also changes the semantic of the comparison between a null component and a component with all properties null: they are considered equivalent.
This commit is contained in:
parent
701f59b132
commit
d71f931429
|
@ -1009,4 +1009,13 @@ public interface AvailableSettings {
|
|||
* Values are {@code true} (pass the NULLs) or {@code false} (do not pass the NULLs).
|
||||
*/
|
||||
String PROCEDURE_NULL_PARAM_PASSING = "hibernate.proc.param_null_passing";
|
||||
|
||||
/*
|
||||
* Enable instantiation of composite/embedded objects when all of its attribute values are {@code null}.
|
||||
* The default (and historical) behavior is that a {@code null} reference will be used to represent the
|
||||
* composite when all of its attributes are {@code null}
|
||||
*
|
||||
* @since 5.1
|
||||
*/
|
||||
String CREATE_EMPTY_COMPOSITES_ENABLED = "hibernate.create_empty_composites.enabled";
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ import java.util.Map;
|
|||
import org.hibernate.EntityMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.boot.spi.MetadataBuildingOptions;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||
import org.hibernate.internal.util.config.ConfigurationHelper;
|
||||
import org.hibernate.mapping.Component;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.tuple.PropertyFactory;
|
||||
|
@ -39,6 +42,7 @@ public class ComponentMetamodel implements Serializable {
|
|||
// cached for efficiency...
|
||||
private final int propertySpan;
|
||||
private final Map propertyIndexes = new HashMap();
|
||||
private final boolean createEmptyCompositesEnabled;
|
||||
|
||||
// public ComponentMetamodel(Component component, SessionFactoryImplementor sessionFactory) {
|
||||
public ComponentMetamodel(Component component, MetadataBuildingOptions metadataBuildingOptions) {
|
||||
|
@ -65,6 +69,15 @@ public class ComponentMetamodel implements Serializable {
|
|||
entityMode,
|
||||
component
|
||||
) : componentTuplizerFactory.constructTuplizer( tuplizerClassName, component );
|
||||
|
||||
final ConfigurationService cs = component.getMetadata().getMetadataBuildingOptions().getServiceRegistry()
|
||||
.getService(ConfigurationService.class);
|
||||
|
||||
this.createEmptyCompositesEnabled = ConfigurationHelper.getBoolean(
|
||||
Environment.CREATE_EMPTY_COMPOSITES_ENABLED,
|
||||
cs.getSettings(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
public boolean isKey() {
|
||||
|
@ -106,4 +119,8 @@ public class ComponentMetamodel implements Serializable {
|
|||
return componentTuplizer;
|
||||
}
|
||||
|
||||
public boolean isCreateEmptyCompositesEnabled() {
|
||||
return createEmptyCompositesEnabled;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
private final FetchMode[] joinedFetch;
|
||||
private final boolean isKey;
|
||||
private boolean hasNotNullProperty;
|
||||
private final boolean createEmptyCompositesEnabled;
|
||||
|
||||
protected final EntityMode entityMode;
|
||||
protected final ComponentTuplizer componentTuplizer;
|
||||
|
@ -80,6 +81,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
|
||||
this.entityMode = metamodel.getEntityMode();
|
||||
this.componentTuplizer = metamodel.getComponentTuplizer();
|
||||
this.createEmptyCompositesEnabled = metamodel.isCreateEmptyCompositesEnabled();
|
||||
}
|
||||
|
||||
public boolean isKey() {
|
||||
|
@ -158,9 +160,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
if ( x == y ) {
|
||||
return true;
|
||||
}
|
||||
if ( x == null || y == null ) {
|
||||
return false;
|
||||
}
|
||||
// null value and empty component are considered equivalent
|
||||
Object[] xvalues = getPropertyValues( x, entityMode );
|
||||
Object[] yvalues = getPropertyValues( y, entityMode );
|
||||
for ( int i = 0; i < propertySpan; i++ ) {
|
||||
|
@ -176,9 +176,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
if ( x == y ) {
|
||||
return true;
|
||||
}
|
||||
if ( x == null || y == null ) {
|
||||
return false;
|
||||
}
|
||||
// null value and empty component are considered equivalent
|
||||
for ( int i = 0; i < propertySpan; i++ ) {
|
||||
if ( !propertyTypes[i].isEqual( getPropertyValue( x, i ), getPropertyValue( y, i ) ) ) {
|
||||
return false;
|
||||
|
@ -193,9 +191,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
if ( x == y ) {
|
||||
return true;
|
||||
}
|
||||
if ( x == null || y == null ) {
|
||||
return false;
|
||||
}
|
||||
// null value and empty component are considered equivalent
|
||||
for ( int i = 0; i < propertySpan; i++ ) {
|
||||
if ( !propertyTypes[i].isEqual( getPropertyValue( x, i ), getPropertyValue( y, i ), factory ) ) {
|
||||
return false;
|
||||
|
@ -253,9 +249,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
if ( x == y ) {
|
||||
return false;
|
||||
}
|
||||
if ( x == null || y == null ) {
|
||||
return true;
|
||||
}
|
||||
// null value and empty component are considered equivalent
|
||||
for ( int i = 0; i < propertySpan; i++ ) {
|
||||
if ( propertyTypes[i].isDirty( getPropertyValue( x, i ), getPropertyValue( y, i ), session ) ) {
|
||||
return true;
|
||||
|
@ -269,9 +263,7 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
if ( x == y ) {
|
||||
return false;
|
||||
}
|
||||
if ( x == null || y == null ) {
|
||||
return true;
|
||||
}
|
||||
// null value and empty component are considered equivalent
|
||||
int loc = 0;
|
||||
for ( int i = 0; i < propertySpan; i++ ) {
|
||||
int len = propertyTypes[i].getColumnSpan( session.getFactory() );
|
||||
|
@ -408,6 +400,9 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
|
||||
public Object getPropertyValue(Object component, int i)
|
||||
throws HibernateException {
|
||||
if (component == null) {
|
||||
component = new Object[propertySpan];
|
||||
}
|
||||
if ( component instanceof Object[] ) {
|
||||
// A few calls to hashCode pass the property values already in an
|
||||
// Object[] (ex: QueryKey hash codes for cached queries).
|
||||
|
@ -429,6 +424,9 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
@Override
|
||||
public Object[] getPropertyValues(Object component, EntityMode entityMode)
|
||||
throws HibernateException {
|
||||
if (component == null) {
|
||||
component = new Object[propertySpan];
|
||||
}
|
||||
if ( component instanceof Object[] ) {
|
||||
// A few calls to hashCode pass the property values already in an
|
||||
// Object[] (ex: QueryKey hash codes for cached queries).
|
||||
|
@ -689,6 +687,9 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
setPropertyValues( result, resolvedValues, entityMode );
|
||||
return result;
|
||||
}
|
||||
else if ( isCreateEmptyCompositesEnabled() ) {
|
||||
return instantiate( owner, session );
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
@ -827,4 +828,8 @@ public class ComponentType extends AbstractType implements CompositeType, Proced
|
|||
public boolean hasNotNullProperty() {
|
||||
return hasNotNullProperty;
|
||||
}
|
||||
|
||||
private boolean isCreateEmptyCompositesEnabled() {
|
||||
return createEmptyCompositesEnabled;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.component.empty;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
@Embeddable
|
||||
public class ComponentEmptyEmbedded {
|
||||
|
||||
private String field1;
|
||||
|
||||
private String field2;
|
||||
|
||||
public String getField1() {
|
||||
return field1;
|
||||
}
|
||||
|
||||
public void setField1(String field1) {
|
||||
this.field1 = field1;
|
||||
}
|
||||
|
||||
public String getField2() {
|
||||
return field2;
|
||||
}
|
||||
|
||||
public void setField2(String field2) {
|
||||
this.field2 = field2;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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.component.empty;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class ComponentEmptyEmbeddedOwner {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Integer id;
|
||||
|
||||
private ComponentEmptyEmbedded embedded;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ComponentEmptyEmbedded getEmbedded() {
|
||||
return embedded;
|
||||
}
|
||||
|
||||
public void setEmbedded(ComponentEmptyEmbedded embedded) {
|
||||
this.embedded = embedded;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.component.empty;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test class for empty embedded dirtiness computation.
|
||||
*
|
||||
* @author Laurent Almeras
|
||||
*/
|
||||
public class EmptyCompositesDirtynessTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] { ComponentEmptyEmbeddedOwner.class, ComponentEmptyEmbedded.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(Configuration configuration) {
|
||||
super.configure( configuration );
|
||||
configuration.getProperties().put( Environment.CREATE_EMPTY_COMPOSITES_ENABLED, Boolean.valueOf( false ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for dirtyness computation consistency when a property is an empty composite.
|
||||
*/
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-7610")
|
||||
public void testCompositesEmpty() {
|
||||
Session s = openSession();
|
||||
s.getTransaction().begin();
|
||||
|
||||
ComponentEmptyEmbeddedOwner owner = new ComponentEmptyEmbeddedOwner();
|
||||
s.persist( owner );
|
||||
|
||||
s.flush();
|
||||
s.getTransaction().commit();
|
||||
|
||||
s.clear();
|
||||
s.getTransaction().begin();
|
||||
owner = (ComponentEmptyEmbeddedOwner) s.get( ComponentEmptyEmbeddedOwner.class, owner.getId() );
|
||||
assertNull( owner.getEmbedded() );
|
||||
owner.setEmbedded( new ComponentEmptyEmbedded() );
|
||||
|
||||
// technically, as all properties are null, update may not be necessary
|
||||
assertFalse( session.isDirty() ); // must be false to avoid unnecessary updates
|
||||
|
||||
s.getTransaction().rollback();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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.component.empty;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test class for empty embedded dirtiness computation.
|
||||
*
|
||||
* @author Laurent Almeras
|
||||
*/
|
||||
public class EmptyInitializedCompositesDirtynessTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] { ComponentEmptyEmbeddedOwner.class, ComponentEmptyEmbedded.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(Configuration configuration) {
|
||||
super.configure( configuration );
|
||||
configuration.getProperties().put( Environment.CREATE_EMPTY_COMPOSITES_ENABLED, Boolean.valueOf( true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for dirtyness computation consistency when a property is an empty composite and that empty composite
|
||||
* initialization is set.
|
||||
*/
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-7610")
|
||||
public void testInitializedCompositesEmpty() {
|
||||
Session s = openSession();
|
||||
s.getTransaction().begin();
|
||||
|
||||
ComponentEmptyEmbeddedOwner owner = new ComponentEmptyEmbeddedOwner();
|
||||
s.persist( owner );
|
||||
|
||||
s.flush();
|
||||
s.getTransaction().commit();
|
||||
|
||||
s.clear();
|
||||
s.getTransaction().begin();
|
||||
owner = (ComponentEmptyEmbeddedOwner) s.get( ComponentEmptyEmbeddedOwner.class, owner.getId() );
|
||||
assertNotNull( owner.getEmbedded() );
|
||||
assertFalse( s.isDirty() );
|
||||
|
||||
s.getTransaction().rollback();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.component.empty;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test class for empty embedded dirtiness computation.
|
||||
*
|
||||
* @author Laurent Almeras
|
||||
*/
|
||||
public class EmptyInitializedCompositesTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] { ComponentEmptyEmbeddedOwner.class, ComponentEmptyEmbedded.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(Configuration configuration) {
|
||||
super.configure( configuration );
|
||||
configuration.getProperties().put( Environment.CREATE_EMPTY_COMPOSITES_ENABLED, Boolean.valueOf( true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty composite initialization.
|
||||
*/
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-7610")
|
||||
public void testCompositesEmpty() {
|
||||
Session s = openSession();
|
||||
s.getTransaction().begin();
|
||||
|
||||
ComponentEmptyEmbeddedOwner owner = new ComponentEmptyEmbeddedOwner();
|
||||
s.persist( owner );
|
||||
|
||||
s.flush();
|
||||
s.getTransaction().commit();
|
||||
|
||||
s.clear();
|
||||
s.getTransaction().begin();
|
||||
owner = (ComponentEmptyEmbeddedOwner) s.get( ComponentEmptyEmbeddedOwner.class, owner.getId() );
|
||||
assertNotNull( owner.getEmbedded() );
|
||||
assertFalse( s.isDirty() );
|
||||
|
||||
owner.setEmbedded( null );
|
||||
assertFalse( s.isDirty() ); // must be false to avoid unnecessary updates
|
||||
|
||||
s.getTransaction().rollback();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue