HHH-16336 Allow same table and subtypes in mappedBy check
This commit is contained in:
parent
15873c17fb
commit
892976187b
|
@ -1076,7 +1076,8 @@ public class BinderHelper {
|
|||
String mappedBy,
|
||||
Value targetValue,
|
||||
String propertyName,
|
||||
PropertyHolder propertyHolder) {
|
||||
PropertyHolder propertyHolder,
|
||||
Map<String, PersistentClass> persistentClasses) {
|
||||
final ToOne toOne;
|
||||
if ( targetValue instanceof Collection ) {
|
||||
toOne = (ToOne) ( (Collection) targetValue ).getElement();
|
||||
|
@ -1085,13 +1086,14 @@ public class BinderHelper {
|
|||
toOne = (ToOne) targetValue;
|
||||
}
|
||||
final String referencedEntityName = toOne.getReferencedEntityName();
|
||||
PersistentClass referencedClass = propertyHolder.getPersistentClass();
|
||||
while ( referencedClass != null ) {
|
||||
if ( referencedClass.getEntityName().equals( referencedEntityName ) ) {
|
||||
final PersistentClass referencedClass = persistentClasses.get( referencedEntityName );
|
||||
PersistentClass ownerClass = propertyHolder.getPersistentClass();
|
||||
while ( ownerClass != null ) {
|
||||
if ( checkReferencedClass( ownerClass, referencedClass ) ) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
referencedClass = referencedClass.getSuperclass();
|
||||
ownerClass = getSuperPersistentClass( ownerClass );
|
||||
}
|
||||
}
|
||||
throw new AnnotationException(
|
||||
|
@ -1101,4 +1103,31 @@ public class BinderHelper {
|
|||
+ "', expected '" + propertyHolder.getEntityName() + "'"
|
||||
);
|
||||
}
|
||||
|
||||
private static boolean checkReferencedClass(PersistentClass ownerClass, PersistentClass referencedClass) {
|
||||
while ( referencedClass != null ) {
|
||||
// Allow different entity types as long as they map to the same table
|
||||
if ( ownerClass.getTable() == referencedClass.getTable() ) {
|
||||
return true;
|
||||
}
|
||||
referencedClass = getSuperPersistentClass( referencedClass );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static PersistentClass getSuperPersistentClass(PersistentClass persistentClass) {
|
||||
return persistentClass.getSuperclass() != null ? persistentClass.getSuperclass()
|
||||
: getSuperPersistentClass( persistentClass.getSuperMappedSuperclass() );
|
||||
}
|
||||
|
||||
private static PersistentClass getSuperPersistentClass(MappedSuperclass mappedSuperclass) {
|
||||
if ( mappedSuperclass != null ) {
|
||||
final PersistentClass superClass = mappedSuperclass.getSuperPersistentClass();
|
||||
if ( superClass != null ) {
|
||||
return superClass;
|
||||
}
|
||||
return getSuperPersistentClass( mappedSuperclass.getSuperMappedSuperclass() );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1540,8 +1540,7 @@ public abstract class CollectionBinder {
|
|||
* return true if it's a Fk, false if it's an association table
|
||||
*/
|
||||
protected boolean bindStarToManySecondPass(Map<String, PersistentClass> persistentClasses) {
|
||||
final PersistentClass persistentClass = persistentClasses.get( getElementType().getName() );
|
||||
if ( noAssociationTable( persistentClass ) ) {
|
||||
if ( noAssociationTable( persistentClasses ) ) {
|
||||
//this is a foreign key
|
||||
bindOneToManySecondPass( persistentClasses );
|
||||
return true;
|
||||
|
@ -1553,7 +1552,10 @@ public abstract class CollectionBinder {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isReversePropertyInJoin(XClass elementType, PersistentClass persistentClass) {
|
||||
private boolean isReversePropertyInJoin(
|
||||
XClass elementType,
|
||||
PersistentClass persistentClass,
|
||||
Map<String, PersistentClass> persistentClasses) {
|
||||
if ( persistentClass != null && isUnownedCollection() ) {
|
||||
final Property mappedByProperty;
|
||||
try {
|
||||
|
@ -1566,7 +1568,7 @@ public abstract class CollectionBinder {
|
|||
+ "' which does not exist in the target entity '" + elementType.getName() + "'"
|
||||
);
|
||||
}
|
||||
checkMappedByType( mappedBy, mappedByProperty.getValue(), propertyName, propertyHolder );
|
||||
checkMappedByType( mappedBy, mappedByProperty.getValue(), propertyName, propertyHolder, persistentClasses );
|
||||
return persistentClass.getJoinNumber( mappedByProperty ) != 0;
|
||||
}
|
||||
else {
|
||||
|
@ -1574,9 +1576,10 @@ public abstract class CollectionBinder {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean noAssociationTable(PersistentClass persistentClass) {
|
||||
private boolean noAssociationTable(Map<String, PersistentClass> persistentClasses) {
|
||||
final PersistentClass persistentClass = persistentClasses.get( getElementType().getName() );
|
||||
return persistentClass != null
|
||||
&& !isReversePropertyInJoin( getElementType(), persistentClass )
|
||||
&& !isReversePropertyInJoin( getElementType(), persistentClass, persistentClasses )
|
||||
&& oneToMany
|
||||
&& !isExplicitAssociationTable
|
||||
&& ( implicitJoinColumn() || explicitForeignJoinColumn() );
|
||||
|
|
|
@ -153,7 +153,13 @@ public class OneToOneSecondPass implements SecondPass {
|
|||
+ "' of the target entity type '" + oneToOne.getReferencedEntityName()
|
||||
+ "' which is not a '@OneToOne' or '@ManyToOne' association" );
|
||||
}
|
||||
checkMappedByType( mappedBy, targetProperty.getValue(), oneToOne.getPropertyName(), propertyHolder );
|
||||
checkMappedByType(
|
||||
mappedBy,
|
||||
targetProperty.getValue(),
|
||||
oneToOne.getPropertyName(),
|
||||
propertyHolder,
|
||||
persistentClasses
|
||||
);
|
||||
}
|
||||
|
||||
private void bindTargetManyToOne(
|
||||
|
|
|
@ -17,7 +17,11 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -29,8 +33,7 @@ public class ManyToManyMappedByTypeTest {
|
|||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityACorrect.class )
|
||||
.addAnnotatedClass( EntityBCorrect.class )
|
||||
.addAnnotatedClass( EntityC.class );
|
||||
.addAnnotatedClass( EntityBCorrect.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +60,35 @@ public class ManyToManyMappedByTypeTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectSameTable() {
|
||||
// Allow different entity types which map to the same table since the mappedBy
|
||||
// in that case would still make sense from a database perspective
|
||||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityACorrect.class )
|
||||
.addAnnotatedClass( EntityBCorrect.class )
|
||||
.addAnnotatedClass( EntityA2Correct.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectSubtype() {
|
||||
// Allow mappedBy subtypes given that users might want to filter the
|
||||
// association with custom @Where annotations and still use a supertype
|
||||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityASupertype.class )
|
||||
.addAnnotatedClass( EntityAMappedSuperclass.class )
|
||||
.addAnnotatedClass( EntityASubtype.class )
|
||||
.addAnnotatedClass( EntityBSubtype.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "EntityACorrect" )
|
||||
@Table( name = "entity_a_correct" )
|
||||
public static class EntityACorrect {
|
||||
@Id
|
||||
private Long id;
|
||||
|
@ -75,6 +106,43 @@ public class ManyToManyMappedByTypeTest {
|
|||
private List<EntityACorrect> parents;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityA2Correct" )
|
||||
@Table( name = "entity_a_correct" )
|
||||
public static class EntityA2Correct {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToMany( mappedBy = "parents" )
|
||||
private List<EntityBCorrect> children;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityASupertype" )
|
||||
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
|
||||
public static class EntityASupertype {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToMany( mappedBy = "parents" )
|
||||
private List<EntityBSubtype> children;
|
||||
}
|
||||
|
||||
@MappedSuperclass
|
||||
public static class EntityAMappedSuperclass extends EntityASupertype {
|
||||
}
|
||||
|
||||
@Entity( name = "EntityASubtype" )
|
||||
public static class EntityASubtype extends EntityAMappedSuperclass {
|
||||
}
|
||||
|
||||
@Entity( name = "EntityBSubtype" )
|
||||
public static class EntityBSubtype {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToMany
|
||||
private List<EntityASubtype> parents;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityAWrong" )
|
||||
public static class EntityAWrong {
|
||||
@Id
|
||||
|
@ -106,6 +174,7 @@ public class ManyToManyMappedByTypeTest {
|
|||
}
|
||||
|
||||
@Entity( name = "SuperclassEntity" )
|
||||
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
|
||||
public static class SuperclassEntity {
|
||||
@Id
|
||||
private Long id;
|
||||
|
|
|
@ -17,8 +17,12 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -30,8 +34,7 @@ public class OneToManyMappedByTypeTest {
|
|||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityACorrect.class )
|
||||
.addAnnotatedClass( EntityBCorrect.class )
|
||||
.addAnnotatedClass( EntityC.class );
|
||||
.addAnnotatedClass( EntityBCorrect.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +61,35 @@ public class OneToManyMappedByTypeTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectSameTable() {
|
||||
// Allow different entity types which map to the same table since the mappedBy
|
||||
// in that case would still make sense from a database perspective
|
||||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityACorrect.class )
|
||||
.addAnnotatedClass( EntityBCorrect.class )
|
||||
.addAnnotatedClass( EntityA2Correct.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectSubtype() {
|
||||
// Allow mappedBy subtypes given that users might want to filter the
|
||||
// association with custom @Where annotations and still use a supertype
|
||||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityASupertype.class )
|
||||
.addAnnotatedClass( EntityAMappedSuperclass.class )
|
||||
.addAnnotatedClass( EntityASubtype.class )
|
||||
.addAnnotatedClass( EntityBSubtype.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "EntityACorrect" )
|
||||
@Table( name = "entity_a_correct" )
|
||||
public static class EntityACorrect {
|
||||
@Id
|
||||
private Long id;
|
||||
|
@ -76,6 +107,43 @@ public class OneToManyMappedByTypeTest {
|
|||
private EntityACorrect parent;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityA2Correct" )
|
||||
@Table( name = "entity_a_correct" )
|
||||
public static class EntityA2Correct {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@OneToMany( mappedBy = "parent" )
|
||||
private List<EntityBCorrect> children;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityASupertype" )
|
||||
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
|
||||
public static class EntityASupertype {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@OneToMany( mappedBy = "parent" )
|
||||
private List<EntityBSubtype> children;
|
||||
}
|
||||
|
||||
@MappedSuperclass
|
||||
public static class EntityAMappedSuperclass extends EntityASupertype {
|
||||
}
|
||||
|
||||
@Entity( name = "EntityASubtype" )
|
||||
public static class EntityASubtype extends EntityAMappedSuperclass {
|
||||
}
|
||||
|
||||
@Entity( name = "EntityBSubtype" )
|
||||
public static class EntityBSubtype {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@ManyToOne
|
||||
private EntityASubtype parent;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityAWrong" )
|
||||
public static class EntityAWrong {
|
||||
@Id
|
||||
|
@ -107,6 +175,7 @@ public class OneToManyMappedByTypeTest {
|
|||
}
|
||||
|
||||
@Entity( name = "SuperclassEntity" )
|
||||
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
|
||||
public static class SuperclassEntity {
|
||||
@Id
|
||||
private Long id;
|
||||
|
|
|
@ -15,7 +15,11 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Inheritance;
|
||||
import jakarta.persistence.InheritanceType;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -27,8 +31,7 @@ public class OneToOneMappedByTypeTest {
|
|||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityACorrect.class )
|
||||
.addAnnotatedClass( EntityBCorrect.class )
|
||||
.addAnnotatedClass( EntityC.class );
|
||||
.addAnnotatedClass( EntityBCorrect.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +58,35 @@ public class OneToOneMappedByTypeTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectSameTable() {
|
||||
// Allow different entity types which map to the same table since the mappedBy
|
||||
// in that case would still make sense from a database perspective
|
||||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityACorrect.class )
|
||||
.addAnnotatedClass( EntityBCorrect.class )
|
||||
.addAnnotatedClass( EntityA2Correct.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCorrectSubtype() {
|
||||
// Allow mappedBy subtypes given that users might want to filter the
|
||||
// association with custom @Where annotations and still use a supertype
|
||||
try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) {
|
||||
final MetadataSources metadataSources = new MetadataSources( ssr )
|
||||
.addAnnotatedClass( EntityASupertype.class )
|
||||
.addAnnotatedClass( EntityAMappedSuperclass.class )
|
||||
.addAnnotatedClass( EntityASubtype.class )
|
||||
.addAnnotatedClass( EntityBSubtype.class );
|
||||
assertDoesNotThrow( () -> metadataSources.buildMetadata() );
|
||||
}
|
||||
}
|
||||
|
||||
@Entity( name = "EntityACorrect" )
|
||||
@Table( name = "entity_a_correct" )
|
||||
public static class EntityACorrect {
|
||||
@Id
|
||||
private Long id;
|
||||
|
@ -73,6 +104,43 @@ public class OneToOneMappedByTypeTest {
|
|||
private EntityACorrect parent;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityA2Correct" )
|
||||
@Table( name = "entity_a_correct" )
|
||||
public static class EntityA2Correct {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@OneToOne( mappedBy = "parent" )
|
||||
private EntityBCorrect child;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityASupertype" )
|
||||
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
|
||||
public static class EntityASupertype {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@OneToOne( mappedBy = "parent" )
|
||||
private EntityBSubtype child;
|
||||
}
|
||||
|
||||
@MappedSuperclass
|
||||
public static class EntityAMappedSuperclass extends EntityASupertype {
|
||||
}
|
||||
|
||||
@Entity( name = "EntityASubtype" )
|
||||
public static class EntityASubtype extends EntityAMappedSuperclass {
|
||||
}
|
||||
|
||||
@Entity( name = "EntityBSubtype" )
|
||||
public static class EntityBSubtype {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@OneToOne
|
||||
private EntityASubtype parent;
|
||||
}
|
||||
|
||||
@Entity( name = "EntityAWrong" )
|
||||
public static class EntityAWrong {
|
||||
@Id
|
||||
|
@ -104,6 +172,7 @@ public class OneToOneMappedByTypeTest {
|
|||
}
|
||||
|
||||
@Entity( name = "SuperclassEntity" )
|
||||
@Inheritance( strategy = InheritanceType.TABLE_PER_CLASS )
|
||||
public static class SuperclassEntity {
|
||||
@Id
|
||||
private Long id;
|
||||
|
|
Loading…
Reference in New Issue