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-17838 @OneToOne relationship + @Embeddable keys + FetchType.LAZY fail in most recent version #9134

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
Original file line number Diff line number Diff line change
Expand Up @@ -1553,7 +1553,19 @@ public static class EntityB {

having the left join we don't want to add an extra implicit join that will be translated into an SQL inner join (see HHH-15342)
*/
if ( fetchTiming == FetchTiming.IMMEDIATE && selected ) {

final ForeignKeyDescriptor.Nature resolvingKeySideOfForeignKey = creationState.getCurrentlyResolvingForeignKeyPart();
final ForeignKeyDescriptor.Nature side;
if ( resolvingKeySideOfForeignKey == ForeignKeyDescriptor.Nature.KEY && this.sideNature == ForeignKeyDescriptor.Nature.TARGET ) {
// If we are currently resolving the key part of a foreign key we do not want to add joins.
// So if the lhs of this association is the target of the FK, we have to use the KEY part to avoid a join
side = ForeignKeyDescriptor.Nature.KEY;
}
else {
side = this.sideNature;
}

if ( ( fetchTiming == FetchTiming.IMMEDIATE && selected ) || needsJoinFetch( side ) ) {
final TableGroup tableGroup = determineTableGroupForFetch(
fetchablePath,
fetchParent,
Expand Down Expand Up @@ -1631,16 +1643,6 @@ else if ( hasNotFoundAction()

*/

final ForeignKeyDescriptor.Nature resolvingKeySideOfForeignKey = creationState.getCurrentlyResolvingForeignKeyPart();
final ForeignKeyDescriptor.Nature side;
if ( resolvingKeySideOfForeignKey == ForeignKeyDescriptor.Nature.KEY && this.sideNature == ForeignKeyDescriptor.Nature.TARGET ) {
// If we are currently resolving the key part of a foreign key we do not want to add joins.
// So if the lhs of this association is the target of the FK, we have to use the KEY part to avoid a join
side = ForeignKeyDescriptor.Nature.KEY;
}
else {
side = this.sideNature;
}
final DomainResult<?> keyResult;
if ( side == ForeignKeyDescriptor.Nature.KEY ) {
final TableGroup tableGroup = sideNature == ForeignKeyDescriptor.Nature.KEY
Expand Down Expand Up @@ -1692,6 +1694,22 @@ else if ( hasNotFoundAction()
);
}

private boolean needsJoinFetch(ForeignKeyDescriptor.Nature side) {
if ( side == ForeignKeyDescriptor.Nature.TARGET ) {
// The target model part doesn't correspond to the identifier of the target entity mapping
// so we must eagerly fetch with a join (subselect would still cause problems).
final EntityIdentifierMapping identifier = entityMappingType.getIdentifierMapping();
final ValuedModelPart targetPart = foreignKeyDescriptor.getTargetPart();
if ( identifier != targetPart ) {
// If the identifier and the target part of the same class, we can preserve laziness as deferred loading will still work
return identifier.getExpressibleJavaType().getJavaTypeClass() != targetPart.getExpressibleJavaType()
.getJavaTypeClass();
}
}

return false;
}

private boolean isAffectedByEnabledFilters(DomainResultCreationState creationState) {
final LoadQueryInfluencers loadQueryInfluencers = creationState.getSqlAstCreationState()
.getLoadQueryInfluencers();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.onetoone.embeddedid;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.OneToOne;
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
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.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

@DomainModel(
annotatedClasses = {
OneToOneJoinColumnsEmbeddedIdTest.EntityA.class,
OneToOneJoinColumnsEmbeddedIdTest.EntityB.class,
}
)
@SessionFactory
@BytecodeEnhanced(runNotEnhancedAsWell = true)
@JiraKey("HHH-17838")
public class OneToOneJoinColumnsEmbeddedIdTest {

@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
EntityAKey entityAKey = new EntityAKey( 1, "1" );
EntityA entityA = new EntityA( entityAKey, "te1" );

EntityBKey entityBKey = new EntityBKey( 1, "1" );
EntityB entityB = new EntityB( entityBKey, entityA );

session.persist( entityA );
session.persist( entityB );
}
);
}

@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
}

@Test
public void testFind(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
EntityAKey entityAKey = new EntityAKey( 1, "1" );
EntityA entityA = session.find( EntityA.class, entityAKey );
assertThat( entityA ).isNotNull();

EntityB entityB = entityA.getEntityB();
assertThat( entityB ).isNotNull();

EntityBKey key = entityB.getEntityBKey();
assertThat( key.id1 ).isEqualTo( 1 );
assertThat( key.id2 ).isEqualTo( "1" );
}
);
}

@Test
public void testFind2(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
EntityBKey entityBKey = new EntityBKey( 1, "1" );
EntityB entityB = session.find( EntityB.class, entityBKey );
assertThat( entityB ).isNotNull();

EntityA entityA = entityB.getEntityA();
assertThat( entityA ).isNotNull();

EntityAKey entityAKey = entityA.getEntityAKey();
assertThat( entityAKey.id1 ).isEqualTo( 1 );
assertThat( entityAKey.id2 ).isEqualTo( "1" );

assertThat( entityA.getName() ).isEqualTo( "te1" );
}
);
}

@Entity(name = "EntityA")
public static class EntityA {

@EmbeddedId
private EntityAKey entityAKey;

private String name;

@OneToOne(mappedBy = "entityA", fetch = FetchType.LAZY)
private EntityB entityB;

public EntityA() {
}

public EntityA(EntityAKey key, String name) {
this.entityAKey = key;
this.name = name;
}

public EntityAKey getEntityAKey() {
return entityAKey;
}

public String getName() {
return name;
}

public EntityB getEntityB() {
return entityB;
}
}

@Embeddable
public static class EntityAKey {

@Column(name = "id1")
private Integer id1;

@Column(name = "id2")
private String id2;

public EntityAKey() {
}

public EntityAKey(Integer id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
}

@Entity(name = "EntityB")
public static class EntityB {
@EmbeddedId
private EntityBKey entityBKey;

@OneToOne(optional = false, fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "id1", referencedColumnName = "id1", nullable = false,
insertable = false, updatable = false),
@JoinColumn(name = "id2", referencedColumnName = "id2", nullable = false,
insertable = false, updatable = false)
})
private EntityA entityA;

public EntityB() {
}

public EntityB(EntityBKey key, EntityA testEntity) {
this.entityBKey = key;
this.entityA = testEntity;
testEntity.entityB = this;
}

public EntityBKey getEntityBKey() {
return entityBKey;
}

public EntityA getEntityA() {
return entityA;
}
}

@Embeddable
public static class EntityBKey {

@Column(name = "id1")
private Integer id1;

@Column(name = "id2")
private String id2;

public EntityBKey() {
}

public EntityBKey(Integer documentType, String no) {
this.id1 = documentType;
this.id2 = no;
}
}
}
Loading