Fix extra query executed for Embedded fk when embeddable has a lazy ToOne association

This commit is contained in:
Andrea Boriero 2020-04-14 17:49:03 +01:00 committed by Steve Ebersole
parent 99778fd9a1
commit 8d026b05d2
7 changed files with 771 additions and 18 deletions

View File

@ -12,6 +12,7 @@ import java.util.function.Consumer;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.ManagedEntity;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.mapping.PersistentClass;
@ -26,6 +27,7 @@ import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
@ -88,6 +90,9 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
if ( entity instanceof HibernateProxy ) {
return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier();
}
return propertyAccess.getGetter().get( entity );
}

View File

@ -29,6 +29,7 @@ import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.sql.ast.Clause;
@ -113,6 +114,9 @@ public class EmbeddedIdentifierMappingImpl implements CompositeIdentifierMapping
@Override
public Object getIdentifier(Object entity, SharedSessionContractImplementor session) {
if ( entity instanceof HibernateProxy ) {
return ( (HibernateProxy) entity ).getHibernateLazyInitializer().getIdentifier();
}
return propertyAccess.getGetter().get( entity );
}

View File

@ -318,9 +318,6 @@ public class SqmUtil {
// assume we have (or can create) a mapping for the parameter's Java type
BasicType basicType = typeConfiguration.standardBasicTypeForJavaType( parameter.getParameterType() );
if ( basicType == null ) {
return StandardBasicTypes.SERIALIZABLE;
}
return basicType;
}
}

View File

@ -31,6 +31,7 @@ import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchDelayedImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
/**
@ -74,15 +75,28 @@ public class EmbeddableForeignKeyResultImpl<T> extends AbstractFetchParent
null,
associatedEntityMappingType.getIdentifierMapping().getJavaTypeDescriptor()
);
Fetch fetch = new EntityFetchSelectImpl(
this,
singularAssociationAttributeMapping,
null,
false,
navigablePath.append( fetchable.getFetchableName() ),
domainResult,
creationState
);
Fetch fetch;
if ( singularAssociationAttributeMapping.getMappedFetchStrategy().getTiming() == FetchTiming.DELAYED ) {
fetch = new EntityFetchDelayedImpl(
this,
singularAssociationAttributeMapping,
null,
false,
navigablePath.append( fetchable.getFetchableName() ),
domainResult
);
}
else {
fetch = new EntityFetchSelectImpl(
this,
singularAssociationAttributeMapping,
null,
false,
navigablePath.append( fetchable.getFetchableName() ),
domainResult,
creationState
);
}
fetches.add( fetch );
}
else {

View File

@ -0,0 +1,339 @@
/*
* 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.compositefk;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.Hibernate;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
/**
* @author Andrea Boriero
*/
@DomainModel(
annotatedClasses = {
LazyManyToOneEmbeddedIdWithToOneFKTest.System.class,
LazyManyToOneEmbeddedIdWithToOneFKTest.SystemUser.class,
LazyManyToOneEmbeddedIdWithToOneFKTest.Subsystem.class
}
)
@SessionFactory(statementInspectorClass = SQLStatementInspector.class)
public class LazyManyToOneEmbeddedIdWithToOneFKTest {
@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Subsystem subsystem = new Subsystem( 2, "sub1" );
PK userKey = new PK( subsystem, "Fab" );
SystemUser user = new SystemUser( userKey, "Fab" );
System system = new System( 1, "sub1" );
system.setUser( user );
session.save( subsystem );
session.save( user );
session.save( system );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete from System" ).executeUpdate();
session.createQuery( "delete from SystemUser" ).executeUpdate();
session.createQuery( "delete from Subsystem" ).executeUpdate();
}
);
}
@Test
public void testGet(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
System system = session.get( System.class, 1 );
assertThat( system, is( notNullValue() ) );
statementInspector.assertExecutedCount( 2 );
assertThat( system.getId(), is( 1 ) );
assertFalse( Hibernate.isInitialized( system.getUser() ) );
PK pk = system.getUser().getPk();
assertTrue( Hibernate.isInitialized( pk.subsystem ) );
assertThat( pk.username, is( "Fab" ) );
assertThat( pk.subsystem.id, is( 2 ) );
assertThat( pk.subsystem.getDescription(), is( "sub1" ) );
SystemUser user = system.getUser();
assertThat( user, is( notNullValue() ) );
statementInspector.assertExecutedCount( 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 0 );
statementInspector.clear();
assertThat( user.getName(), is( "Fab" ) );
assertTrue( Hibernate.isInitialized( system.getUser() ) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
}
);
}
@Test
public void testHql(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
System system = (System) session.createQuery( "from System e where e.id = :id" )
.setParameter( "id", 1 ).uniqueResult();
assertThat( system, is( notNullValue() ) );
statementInspector.assertExecutedCount( 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 0 );
assertFalse( Hibernate.isInitialized( system.getUser() ) );
final PK pk = system.getUser().getPk();
assertTrue( Hibernate.isInitialized( pk.subsystem ) );
assertThat( pk.username, is( "Fab" ) );
assertThat( pk.subsystem.id, is( 2 ) );
assertThat( pk.subsystem.getDescription(), is( "sub1" ) );
SystemUser user = system.getUser();
assertThat( user, is( notNullValue() ) );
statementInspector.assertExecutedCount( 2 );
statementInspector.clear();
assertThat( user.getName(), is( "Fab" ) );
assertTrue( Hibernate.isInitialized( system.getUser() ) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
}
);
}
@Test
public void testHqlJoin(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
System system = session.createQuery( "from System e join e.user where e.id = :id", System.class )
.setParameter( "id", 1 ).uniqueResult();
statementInspector.assertExecutedCount( 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 0 );
assertFalse( Hibernate.isInitialized( system.getUser() ) );
assertThat( system, is( notNullValue() ) );
SystemUser user = system.getUser();
assertThat( user, is( notNullValue() ) );
}
);
}
@Test
public void testHqlJoinFetch(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
System system = session.createQuery(
"from System e join fetch e.user where e.id = :id",
System.class
)
.setParameter( "id", 1 ).uniqueResult();
statementInspector.assertExecutedCount( 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 0 );
assertTrue( Hibernate.isInitialized( system.getUser() ) );
assertThat( system, is( notNullValue() ) );
SystemUser user = system.getUser();
assertThat( user, is( notNullValue() ) );
}
);
}
@Entity(name = "System")
public static class System {
@Id
private Integer id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
SystemUser user;
public System() {
}
public System(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SystemUser getUser() {
return user;
}
public void setUser(SystemUser user) {
this.user = user;
}
}
@Entity(name = "SystemUser")
public static class SystemUser {
@EmbeddedId
private PK pk;
private String name;
public SystemUser() {
}
public SystemUser(PK pk, String name) {
this.pk = pk;
this.name = name;
}
public PK getPk() {
return pk;
}
public void setPk(PK pk) {
this.pk = pk;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Embeddable
public static class PK implements Serializable {
@ManyToOne
private Subsystem subsystem;
private String username;
public PK(Subsystem subsystem, String username) {
this.subsystem = subsystem;
this.username = username;
}
private PK() {
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
PK pk = (PK) o;
return Objects.equals( subsystem, pk.subsystem ) &&
Objects.equals( username, pk.username );
}
@Override
public int hashCode() {
return Objects.hash( subsystem, username );
}
}
@Entity(name = "Subsystem")
public static class Subsystem {
@Id
private Integer id;
private String description;
public Subsystem() {
}
public Subsystem(Integer id, String description) {
this.id = id;
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
// public Integer getId() {
// return id;
// }
}
}

View File

@ -0,0 +1,390 @@
/*
* 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.compositefk;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import org.hibernate.Hibernate;
import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel;
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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Andrea Boriero
*/
@DomainModel(
annotatedClasses = {
ManyToOneEmbeddedIdWithLazyToOneFKTest.System.class,
ManyToOneEmbeddedIdWithLazyToOneFKTest.SystemUser.class,
ManyToOneEmbeddedIdWithLazyToOneFKTest.Subsystem.class
}
)
@SessionFactory(statementInspectorClass = SQLStatementInspector.class)
public class ManyToOneEmbeddedIdWithLazyToOneFKTest {
@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Subsystem subsystem = new Subsystem( 2, "sub1" );
PK userKey = new PK( subsystem, "Fab" );
SystemUser user = new SystemUser( userKey, "Fab" );
System system = new System( 1, "sub1" );
system.setUser( user );
session.save( subsystem );
session.save( user );
session.save( system );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createQuery( "delete from System" ).executeUpdate();
session.createQuery( "delete from SystemUser" ).executeUpdate();
session.createQuery( "delete from Subsystem" ).executeUpdate();
}
);
}
@Test
public void testGet(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
System system = session.get( System.class, 1 );
assertThat( system, is( notNullValue() ) );
assertThat( system.getId(), is( 1 ) );
assertTrue( Hibernate.isInitialized( system.getUser() ) );
PK pk = system.getUser().getPk();
assertFalse( Hibernate.isInitialized( pk.subsystem ) );
SystemUser user = system.getUser();
assertThat( user, is( notNullValue() ) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
statementInspector.clear();
assertThat( pk.username, is( "Fab" ) );
Subsystem subsystem = pk.getSubsystem();
assertThat( subsystem.getId(), is( 2 ) );
assertThat( subsystem.getDescription(), is( "sub1" ) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
}
);
}
@Test
public void testHql(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
/*
select
s1_0.id,
s1_0.name,
s1_0.user_subsystem_id,
s1_0.user_username
from
System as s1_0
where
s1_0.id = ?
select
s1_0.subsystem_id,
s1_0.username,
s1_0.name
from
SystemUser as s1_0
where
(
s1_0.subsystem_id, s1_0.username
) in (
(
?, ?
)
)
*/
scope.inTransaction(
session -> {
System system = (System) session.createQuery( "from System e where e.id = :id" )
.setParameter( "id", 1 ).uniqueResult();
assertThat( system, is( notNullValue() ) );
statementInspector.assertExecutedCount( 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 0 );
assertTrue( Hibernate.isInitialized( system.getUser() ) );
final PK pk = system.getUser().getPk();
assertFalse( Hibernate.isInitialized( pk.subsystem ) );
statementInspector.clear();
assertThat( pk.username, is( "Fab" ) );
Subsystem subsystem = pk.getSubsystem();
assertThat( subsystem.getId(), is( 2 ) );
assertThat( subsystem.getDescription(), is( "sub1" ) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
}
);
}
@Test
public void testHqlJoin(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
System system = session.createQuery( "from System e join e.user where e.id = :id", System.class )
.setParameter( "id", 1 ).uniqueResult();
statementInspector.assertExecutedCount( 2 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 0 );
assertThat( system, is( notNullValue() ) );
SystemUser user = system.getUser();
assertThat( user, is( notNullValue() ) );
assertTrue( Hibernate.isInitialized( system.getUser() ) );
assertFalse( Hibernate.isInitialized( system.getUser().getPk().subsystem ) );
statementInspector.clear();
PK pk = system.getUser().getPk();
assertThat( pk.username, is( "Fab" ) );
Subsystem subsystem = pk.getSubsystem();
assertThat( subsystem.getId(), is( 2 ) );
assertThat( subsystem.getDescription(), is( "sub1" ) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
}
);
}
@Test
public void testHqlJoinFetch(SessionFactoryScope scope) {
SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector();
statementInspector.clear();
scope.inTransaction(
session -> {
System system = session.createQuery(
"from System e join fetch e.user where e.id = :id",
System.class
).setParameter( "id", 1 ).uniqueResult();
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
assertThat( system, is( notNullValue() ) );
SystemUser user = system.getUser();
assertThat( user, is( notNullValue() ) );
statementInspector.clear();
PK pk = system.getUser().getPk();
assertThat( pk.username, is( "Fab" ) );
Subsystem subsystem = pk.getSubsystem();
assertThat( subsystem.getId(), is( 2 ) );
assertThat( subsystem.getDescription(), is( "sub1" ) );
statementInspector.assertExecutedCount( 1 );
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 0 );
}
);
}
@Entity(name = "System")
public static class System {
@Id
private Integer id;
private String name;
@ManyToOne
SystemUser user;
public System() {
}
public System(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public SystemUser getUser() {
return user;
}
public void setUser(SystemUser user) {
this.user = user;
}
}
@Entity(name = "SystemUser")
public static class SystemUser {
@EmbeddedId
private PK pk;
private String name;
public SystemUser() {
}
public SystemUser(PK pk, String name) {
this.pk = pk;
this.name = name;
}
public PK getPk() {
return pk;
}
public void setPk(PK pk) {
this.pk = pk;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Embeddable
public static class PK implements Serializable {
@ManyToOne(fetch = FetchType.LAZY)
private Subsystem subsystem;
private String username;
public PK(Subsystem subsystem, String username) {
this.subsystem = subsystem;
this.username = username;
}
private PK() {
}
public Subsystem getSubsystem() {
return subsystem;
}
public void setSubsystem(Subsystem subsystem) {
this.subsystem = subsystem;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
PK pk = (PK) o;
return Objects.equals( subsystem, pk.subsystem ) &&
Objects.equals( username, pk.username );
}
@Override
public int hashCode() {
return Objects.hash( subsystem, username );
}
}
@Entity(name = "Subsystem")
public static class Subsystem {
@Id
private Integer id;
private String description;
public Subsystem() {
}
public Subsystem(Integer id, String description) {
this.id = id;
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getId() {
return id;
}
}
}

View File

@ -124,14 +124,18 @@ public class ManyToOneEmbeddedIdWithToOneFKTest {
s2_0.id=?
select
manytoonee0_.subsystem_id as subsyste3_2_0_,
manytoonee0_.username as username1_2_0_,
manytoonee0_.name as name2_2_0_
s1_0.subsystem_id,
s1_0.username
from
SystemUser manytoonee0_
SystemUser as s1_0
where
manytoonee0_.subsystem_id=?
and manytoonee0_.username=?
(
s1_0.subsystem_id, s1_0.username
) in (
(
?, ?
)
)
*/
scope.inTransaction(
session -> {