HHH-16463 fix use of @PrimaryKeyJoinColumn with @MapsId
supporting this mapping is required by JPA
This commit is contained in:
parent
2c85e5d190
commit
914227de93
|
@ -21,7 +21,6 @@ import org.hibernate.boot.model.naming.Identifier;
|
|||
import org.hibernate.boot.model.naming.ImplicitJoinColumnNameSource;
|
||||
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
|
||||
import org.hibernate.boot.model.naming.ImplicitPrimaryKeyJoinColumnNameSource;
|
||||
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
|
||||
import org.hibernate.boot.model.relational.Database;
|
||||
import org.hibernate.boot.model.source.spi.AttributePath;
|
||||
import org.hibernate.boot.spi.InFlightMetadataCollector;
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
*/
|
||||
package org.hibernate.boot.model.internal;
|
||||
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.PrimaryKeyJoinColumn;
|
||||
import jakarta.persistence.PrimaryKeyJoinColumns;
|
||||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.annotations.Columns;
|
||||
import org.hibernate.annotations.Formula;
|
||||
|
@ -27,6 +31,8 @@ import jakarta.persistence.ManyToOne;
|
|||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromAnnotation;
|
||||
import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromNoAnnotation;
|
||||
import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnsFromAnnotations;
|
||||
|
@ -266,6 +272,32 @@ class ColumnsBuilder {
|
|||
}
|
||||
return joinColumn;
|
||||
}
|
||||
else if ( property.isAnnotationPresent( MapsId.class ) ) {
|
||||
// inelegant solution to HHH-16463, let the PrimaryKeyJoinColumn
|
||||
// masquerade as a regular JoinColumn (when a @OneToOne maps to
|
||||
// the primary key of the child table, it's more elegant and more
|
||||
// spec-compliant to map the association with @PrimaryKeyJoinColumn)
|
||||
if ( property.isAnnotationPresent( PrimaryKeyJoinColumn.class ) ) {
|
||||
final PrimaryKeyJoinColumn column = property.getAnnotation( PrimaryKeyJoinColumn.class );
|
||||
return new JoinColumn[] { new JoinColumnAdapter( column ) };
|
||||
}
|
||||
else if ( property.isAnnotationPresent( PrimaryKeyJoinColumns.class ) ) {
|
||||
final PrimaryKeyJoinColumns primaryKeyJoinColumns = property.getAnnotation( PrimaryKeyJoinColumns.class );
|
||||
final JoinColumn[] joinColumns = new JoinColumn[primaryKeyJoinColumns.value().length];
|
||||
final PrimaryKeyJoinColumn[] columns = primaryKeyJoinColumns.value();
|
||||
if ( columns.length == 0 ) {
|
||||
throw new AnnotationException( "Property '" + getPath( propertyHolder, inferredData)
|
||||
+ "' has an empty '@PrimaryKeyJoinColumns' annotation" );
|
||||
}
|
||||
for ( int i = 0; i < columns.length; i++ ) {
|
||||
joinColumns[i] = new JoinColumnAdapter( columns[i] );
|
||||
}
|
||||
return joinColumns;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
@ -288,4 +320,62 @@ class ColumnsBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ClassExplicitlyAnnotation")
|
||||
private static final class JoinColumnAdapter implements JoinColumn {
|
||||
private final PrimaryKeyJoinColumn column;
|
||||
|
||||
public JoinColumnAdapter(PrimaryKeyJoinColumn column) {
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return column.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String referencedColumnName() {
|
||||
return column.referencedColumnName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unique() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nullable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean insertable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String columnDefinition() {
|
||||
return column.columnDefinition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String table() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForeignKey foreignKey() {
|
||||
return column.foreignKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return JoinColumn.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,6 @@ import org.hibernate.boot.model.IdentifierGeneratorDefinition;
|
|||
import org.hibernate.boot.spi.AccessType;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.boot.spi.PropertyData;
|
||||
import org.hibernate.boot.spi.SecondPass;
|
||||
import org.hibernate.engine.OptimisticLockStyle;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.mapping.Collection;
|
||||
|
@ -58,7 +57,6 @@ import org.hibernate.mapping.GeneratorCreator;
|
|||
import org.hibernate.mapping.Join;
|
||||
import org.hibernate.mapping.KeyValue;
|
||||
import org.hibernate.mapping.MappedSuperclass;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.mapping.RootClass;
|
||||
import org.hibernate.mapping.SimpleValue;
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.onetoone.pkjoincolumn;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.PrimaryKeyJoinColumn;
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@JiraKey("HHH-16463")
|
||||
@Jpa(annotatedClasses = {OneToOneJoinColumnTest.Parent.class, OneToOneJoinColumnTest.Child.class},
|
||||
useCollectingStatementInspector = true)
|
||||
public class OneToOneJoinColumnTest {
|
||||
|
||||
static final String PARENT_ID = "parent_key";
|
||||
static final String CHILD_ID = "child_key";
|
||||
|
||||
@Test
|
||||
public void test(EntityManagerFactoryScope scope) {
|
||||
SQLStatementInspector collectingStatementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Parent parent = new Parent();
|
||||
parent.setId(2L);
|
||||
Child child = new Child();
|
||||
parent.setChild(child);
|
||||
child.setParent(parent);
|
||||
entityManager.persist(parent);
|
||||
}
|
||||
);
|
||||
collectingStatementInspector.clear();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Parent parent = entityManager.find( Parent.class, 2L );
|
||||
assertNotNull(parent.getChild());
|
||||
}
|
||||
);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfJoins(0, 1);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfOccurrenceInQueryNoSpace(0, CHILD_ID, 2);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfOccurrenceInQueryNoSpace(0, PARENT_ID, 3);
|
||||
}
|
||||
|
||||
@Entity(name = "Parent")
|
||||
static class Parent implements Serializable {
|
||||
|
||||
@Id
|
||||
@Column(name = PARENT_ID)
|
||||
private Long id;
|
||||
|
||||
//@PrimaryKeyJoinColumn
|
||||
@OneToOne(mappedBy = "parent", cascade = CascadeType.PERSIST)
|
||||
private Child child;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Child getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(Child optionalData) {
|
||||
this.child = optionalData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Entity(name = "Child")
|
||||
static class Child implements Serializable {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
// this is the thing we always allowed in the past,
|
||||
// but really @PrimaryKeyJoinColumn is more elegant
|
||||
@MapsId
|
||||
@OneToOne
|
||||
@JoinColumn(name = CHILD_ID)
|
||||
private Parent parent;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Parent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Parent parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.onetoone.pkjoincolumn;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@JiraKey("HHH-16463")
|
||||
@Jpa(annotatedClasses = {OneToOnePkJoinColumnNoMapsIdTest.Parent.class, OneToOnePkJoinColumnNoMapsIdTest.Child.class},
|
||||
useCollectingStatementInspector = true)
|
||||
public class OneToOnePkJoinColumnNoMapsIdTest {
|
||||
|
||||
static final String PARENT_ID = "parent_key";
|
||||
static final String CHILD_ID = "child_key";
|
||||
|
||||
@Test
|
||||
public void test(EntityManagerFactoryScope scope) {
|
||||
SQLStatementInspector collectingStatementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Parent parent = new Parent();
|
||||
parent.setId(2L);
|
||||
Child child = new Child();
|
||||
child.setId(2L);
|
||||
parent.setChild(child);
|
||||
child.setParent(parent);
|
||||
entityManager.persist(parent);
|
||||
}
|
||||
);
|
||||
collectingStatementInspector.clear();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Parent parent = entityManager.find( Parent.class, 2L );
|
||||
assertNotNull(parent.getChild());
|
||||
}
|
||||
);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfJoins(0, 1);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfOccurrenceInQueryNoSpace(0, CHILD_ID, 2);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfOccurrenceInQueryNoSpace(0, PARENT_ID, 3);
|
||||
}
|
||||
|
||||
@Entity(name = "Parent")
|
||||
static class Parent implements Serializable {
|
||||
|
||||
@Id
|
||||
@Column(name = PARENT_ID)
|
||||
private Long id;
|
||||
|
||||
//@PrimaryKeyJoinColumn
|
||||
@OneToOne(mappedBy = "parent", cascade = CascadeType.PERSIST)
|
||||
private Child child;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Child getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(Child optionalData) {
|
||||
this.child = optionalData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Entity(name = "Child")
|
||||
static class Child implements Serializable {
|
||||
|
||||
@Id
|
||||
@Column(name = CHILD_ID)
|
||||
private Long id;
|
||||
|
||||
// this is an alternative to @MapsId, and was
|
||||
// the way to do it in older versions of JPA,
|
||||
// but has the disadvantages that:
|
||||
// a) you need to map the column twice, and
|
||||
// b) you need to manually assign the id
|
||||
@OneToOne(optional = false)
|
||||
@JoinColumn(name = CHILD_ID)
|
||||
private Parent parent;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Parent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Parent parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.orm.test.mapping.onetoone.pkjoincolumn;
|
||||
|
||||
import java.io.Serializable;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
import jakarta.persistence.PrimaryKeyJoinColumn;
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@JiraKey("HHH-16463")
|
||||
@Jpa(annotatedClasses = {OneToOnePkJoinColumnTest.Parent.class, OneToOnePkJoinColumnTest.Child.class},
|
||||
useCollectingStatementInspector = true)
|
||||
public class OneToOnePkJoinColumnTest {
|
||||
|
||||
static final String PARENT_ID = "parent_key";
|
||||
static final String CHILD_ID = "child_key";
|
||||
|
||||
@Test
|
||||
public void test(EntityManagerFactoryScope scope) {
|
||||
SQLStatementInspector collectingStatementInspector = scope.getCollectingStatementInspector();
|
||||
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Parent parent = new Parent();
|
||||
parent.setId(2L);
|
||||
Child child = new Child();
|
||||
parent.setChild(child);
|
||||
child.setParent(parent);
|
||||
entityManager.persist(parent);
|
||||
}
|
||||
);
|
||||
collectingStatementInspector.clear();
|
||||
scope.inTransaction(
|
||||
entityManager -> {
|
||||
Parent parent = entityManager.find( Parent.class, 2L );
|
||||
assertNotNull(parent.getChild());
|
||||
}
|
||||
);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfJoins(0, 1);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfOccurrenceInQueryNoSpace(0, CHILD_ID, 2);
|
||||
collectingStatementInspector
|
||||
.assertNumberOfOccurrenceInQueryNoSpace(0, PARENT_ID, 3);
|
||||
}
|
||||
|
||||
@Entity(name = "Parent")
|
||||
static class Parent implements Serializable {
|
||||
|
||||
@Id
|
||||
@Column(name = PARENT_ID)
|
||||
private Long id;
|
||||
|
||||
//@PrimaryKeyJoinColumn
|
||||
@OneToOne(mappedBy = "parent", cascade = CascadeType.PERSIST)
|
||||
private Child child;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Child getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(Child optionalData) {
|
||||
this.child = optionalData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Entity(name = "Child")
|
||||
static class Child implements Serializable {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
// this is the preferred way to do it I believe,
|
||||
// but Hibernate never recognized this mapping
|
||||
@MapsId
|
||||
@OneToOne
|
||||
@PrimaryKeyJoinColumn(name = CHILD_ID)
|
||||
private Parent parent;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Parent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Parent parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue