HHH-16336 Allow same table and subtypes in mappedBy check

This commit is contained in:
Marco Belladelli 2023-03-20 11:07:10 +01:00 committed by Christian Beikov
parent 15873c17fb
commit 892976187b
6 changed files with 265 additions and 20 deletions

View File

@ -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;
}
}

View File

@ -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() );

View File

@ -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(

View File

@ -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;

View File

@ -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;

View File

@ -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;