Skip to content

Commit 943acc7

Browse files
mattdreessebersole
authored andcommitted
HHH-5764 - Support for multi-level derived ids
(cherry picked from commit a32372d)
1 parent b4b6fa9 commit 943acc7

16 files changed

+484
-61
lines changed

hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1611,7 +1611,8 @@ public void processSecondPasses(MetadataBuildingContext buildingContext) {
16111611

16121612
processSecondPasses( pkDrivenByDefaultMapsIdSecondPassList );
16131613
processSecondPasses( setSimpleValueTypeSecondPassList );
1614-
processSecondPasses( copyIdentifierComponentSecondPasList );
1614+
1615+
processCopyIdentifierSecondPassesInOrder();
16151616

16161617
processFkSecondPassesInOrder();
16171618

@@ -1637,6 +1638,14 @@ public void processSecondPasses(MetadataBuildingContext buildingContext) {
16371638
}
16381639
}
16391640

1641+
private void processCopyIdentifierSecondPassesInOrder() {
1642+
if ( copyIdentifierComponentSecondPasList == null ) {
1643+
return;
1644+
}
1645+
sortCopyIdentifierComponentSecondPasses();
1646+
processSecondPasses( copyIdentifierComponentSecondPasList );
1647+
}
1648+
16401649
private void processSecondPasses(ArrayList<? extends SecondPass> secondPasses) {
16411650
if ( secondPasses == null ) {
16421651
return;
@@ -1649,6 +1658,40 @@ private void processSecondPasses(ArrayList<? extends SecondPass> secondPasses) {
16491658
secondPasses.clear();
16501659
}
16511660

1661+
private void sortCopyIdentifierComponentSecondPasses() {
1662+
1663+
ArrayList<CopyIdentifierComponentSecondPass> sorted =
1664+
new ArrayList<CopyIdentifierComponentSecondPass>( copyIdentifierComponentSecondPasList.size() );
1665+
Set<CopyIdentifierComponentSecondPass> toSort = new HashSet<CopyIdentifierComponentSecondPass>();
1666+
toSort.addAll( copyIdentifierComponentSecondPasList );
1667+
topologicalSort( sorted, toSort );
1668+
copyIdentifierComponentSecondPasList = sorted;
1669+
}
1670+
1671+
/* naive O(n^3) topological sort */
1672+
private void topologicalSort( List<CopyIdentifierComponentSecondPass> sorted, Set<CopyIdentifierComponentSecondPass> toSort ) {
1673+
while (!toSort.isEmpty()) {
1674+
CopyIdentifierComponentSecondPass independent = null;
1675+
1676+
searchForIndependent:
1677+
for ( CopyIdentifierComponentSecondPass secondPass : toSort ) {
1678+
for ( CopyIdentifierComponentSecondPass other : toSort ) {
1679+
if (secondPass.dependentUpon( other )) {
1680+
continue searchForIndependent;
1681+
}
1682+
}
1683+
independent = secondPass;
1684+
break;
1685+
}
1686+
if (independent == null) {
1687+
throw new MappingException( "cyclic dependency in derived identities" );
1688+
}
1689+
toSort.remove( independent );
1690+
sorted.add( independent );
1691+
}
1692+
}
1693+
1694+
16521695
private void processFkSecondPassesInOrder() {
16531696
if ( fkSecondPassList == null || fkSecondPassList.isEmpty() ) {
16541697
return;

hibernate-core/src/main/java/org/hibernate/cfg/CopyIdentifierComponentSecondPass.java

Lines changed: 118 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Iterator;
1111
import java.util.Locale;
1212
import java.util.Map;
13+
import java.util.concurrent.atomic.AtomicInteger;
1314

1415
import org.hibernate.AnnotationException;
1516
import org.hibernate.AssertionFailure;
@@ -75,81 +76,138 @@ public void doSecondPass(Map persistentClasses) throws MappingException {
7576
columnByReferencedName.put( referencedColumnName.toLowerCase(Locale.ROOT), joinColumn );
7677
}
7778
//try default column orientation
78-
int index = 0;
79+
AtomicInteger index = new AtomicInteger( 0 );
7980
if ( columnByReferencedName.isEmpty() ) {
8081
isExplicitReference = false;
8182
for (Ejb3JoinColumn joinColumn : joinColumns) {
82-
columnByReferencedName.put( "" + index, joinColumn );
83-
index++;
83+
columnByReferencedName.put( "" + index.get(), joinColumn );
84+
index.getAndIncrement();
8485
}
85-
index = 0;
86+
index.set( 0 );
8687
}
8788

8889
while ( properties.hasNext() ) {
8990
Property referencedProperty = properties.next();
9091
if ( referencedProperty.isComposite() ) {
91-
throw new AssertionFailure( "Unexpected nested component on the referenced entity when mapping a @MapsId: "
92-
+ referencedEntityName);
92+
Property property = createComponentProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
93+
component.addProperty( property );
9394
}
9495
else {
95-
Property property = new Property();
96-
property.setName( referencedProperty.getName() );
97-
//FIXME set optional?
98-
//property.setOptional( property.isOptional() );
99-
property.setPersistentClass( component.getOwner() );
100-
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
101-
SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() );
102-
property.setValue( value );
103-
final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
104-
value.setTypeName( referencedValue.getTypeName() );
105-
value.setTypeParameters( referencedValue.getTypeParameters() );
106-
final Iterator<Selectable> columns = referencedValue.getColumnIterator();
107-
108-
if ( joinColumns[0].isNameDeferred() ) {
109-
joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
110-
referencedPersistentClass,
111-
columns,
112-
value);
96+
Property property = createSimpleProperty( referencedPersistentClass, isExplicitReference, columnByReferencedName, index, referencedProperty );
97+
component.addProperty( property );
98+
}
99+
}
100+
}
101+
102+
private Property createComponentProperty(
103+
PersistentClass referencedPersistentClass,
104+
boolean isExplicitReference,
105+
Map<String, Ejb3JoinColumn> columnByReferencedName,
106+
AtomicInteger index,
107+
Property referencedProperty ) {
108+
Property property = new Property();
109+
property.setName( referencedProperty.getName() );
110+
//FIXME set optional?
111+
//property.setOptional( property.isOptional() );
112+
property.setPersistentClass( component.getOwner() );
113+
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
114+
Component value = new Component( buildingContext.getMetadataCollector(), component.getOwner() );
115+
116+
property.setValue( value );
117+
final Component referencedValue = (Component) referencedProperty.getValue();
118+
value.setTypeName( referencedValue.getTypeName() );
119+
value.setTypeParameters( referencedValue.getTypeParameters() );
120+
value.setComponentClassName( referencedValue.getComponentClassName() );
121+
122+
123+
Iterator<Property> propertyIterator = referencedValue.getPropertyIterator();
124+
while(propertyIterator.hasNext())
125+
{
126+
Property referencedComponentProperty = propertyIterator.next();
127+
128+
if ( referencedComponentProperty.isComposite() ) {
129+
Property componentProperty = createComponentProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
130+
value.addProperty( componentProperty );
131+
}
132+
else {
133+
Property componentProperty = createSimpleProperty( referencedValue.getOwner(), isExplicitReference, columnByReferencedName, index, referencedComponentProperty );
134+
value.addProperty( componentProperty );
135+
}
136+
}
137+
138+
return property;
139+
}
140+
141+
142+
private Property createSimpleProperty(
143+
PersistentClass referencedPersistentClass,
144+
boolean isExplicitReference,
145+
Map<String, Ejb3JoinColumn> columnByReferencedName,
146+
AtomicInteger index,
147+
Property referencedProperty ) {
148+
Property property = new Property();
149+
property.setName( referencedProperty.getName() );
150+
//FIXME set optional?
151+
//property.setOptional( property.isOptional() );
152+
property.setPersistentClass( component.getOwner() );
153+
property.setPropertyAccessorName( referencedProperty.getPropertyAccessorName() );
154+
SimpleValue value = new SimpleValue( buildingContext.getMetadataCollector(), component.getTable() );
155+
property.setValue( value );
156+
final SimpleValue referencedValue = (SimpleValue) referencedProperty.getValue();
157+
value.setTypeName( referencedValue.getTypeName() );
158+
value.setTypeParameters( referencedValue.getTypeParameters() );
159+
final Iterator<Selectable> columns = referencedValue.getColumnIterator();
160+
161+
if ( joinColumns[0].isNameDeferred() ) {
162+
joinColumns[0].copyReferencedStructureAndCreateDefaultJoinColumns(
163+
referencedPersistentClass,
164+
columns,
165+
value);
166+
}
167+
else {
168+
//FIXME take care of Formula
169+
while ( columns.hasNext() ) {
170+
final Selectable selectable = columns.next();
171+
if ( ! Column.class.isInstance( selectable ) ) {
172+
log.debug( "Encountered formula definition; skipping" );
173+
continue;
174+
}
175+
final Column column = (Column) selectable;
176+
final Ejb3JoinColumn joinColumn;
177+
String logicalColumnName = null;
178+
if ( isExplicitReference ) {
179+
final String columnName = column.getName();
180+
logicalColumnName = buildingContext.getMetadataCollector().getLogicalColumnName(
181+
referencedPersistentClass.getTable(),
182+
columnName
183+
);
184+
//JPA 2 requires referencedColumnNames to be case insensitive
185+
joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) );
113186
}
114187
else {
115-
//FIXME take care of Formula
116-
while ( columns.hasNext() ) {
117-
final Selectable selectable = columns.next();
118-
if ( ! Column.class.isInstance( selectable ) ) {
119-
log.debug( "Encountered formula definition; skipping" );
120-
continue;
121-
}
122-
final Column column = (Column) selectable;
123-
final Ejb3JoinColumn joinColumn;
124-
String logicalColumnName = null;
125-
if ( isExplicitReference ) {
126-
final String columnName = column.getName();
127-
logicalColumnName = buildingContext.getMetadataCollector().getLogicalColumnName(
128-
referencedPersistentClass.getTable(),
129-
columnName
130-
);
131-
//JPA 2 requires referencedColumnNames to be case insensitive
132-
joinColumn = columnByReferencedName.get( logicalColumnName.toLowerCase(Locale.ROOT ) );
133-
}
134-
else {
135-
joinColumn = columnByReferencedName.get( "" + index );
136-
index++;
137-
}
138-
if ( joinColumn == null && ! joinColumns[0].isNameDeferred() ) {
139-
throw new AnnotationException(
140-
isExplicitReference ?
141-
"Unable to find column reference in the @MapsId mapping: " + logicalColumnName :
142-
"Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: " + referencedEntityName
143-
);
144-
}
145-
final String columnName = joinColumn == null || joinColumn.isNameDeferred() ? "tata_" + column.getName() : joinColumn
146-
.getName();
147-
value.addColumn( new Column( columnName ) );
148-
column.setValue( value );
149-
}
188+
joinColumn = columnByReferencedName.get( "" + index.get() );
189+
index.getAndIncrement();
150190
}
151-
component.addProperty( property );
191+
if ( joinColumn == null && ! joinColumns[0].isNameDeferred() ) {
192+
throw new AnnotationException(
193+
isExplicitReference ?
194+
"Unable to find column reference in the @MapsId mapping: " + logicalColumnName :
195+
"Implicit column reference in the @MapsId mapping fails, try to use explicit referenceColumnNames: " + referencedEntityName
196+
);
197+
}
198+
final String columnName = joinColumn == null || joinColumn.isNameDeferred() ? "tata_" + column.getName() : joinColumn
199+
.getName();
200+
value.addColumn( new Column( columnName ) );
201+
if ( joinColumn != null ) {
202+
joinColumn.linkWithValue( value );
203+
}
204+
column.setValue( value );
152205
}
153206
}
207+
return property;
208+
}
209+
210+
public boolean dependentUpon( CopyIdentifierComponentSecondPass other ) {
211+
return this.referencedEntityName.equals( other.component.getOwner().getEntityName() );
154212
}
155213
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.hibernate.test.annotations.derivedidentities.e3.b2;
2+
3+
import javax.persistence.EmbeddedId;
4+
import javax.persistence.Entity;
5+
import javax.persistence.ManyToOne;
6+
import javax.persistence.MapsId;
7+
8+
@Entity
9+
public class Dependent {
10+
11+
@EmbeddedId
12+
DependentId id;
13+
14+
@MapsId("empPK")
15+
@ManyToOne
16+
Employee emp;
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.hibernate.test.annotations.derivedidentities.e3.b2;
2+
3+
import javax.persistence.Embeddable;
4+
import java.io.Serializable;
5+
6+
@Embeddable
7+
public class DependentId implements Serializable {
8+
String name;
9+
EmployeeId empPK;
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* Copyright (c) 2011, Red Hat Inc. or third-party contributors as
5+
* indicated by the @author tags or express copyright attribution
6+
* statements applied by the authors. All third-party contributions are
7+
* distributed under license by Red Hat Inc.
8+
*
9+
* This copyrighted material is made available to anyone wishing to use, modify,
10+
* copy, or redistribute it subject to the terms and conditions of the GNU
11+
* Lesser General Public License, as published by the Free Software Foundation.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16+
* for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public License
19+
* along with this distribution; if not, write to:
20+
* Free Software Foundation, Inc.
21+
* 51 Franklin Street, Fifth Floor
22+
* Boston, MA 02110-1301 USA
23+
*/
24+
package org.hibernate.test.annotations.derivedidentities.e3.b2;
25+
26+
import org.hibernate.Session;
27+
import org.hibernate.test.util.SchemaUtil;
28+
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
29+
import org.junit.Test;
30+
31+
import static org.junit.Assert.assertEquals;
32+
import static org.junit.Assert.assertNotNull;
33+
import static org.junit.Assert.assertTrue;
34+
35+
/**
36+
* @author Emmanuel Bernard
37+
* @author Matt Drees
38+
*/
39+
public class DerivedIdentityEmbeddedIdParentEmbeddedIdGrandparentEmbeddedIdDepTest extends BaseNonConfigCoreFunctionalTestCase {
40+
@Test
41+
public void testManyToOne() throws Exception {
42+
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_firstName", metadata() ) );
43+
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "emp_lastName", metadata() ) );
44+
assertTrue( SchemaUtil.isColumnPresent( "Dependent", "name", metadata() ) );
45+
assertTrue( !SchemaUtil.isColumnPresent( "Dependent", "firstName", metadata() ) );
46+
assertTrue( !SchemaUtil.isColumnPresent( "Dependent", "lastName", metadata() ) );
47+
48+
assertTrue( SchemaUtil.isColumnPresent( "Policy", "dep_emp_firstName", metadata() ) );
49+
assertTrue( SchemaUtil.isColumnPresent( "Policy", "dep_emp_lastName", metadata() ) );
50+
assertTrue( SchemaUtil.isColumnPresent( "Policy", "type", metadata() ) );
51+
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "firstName", metadata() ) );
52+
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "lastName", metadata() ) );
53+
assertTrue( !SchemaUtil.isColumnPresent( "Policy", "name", metadata() ) );
54+
55+
56+
final Employee e = new Employee();
57+
e.empId = new EmployeeId();
58+
e.empId.firstName = "Emmanuel";
59+
e.empId.lastName = "Bernard";
60+
final Session s = openSession();
61+
s.getTransaction().begin();
62+
s.persist( e );
63+
final Dependent d = new Dependent();
64+
d.emp = e;
65+
d.id = new DependentId();
66+
d.id.name = "Doggy";
67+
s.persist( d );
68+
Policy p = new Policy();
69+
p.dep = d;
70+
p.id = new PolicyId();
71+
p.id.type = "Vet Insurance";
72+
s.persist( p );
73+
74+
s.flush();
75+
s.clear();
76+
p = (Policy) s.get( Policy.class, p.id );
77+
assertNotNull( p.dep );
78+
assertEquals( e.empId.firstName, p.dep.emp.empId.firstName );
79+
s.getTransaction().rollback();
80+
s.close();
81+
}
82+
83+
@Override
84+
protected Class<?>[] getAnnotatedClasses() {
85+
return new Class<?>[]{Policy.class, Dependent.class, Employee.class};
86+
}
87+
}

0 commit comments

Comments
 (0)