HHH-15111 MappingException is thrown for @JoinColumn with referencedColumnName on a @SecondaryTable

This commit is contained in:
Andrea Boriero 2022-03-09 17:50:20 +01:00 committed by Christian Beikov
parent cd78676608
commit 1d67993173
5 changed files with 150 additions and 7 deletions

View File

@ -69,6 +69,7 @@ import org.hibernate.cfg.PropertyData;
import org.hibernate.cfg.QuerySecondPass;
import org.hibernate.cfg.RecoverableException;
import org.hibernate.cfg.SecondPass;
import org.hibernate.cfg.SecondaryTableFromAnnotationSecondPass;
import org.hibernate.cfg.SecondaryTableSecondPass;
import org.hibernate.cfg.SetBasicValueTypeSecondPass;
import org.hibernate.cfg.UniqueConstraintHolder;
@ -1591,6 +1592,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
private ArrayList<FkSecondPass> fkSecondPassList;
private ArrayList<CreateKeySecondPass> createKeySecondPasList;
private ArrayList<SecondaryTableSecondPass> secondaryTableSecondPassList;
private ArrayList<SecondaryTableFromAnnotationSecondPass> secondaryTableFromAnnotationSecondPassesList;
private ArrayList<QuerySecondPass> querySecondPassList;
private ArrayList<ImplicitColumnNamingSecondPass> implicitColumnNamingSecondPassList;
@ -1618,6 +1620,12 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
else if ( secondPass instanceof SecondaryTableSecondPass ) {
addSecondaryTableSecondPass( (SecondaryTableSecondPass) secondPass, onTopOfTheQueue );
}
else if ( secondPass instanceof SecondaryTableFromAnnotationSecondPass ) {
addSecondaryTableFromAnnotationSecondPass(
(SecondaryTableFromAnnotationSecondPass) secondPass,
onTopOfTheQueue
);
}
else if ( secondPass instanceof QuerySecondPass ) {
addQuerySecondPass( (QuerySecondPass) secondPass, onTopOfTheQueue );
}
@ -1677,6 +1685,13 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
addSecondPass( secondPass, secondaryTableSecondPassList, onTopOfTheQueue );
}
private void addSecondaryTableFromAnnotationSecondPass(SecondaryTableFromAnnotationSecondPass secondPass, boolean onTopOfTheQueue){
if ( secondaryTableFromAnnotationSecondPassesList == null ) {
secondaryTableFromAnnotationSecondPassesList = new ArrayList<>();
}
addSecondPass( secondPass, secondaryTableFromAnnotationSecondPassesList, onTopOfTheQueue );
}
private void addQuerySecondPass(QuerySecondPass secondPass, boolean onTopOfTheQueue) {
if ( querySecondPassList == null ) {
querySecondPassList = new ArrayList<>();
@ -1765,6 +1780,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
private void processFkSecondPassesInOrder() {
if ( fkSecondPassList == null || fkSecondPassList.isEmpty() ) {
processSecondPasses( secondaryTableFromAnnotationSecondPassesList );
return;
}
@ -1798,6 +1814,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
sp.doSecondPass( getEntityBindingMap() );
}
processSecondPasses( secondaryTableFromAnnotationSecondPassesList );
processEndOfQueue( endOfQueueFkSecondPasses );
fkSecondPassList.clear();

View File

@ -705,6 +705,8 @@ public final class AnnotationBinder {
context.getMetadataCollector().addEntityBinding( persistentClass );
//Process secondary tables and complementary definitions (ie o.h.a.Table)
context.getMetadataCollector()
.addSecondPass( new SecondaryTableFromAnnotationSecondPass( entityBinder, propertyHolder, clazzToProcess ) );
context.getMetadataCollector()
.addSecondPass( new SecondaryTableSecondPass( entityBinder, propertyHolder, clazzToProcess ) );

View File

@ -0,0 +1,24 @@
package org.hibernate.cfg;
import java.util.Map;
import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
import org.hibernate.cfg.annotations.EntityBinder;
import org.hibernate.mapping.PersistentClass;
public class SecondaryTableFromAnnotationSecondPass implements SecondPass{
private final EntityBinder entityBinder;
private final PropertyHolder propertyHolder;
private final XAnnotatedElement annotatedClass;
public SecondaryTableFromAnnotationSecondPass(EntityBinder entityBinder, PropertyHolder propertyHolder, XAnnotatedElement annotatedClass) {
this.entityBinder = entityBinder;
this.propertyHolder = propertyHolder;
this.annotatedClass = annotatedClass;
}
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
entityBinder.finalSecondaryTableFromAnnotationBinding( propertyHolder );
}
}

View File

@ -12,6 +12,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import jakarta.persistence.Access;
import jakarta.persistence.Cacheable;
import jakarta.persistence.ConstraintMode;
@ -125,6 +127,9 @@ public class EntityBinder {
// atm we use both from here; HBM binding solely uses InFlightMetadataCollector.EntityTableXref
private final java.util.Map<String, Join> secondaryTables = new HashMap<>();
private final java.util.Map<String, Object> secondaryTableJoins = new HashMap<>();
private final java.util.Map<String, Join> secondaryTablesFromAnnotation = new HashMap<>();
private final java.util.Map<String, Object> secondaryTableFromAnnotationJoins = new HashMap<>();
private final List<Filter> filters = new ArrayList<>();
private boolean ignoreIdAnnotations;
private AccessType propertyAccessType = AccessType.DEFAULT;
@ -813,13 +818,28 @@ public class EntityBinder {
* Those operations has to be done after the id definition of the persistence class.
* ie after the properties parsing
*/
Iterator<Join> joins = secondaryTables.values().iterator();
Iterator<Object> joinColumns = secondaryTableJoins.values().iterator();
while ( joins.hasNext() ) {
Object uncastedColumn = joinColumns.next();
Join join = joins.next();
createPrimaryColumnsToSecondaryTable( uncastedColumn, propertyHolder, join );
for ( Map.Entry<String, Join> entrySet : secondaryTables.entrySet() ) {
if ( !secondaryTablesFromAnnotation.containsKey( entrySet.getKey() ) ) {
Object uncastedColumn = joinColumns.next();
createPrimaryColumnsToSecondaryTable( uncastedColumn, propertyHolder, entrySet.getValue() );
}
}
}
public void finalSecondaryTableFromAnnotationBinding(PropertyHolder propertyHolder) {
/*
* Those operations have to be done before the end of the FK second pass processing in order
* to find the join columns belonging to secondary tables
*/
Iterator<Object> joinColumns = secondaryTableFromAnnotationJoins.values().iterator();
for ( Map.Entry<String, Join> entrySet : secondaryTables.entrySet() ) {
if ( secondaryTablesFromAnnotation.containsKey( entrySet.getKey() ) ) {
Object uncastedColumn = joinColumns.next();
createPrimaryColumnsToSecondaryTable( uncastedColumn, propertyHolder, entrySet.getValue() );
}
}
}
@ -1106,8 +1126,15 @@ public class EntityBinder {
createPrimaryColumnsToSecondaryTable( joinColumns, propertyHolder, join );
}
else {
secondaryTables.put( table.getQuotedName(), join );
secondaryTableJoins.put( table.getQuotedName(), joinColumns );
final String quotedName = table.getQuotedName();
if ( secondaryTable != null ) {
secondaryTablesFromAnnotation.put( quotedName, join );
secondaryTableFromAnnotationJoins.put( quotedName, joinColumns );
}
else {
secondaryTableJoins.put( quotedName, joinColumns );
}
secondaryTables.put( quotedName, join );
}
return join;

View File

@ -0,0 +1,72 @@
package org.hibernate.orm.test.annotations.joincolumn;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.DiscriminatorColumn;
import jakarta.persistence.DiscriminatorValue;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.SecondaryTable;
import jakarta.persistence.Table;
@DomainModel(
annotatedClasses = {
JoinColumnWithSecondaryTableTest.Being.class,
JoinColumnWithSecondaryTableTest.Animal.class,
JoinColumnWithSecondaryTableTest.Cat.class,
JoinColumnWithSecondaryTableTest.Toy.class
}
)
@SessionFactory
@TestForIssue( jiraKey = "HHH-15111")
public class JoinColumnWithSecondaryTableTest {
@Test
public void testIt(SessionFactoryScope scope){
}
@Entity
@Table(name = "being")
@Inheritance
@DiscriminatorColumn(name = "type")
public static abstract class Being {
@Id
private Long id;
}
@Entity
@SecondaryTable(name = "animal")
public static abstract class Animal extends Being {
@Column(name = "uuid", table = "animal")
private String uuid;
}
@Entity
@SecondaryTable(name = "cat")
@DiscriminatorValue(value = "CAT")
public static class Cat extends Animal {
@Column(name = "name", table = "cat")
private String name;
}
@Entity
public static class Toy {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "animal_uuid", referencedColumnName = "uuid")
private Cat cat;
}
}