diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java index f96a0a74b0..a73dff94b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotatedJoinColumn.java @@ -251,7 +251,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn { String suffixForDefaultColumnName, MetadataBuildingContext buildingContext) { if ( ann != null ) { - if ( BinderHelper.isEmptyAnnotationValue( mappedBy ) ) { + if ( !BinderHelper.isEmptyOrNullAnnotationValue( mappedBy ) ) { throw new AnnotationException( "Illegal attempt to define a @JoinColumn with a mappedBy association: " + BinderHelper.getRelativePath( propertyHolder, propertyName ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java new file mode 100644 index 0000000000..36003c0482 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/onetoone/OverrideOneToOneJoinColumnTest.java @@ -0,0 +1,202 @@ +package org.hibernate.orm.test.annotations.onetoone; + +import java.util.Collection; +import java.util.Optional; + +import org.hibernate.AnnotationException; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.mapping.ForeignKey; +import org.hibernate.mapping.Table; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AssociationOverride; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToOne; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Aresnii Skvortsov + */ +@TestForIssue(jiraKey = "HHH-4384") +@BaseUnitTest +public class OverrideOneToOneJoinColumnTest { + + @Test + public void allowIfJoinColumnIsAbsent() { + try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) { + final Metadata metadata = new MetadataSources( ssr ) + .addAnnotatedClass( Person.class ) + .addAnnotatedClass( State.class ) + .buildMetadata(); + + final Table personTable = metadata.getDatabase().getDefaultNamespace().locateTable( + Identifier.toIdentifier( "PERSON_TABLE" ) ); + final Collection foreignKeys = personTable.getForeignKeys().values(); + assertThat( foreignKeys.size(), is( 1 ) ); + final Optional foreignKey = foreignKeys.stream().findFirst(); + + assertEquals( + "PERSON_ADDRESS_STATE", + foreignKey.get().getColumn( 0 ).getName(), + "Overridden join column name should be applied" + ); + } + } + + @Test + public void disallowOnSideWithMappedBy() { + try (StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build()) { + final AnnotationException ex = assertThrows( + AnnotationException.class, () -> + new MetadataSources( ssr ) + .addAnnotatedClass( Employee.class ) + .addAnnotatedClass( PartTimeEmployee.class ) + .addAnnotatedClass( Desk.class ) + .buildMetadata() + ); + + assertTrue( + ex.getMessage().startsWith( "Illegal attempt to define a @JoinColumn with a mappedBy association:" ), + "Should disallow exactly because of @JoinColumn override on side with mappedBy" + ); + } + } + + @Entity(name = "Person") + @jakarta.persistence.Table(name = "PERSON_TABLE") + public static class Person { + + private String id; + + private Address address; + + @Id + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Embedded + @AssociationOverride(name = "state", joinColumns = { @JoinColumn(name = "PERSON_ADDRESS_STATE") }) + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + @Embeddable + public static class Address { + + private String street; + + private String city; + + private State state; + + @OneToOne + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + } + + @Entity(name = "State") + @jakarta.persistence.Table(name = "STATE_TABLE") + public static class State { + + private String id; + + private String name; + + @Id + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @MappedSuperclass + public static class Employee { + + @Id + private Long id; + + private String name; + + @OneToOne(mappedBy = "employee") + protected Desk desk; + } + + @Entity + @AssociationOverride(name = "desk", + joinColumns = @JoinColumn(name = "PARTTIMEEMPLOYEE_DESK")) + public static class PartTimeEmployee extends Employee { + + } + + @Entity(name = "Desk") + public static class Desk { + @Id + private Long id; + + @OneToOne + private PartTimeEmployee employee; + + private String location; + } + + +}