HHH-5764 - Support for multi-level derived ids

(cherry picked from commit a32372d751)
This commit is contained in:
Matt Drees 2014-05-21 17:31:30 -06:00 committed by Steve Ebersole
parent b4b6fa97e6
commit 943acc78bc
16 changed files with 486 additions and 63 deletions

View File

@ -1611,7 +1611,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
processSecondPasses( pkDrivenByDefaultMapsIdSecondPassList );
processSecondPasses( setSimpleValueTypeSecondPassList );
processSecondPasses( copyIdentifierComponentSecondPasList );
processCopyIdentifierSecondPassesInOrder();
processFkSecondPassesInOrder();
@ -1637,6 +1638,14 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
}
}
private void processCopyIdentifierSecondPassesInOrder() {
if ( copyIdentifierComponentSecondPasList == null ) {
return;
}
sortCopyIdentifierComponentSecondPasses();
processSecondPasses( copyIdentifierComponentSecondPasList );
}
private void processSecondPasses(ArrayList<? extends SecondPass> secondPasses) {
if ( secondPasses == null ) {
return;
@ -1649,6 +1658,40 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
secondPasses.clear();
}
private void sortCopyIdentifierComponentSecondPasses() {
ArrayList<CopyIdentifierComponentSecondPass> sorted =
new ArrayList<CopyIdentifierComponentSecondPass>( copyIdentifierComponentSecondPasList.size() );
Set<CopyIdentifierComponentSecondPass> toSort = new HashSet<CopyIdentifierComponentSecondPass>();
toSort.addAll( copyIdentifierComponentSecondPasList );
topologicalSort( sorted, toSort );
copyIdentifierComponentSecondPasList = sorted;
}
/* naive O(n^3) topological sort */
private void topologicalSort( List<CopyIdentifierComponentSecondPass> sorted, Set<CopyIdentifierComponentSecondPass> toSort ) {
while (!toSort.isEmpty()) {
CopyIdentifierComponentSecondPass independent = null;
searchForIndependent:
for ( CopyIdentifierComponentSecondPass secondPass : toSort ) {
for ( CopyIdentifierComponentSecondPass other : toSort ) {
if (secondPass.dependentUpon( other )) {
continue searchForIndependent;
}
}
independent = secondPass;
break;
}
if (independent == null) {
throw new MappingException( "cyclic dependency in derived identities" );
}
toSort.remove( independent );
sorted.add( independent );
}
}
private void processFkSecondPassesInOrder() {
if ( fkSecondPassList == null || fkSecondPassList.isEmpty() ) {
return;

View File

@ -10,6 +10,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
@ -75,81 +76,138 @@ public class CopyIdentifierComponentSecondPass implements SecondPass {
columnByReferencedName.put( referencedColumnName.toLowerCase(Locale.ROOT), joinColumn );
}
//try default column orientation
int index = 0;
AtomicInteger index = new AtomicInteger( 0 );
if ( columnByReferencedName.isEmpty() ) {
isExplicitReference = false;
for (Ejb3JoinColumn joinColumn : joinColumns) {
columnByReferencedName.put( "" + index, joinColumn );
index++;
columnByReferencedName.put( "" + index.get(), joinColumn );
index.getAndIncrement();
}
index = 0;
index.set( 0 );
}
while ( properties.hasNext() ) {
Property referencedProperty = properties.next();
if ( referencedProperty.isComposite() ) {
throw new AssertionFailure( "Unexpected nested component on the referenced entity when mapping a @MapsId: "
+ referencedEntityName);
Property property = createComponentProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
component.addProperty( property );
}
else {
Property property = new Property();
property.setName( referencedProperty.getName() );
//FIXME set optional?
//property.setOptional( property.isOptional() );
property.setPersistentClass( component.getOwner() );
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() );
property.setValue( value );
final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
value.setTypeName( referencedValue.getTypeName() );
value.setTypeParameters( referencedValue.getTypeParameters() );
final Iterator<Selectable> columns = referencedValue.getColumnIterator();
if ( joinColumns[0].isNameDeferred() ) {
joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
referencedPersistentClass,
columns,
value);
}
else {
//FIXME take care of Formula
while ( columns.hasNext() ) {
final Selectable selectable = columns.next();
if ( ! Column.class.isInstance( selectable ) ) {
log.debug( "Encountered formula definition; skipping" );
continue;
}
final Column column = (Column) selectable;
final Ejb3JoinColumn joinColumn;
String logicalColumnName = null;
if ( isExplicitReference ) {
final String columnName = column.getName();
logicalColumnName = buildingContext.getMetadataCollector().getLogicalColumnName(
referencedPersistentClass.getTable(),
columnName
);
//JPA 2 requires referencedColumnNames to be case insensitive
joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) );
}
else {
joinColumn = columnByReferencedName.get( "" + index );
index++;
}
if ( joinColumn == null && ! joinColumns[0].isNameDeferred() ) {
throw new AnnotationException(
isExplicitReference ?
"Unable to find column reference in the @MapsId mapping: " + logicalColumnName :
"Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: " + referencedEntityName
);
}
final String columnName = joinColumn == null || joinColumn.isNameDeferred() ? "tata_" + column.getName() : joinColumn
.getName();
value.addColumn( new Column( columnName ) );
column.setValue( value );
}
}
Property property = createSimpleProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
component.addProperty( property );
}
}
}
private Property createComponentProperty(
PersistentClass referencedPersistentClass,
boolean isExplicitReference,
Map<String, Ejb3JoinColumn> columnByReferencedName,
AtomicInteger index,
Property referencedProperty ) {
Property property = new Property();
property.setName( referencedProperty.getName() );
//FIXME set optional?
//property.setOptional( property.isOptional() );
property.setPersistentClass( component.getOwner() );
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
Component value = new Component( buildingContext.getMetadataCollector(), component.getOwner() );
property.setValue( value );
final Component referencedValue = (Component) referencedProperty.getValue();
value.setTypeName( referencedValue.getTypeName() );
value.setTypeParameters( referencedValue.getTypeParameters() );
value.setComponentClassName( referencedValue.getComponentClassName() );
Iterator<Property> propertyIterator = referencedValue.getPropertyIterator();
while(propertyIterator.hasNext())
{
Property referencedComponentProperty = propertyIterator.next();
if ( referencedComponentProperty.isComposite() ) {
Property componentProperty = createComponentProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
value.addProperty( componentProperty );
}
else {
Property componentProperty = createSimpleProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
value.addProperty( componentProperty );
}
}
return property;
}
private Property createSimpleProperty(
PersistentClass referencedPersistentClass,
boolean isExplicitReference,
Map<String, Ejb3JoinColumn> columnByReferencedName,
AtomicInteger index,
Property referencedProperty ) {
Property property = new Property();
property.setName( referencedProperty.getName() );
//FIXME set optional?
//property.setOptional( property.isOptional() );
property.setPersistentClass( component.getOwner() );
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() );
property.setValue( value );
final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
value.setTypeName( referencedValue.getTypeName() );
value.setTypeParameters( referencedValue.getTypeParameters() );
final Iterator<Selectable> columns = referencedValue.getColumnIterator();
if ( joinColumns[0].isNameDeferred() ) {
joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
referencedPersistentClass,
columns,
value);
}
else {
//FIXME take care of Formula
while ( columns.hasNext() ) {
final Selectable selectable = columns.next();
if ( ! Column.class.isInstance( selectable ) ) {
log.debug( "Encountered formula definition; skipping" );
continue;
}
final Column column = (Column) selectable;
final Ejb3JoinColumn joinColumn;
String logicalColumnName = null;
if ( isExplicitReference ) {
final String columnName = column.getName();
logicalColumnName = buildingContext.getMetadataCollector().getLogicalColumnName(
referencedPersistentClass.getTable(),
columnName
);
//JPA 2 requires referencedColumnNames to be case insensitive
joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) );
}
else {
joinColumn = columnByReferencedName.get( "" + index.get() );
index.getAndIncrement();
}
if ( joinColumn == null && ! joinColumns[0].isNameDeferred() ) {
throw new AnnotationException(
isExplicitReference ?
"Unable to find column reference in the @MapsId mapping: " + logicalColumnName :
"Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: " + referencedEntityName
);
}
final String columnName = joinColumn == null || joinColumn.isNameDeferred() ? "tata_" + column.getName() : joinColumn
.getName();
value.addColumn( new Column( columnName ) );
if ( joinColumn != null ) {
joinColumn.linkWithValue( value );
}
column.setValue( value );
}
}
return property;
}
public boolean dependentUpon( CopyIdentifierComponentSecondPass other ) {
return this.referencedEntityName.equals( other.component.getOwner().getEntityName() );
}
}

View File

@ -0,0 +1,17 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
@Entity
public class Dependent {
@EmbeddedId
DependentId id;
@MapsId("empPK")
@ManyToOne
Employee emp;
}

View File

@ -0,0 +1,10 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class DependentId implements Serializable {
String name;
EmployeeId empPK;
}

View File

@ -0,0 +1,87 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.derivedidentities.e3.b2;
import org.hibernate.Session;
import org.hibernate.test.util.SchemaUtil;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Emmanuel Bernard
* @author Matt Drees
*/
public class DerivedIdentityEmbeddedIdParentEmbeddedIdGrandparentEmbeddedIdDepTest extends BaseNonConfigCoreFunctionalTestCase {
@Test
public void testManyToOne() throws Exception {
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_firstName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_lastName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "name", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Dependent", "firstName", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Dependent", "lastName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "dep_emp_firstName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "dep_emp_lastName", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "type", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "firstName", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "lastName", metadata() ) );
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "name", metadata() ) );
final Employee e = new Employee();
e.empId = new EmployeeId();
e.empId.firstName = "Emmanuel";
e.empId.lastName = "Bernard";
final Session s = openSession();
s.getTransaction().begin();
s.persist( e );
final Dependent d = new Dependent();
d.emp = e;
d.id = new DependentId();
d.id.name = "Doggy";
s.persist( d );
Policy p = new Policy();
p.dep = d;
p.id = new PolicyId();
p.id.type = "Vet Insurance";
s.persist( p );
s.flush();
s.clear();
p = (Policy) s.get( Policy.class, p.id );
assertNotNull( p.dep );
assertEquals( e.empId.firstName, p.dep.emp.empId.firstName );
s.getTransaction().rollback();
s.close();
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{Policy.class, Dependent.class, Employee.class};
}
}

View File

@ -0,0 +1,11 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
@Entity
public class Employee {
@EmbeddedId
EmployeeId empId;
}

View File

@ -0,0 +1,10 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmployeeId implements Serializable {
String firstName;
String lastName;
}

View File

@ -0,0 +1,18 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
@Entity
public class Policy {
@EmbeddedId
PolicyId id;
@MapsId("depPK")
@ManyToOne
Dependent dep;
}

View File

@ -0,0 +1,10 @@
package org.hibernate.test.annotations.derivedidentities.e3.b2;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class PolicyId implements Serializable {
String type;
DependentId depPK;
}

View File

@ -0,0 +1,18 @@
package org.hibernate.test.annotations.derivedidentities.e3.b3;
import javax.persistence.*;
@Entity
public class Dependent {
@EmbeddedId
DependentId id;
@JoinColumns({
@JoinColumn(name = "FIRSTNAME", referencedColumnName = "FIRSTNAME"),
@JoinColumn(name = "LASTNAME", referencedColumnName = "lastName")
})
@MapsId("empPK")
@ManyToOne
Employee emp;
}

View File

@ -0,0 +1,10 @@
package org.hibernate.test.annotations.derivedidentities.e3.b3;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class DependentId implements Serializable {
String name;
EmployeeId empPK;
}

View File

@ -0,0 +1,81 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.annotations.derivedidentities.e3.b3;
import org.hibernate.Session;
import org.hibernate.test.util.SchemaUtil;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* @author Emmanuel Bernard
* @author Matt Drees
*/
public class DerivedIdentityEmbeddedIdParentEmbeddedIdGrandparentEmbeddedIdColumnOverridesDepTest extends BaseNonConfigCoreFunctionalTestCase {
@Test
public void testManyToOne() throws Exception {
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "FIRSTNAME", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "LASTNAME", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "name", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "FIRSTNAME", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "LASTNAME", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "NAME", metadata() ) );
assertTrue( SchemaUtil.isColumnPresent( "Policy", "type", metadata() ) );
final Employee e = new Employee();
e.empId = new EmployeeId();
e.empId.firstName = "Emmanuel";
e.empId.lastName = "Bernard";
final Session s = openSession();
s.getTransaction().begin();
s.persist( e );
final Dependent d = new Dependent();
d.emp = e;
d.id = new DependentId();
d.id.name = "Doggy";
s.persist( d );
Policy p = new Policy();
p.dep = d;
p.id = new PolicyId();
p.id.type = "Vet Insurance";
s.persist( p );
s.flush();
s.clear();
p = (Policy) s.get( Policy.class, p.id );
assertNotNull( p.dep );
assertEquals( e.empId.firstName, p.dep.emp.empId.firstName );
s.getTransaction().rollback();
s.close();
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{Policy.class, Dependent.class, Employee.class};
}
}

View File

@ -0,0 +1,11 @@
package org.hibernate.test.annotations.derivedidentities.e3.b3;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
@Entity
public class Employee {
@EmbeddedId
EmployeeId empId;
}

View File

@ -0,0 +1,10 @@
package org.hibernate.test.annotations.derivedidentities.e3.b3;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class EmployeeId implements Serializable {
String firstName;
String lastName;
}

View File

@ -0,0 +1,19 @@
package org.hibernate.test.annotations.derivedidentities.e3.b3;
import javax.persistence.*;
@Entity
public class Policy {
@EmbeddedId
PolicyId id;
@JoinColumns({
@JoinColumn(name = "FIRSTNAME", referencedColumnName = "FIRSTNAME"),
@JoinColumn(name = "LASTNAME", referencedColumnName = "lastName"),
@JoinColumn(name = "NAME", referencedColumnName = "Name")
})
@MapsId("depPK")
@ManyToOne
Dependent dep;
}

View File

@ -0,0 +1,10 @@
package org.hibernate.test.annotations.derivedidentities.e3.b3;
import javax.persistence.Embeddable;
import java.io.Serializable;
@Embeddable
public class PolicyId implements Serializable {
String type;
DependentId depPK;
}