Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HHH-18702 Exception using @EmbeddedId with @OneToMany that refers to an alternate key column #9071

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 54 additions & 24 deletions hibernate-core/src/main/java/org/hibernate/type/EntityType.java
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,15 @@ public int getHashCode(Object x, SessionFactoryImplementor factory) {
}
else {
assert uniqueKeyPropertyName != null;
final Object uniqueKey;
final Type keyType = persister.getPropertyType( uniqueKeyPropertyName );
return keyType.getHashCode( x, factory );
if ( keyType.getReturnedClass().isAssignableFrom( x.getClass() ) ) {
uniqueKey = x;
}
else {
uniqueKey = persister.getPropertyValue( x, uniqueKeyPropertyName );
}
return keyType.getHashCode( uniqueKey, factory );
}
}

Expand All @@ -376,39 +383,62 @@ public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) {
}

final EntityPersister persister = getAssociatedEntityPersister( factory );
final Class<?> mappedClass = persister.getMappedClass();
Object xid;
final LazyInitializer lazyInitializerX = extractLazyInitializer( x );
if ( lazyInitializerX != null ) {
xid = lazyInitializerX.getInternalIdentifier();
}
else {
if ( mappedClass.isAssignableFrom( x.getClass() ) ) {
xid = persister.getIdentifier( x );
if ( isReferenceToPrimaryKey() ) {
final Class<?> mappedClass = persister.getMappedClass();
Object xid;
final LazyInitializer lazyInitializerX = extractLazyInitializer( x );
if ( lazyInitializerX != null ) {
xid = lazyInitializerX.getInternalIdentifier();
}
else {
//JPA 2 case where @IdClass contains the id and not the associated entity
xid = x;
if ( mappedClass.isAssignableFrom( x.getClass() ) ) {
xid = persister.getIdentifier( x );
}
else {
//JPA 2 case where @IdClass contains the id and not the associated entity
xid = x;
}
}
}

Object yid;
final LazyInitializer lazyInitializerY = extractLazyInitializer( y );
if ( lazyInitializerY != null ) {
yid = lazyInitializerY.getInternalIdentifier();
Object yid;
final LazyInitializer lazyInitializerY = extractLazyInitializer( y );
if ( lazyInitializerY != null ) {
yid = lazyInitializerY.getInternalIdentifier();
}
else {
if ( mappedClass.isAssignableFrom( y.getClass() ) ) {
yid = persister.getIdentifier( y );
}
else {
//JPA 2 case where @IdClass contains the id and not the associated entity
yid = y;
}
}

// Check for reference equality first as the type-specific checks by IdentifierType are sometimes non-trivial
return (xid == yid) || persister.getIdentifierType().isEqual( xid, yid, factory );
}
else {
if ( mappedClass.isAssignableFrom( y.getClass() ) ) {
yid = persister.getIdentifier( y );
assert uniqueKeyPropertyName != null;
final Object xUniqueKey;
final Type keyType = persister.getPropertyType( uniqueKeyPropertyName );
if ( keyType.getReturnedClass().isAssignableFrom( x.getClass() ) ) {
xUniqueKey = x;
}
else {
//JPA 2 case where @IdClass contains the id and not the associated entity
yid = y;
xUniqueKey = persister.getPropertyValue( x, uniqueKeyPropertyName );
}
}

// Check for reference equality first as the type-specific checks by IdentifierType are sometimes non-trivial
return ( xid == yid ) || persister.getIdentifierType().isEqual( xid, yid, factory );
final Object yUniqueKey;
if ( keyType.getReturnedClass().isAssignableFrom( y.getClass() ) ) {
yUniqueKey = y;
}
else {
yUniqueKey = persister.getPropertyValue( y, uniqueKeyPropertyName );
}
return (xUniqueKey == yUniqueKey)
|| keyType.isEqual( xUniqueKey, yUniqueKey, factory );
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.annotations.derivedidentities;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@JiraKey("HHH-18702")
@DomainModel(
annotatedClasses = {
OneToManyEmbeddableId.Parent.class,
OneToManyEmbeddableId.FirstChild.class,
OneToManyEmbeddableId.SecondChild.class,
}
)
@SessionFactory
public class OneToManyEmbeddableId {
private static final BigDecimal FIRST_CHILD_CODE = new BigDecimal( 2 );

@BeforeAll
public static void init(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Parent parent = new Parent( BigDecimal.TEN, "Lio" );
FirstChild firstChild = new FirstChild( parent, BigDecimal.ONE, FIRST_CHILD_CODE );
SecondChild secondChild = new SecondChild( firstChild, BigDecimal.TEN, "Al" );
firstChild.addChild( secondChild );
session.persist( parent );
session.persist( firstChild );
session.persist( secondChild );
}
);
}

@Test
public void propertyNavigationTest(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
FirstChild firstChild = session.createQuery(
"select f from FirstChild f where f.code = :code", FirstChild.class )
.setParameter( "code", FIRST_CHILD_CODE ).getSingleResult();

assertThat( firstChild ).isNotNull();

assertThat( firstChild.getChildren() ).hasSize( 1 );
}
);

}

@Entity(name = "TestEntity")
public static class Parent {
@Id
private BigDecimal id;

private String name;

public Parent() {
}

public Parent(BigDecimal id, String name) {
this.id = id;
this.name = name;
}

@OneToMany(mappedBy = "id.parent")
private List<FirstChild> children = new ArrayList<>();

void addChild(FirstChild firstChild) {
children.add( firstChild );
}
}

@Entity(name = "FirstChild")
public static class FirstChild {
@EmbeddedId
private FirstChildId id;

@Column(name = "FIRST_CHILD_CODE")
private BigDecimal code;

@OneToMany(mappedBy = "id.firstChild")
private List<SecondChild> children = new ArrayList<>();

public FirstChildId getId() {
return id;
}

public FirstChild() {
}

public FirstChild(Parent parent, BigDecimal bigDecimalNum, BigDecimal code) {
this.id = new FirstChildId( bigDecimalNum, parent );
parent.addChild( this );
this.code = code;
}

public List<SecondChild> getChildren() {
return children;
}

void addChild(SecondChild child) {
children.add( child );
}
}

@Embeddable
public static class FirstChildId {
private BigDecimal bigDecimalNum;

@ManyToOne()
private Parent parent;

public FirstChildId() {
}

public FirstChildId(BigDecimal bigDecimalNum, Parent parent) {
this.bigDecimalNum = bigDecimalNum;
this.parent = parent;
}
}

@Entity(name = "SecondChild")
public static class SecondChild {
@EmbeddedId
private SecondChildId id;

private String name;

public SecondChild() {
}

public SecondChild(FirstChild firstChild, BigDecimal bigDecimalNum, String name) {
this.id = new SecondChildId( bigDecimalNum, firstChild );
this.name = name;
}
}

@Embeddable
public static class SecondChildId {
private BigDecimal bigDecimalNum;

@ManyToOne
@JoinColumn(name = "FIRST_CHILD_CODE", referencedColumnName = "FIRST_CHILD_CODE")
private FirstChild firstChild;

public SecondChildId() {
}

public SecondChildId(BigDecimal bigDecimalNum, FirstChild firstChild) {
this.bigDecimalNum = bigDecimalNum;
this.firstChild = firstChild;
}
}

}
Loading