HHH-14993 - EAGER non-inverse collection throws StackOverflowError if `max_fetch_depth` not set
This commit is contained in:
parent
92b46bca5b
commit
38cffd0c8f
|
@ -798,12 +798,8 @@ public class LoaderSelectBuilder {
|
||||||
joined = false;
|
joined = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean changeFetchDepth = !( fetchable instanceof BasicValuedModelPart )
|
|
||||||
&& !( fetchable instanceof EmbeddedAttributeMapping )
|
|
||||||
&& !( fetchable instanceof CollectionPart );
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ( changeFetchDepth ) {
|
if ( fetchable.incrementFetchDepth() ) {
|
||||||
fetchDepth++;
|
fetchDepth++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -877,7 +873,7 @@ public class LoaderSelectBuilder {
|
||||||
fetches.add( fetch );
|
fetches.add( fetch );
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if ( changeFetchDepth ) {
|
if ( fetchable.incrementFetchDepth() ) {
|
||||||
fetchDepth--;
|
fetchDepth--;
|
||||||
}
|
}
|
||||||
// Only set the currentBagRole to the previous value for non-join fetches,
|
// Only set the currentBagRole to the previous value for non-join fetches,
|
||||||
|
|
|
@ -408,6 +408,12 @@ public class EntityCollectionPart
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean incrementFetchDepth() {
|
||||||
|
// the collection itself already increments the depth
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ModelPart findSubPart(String name) {
|
public ModelPart findSubPart(String name) {
|
||||||
return findSubPart( name, null );
|
return findSubPart( name, null );
|
||||||
|
|
|
@ -430,6 +430,27 @@ public class PluralAttributeMappingImpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fetch resolveCircularFetch(
|
||||||
|
NavigablePath fetchablePath,
|
||||||
|
FetchParent fetchParent,
|
||||||
|
FetchTiming fetchTiming,
|
||||||
|
DomainResultCreationState creationState) {
|
||||||
|
if ( fetchTiming == FetchTiming.IMMEDIATE ) {
|
||||||
|
final boolean alreadyVisited = creationState.isAssociationKeyVisited( fkDescriptor.getAssociationKey() );
|
||||||
|
if ( alreadyVisited ) {
|
||||||
|
return createSelectEagerCollectionFetch(
|
||||||
|
fetchParent,
|
||||||
|
fetchablePath,
|
||||||
|
creationState,
|
||||||
|
creationState.getSqlAstCreationState()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private Fetch createSelectEagerCollectionFetch(
|
private Fetch createSelectEagerCollectionFetch(
|
||||||
FetchParent fetchParent,
|
FetchParent fetchParent,
|
||||||
NavigablePath fetchablePath,
|
NavigablePath fetchablePath,
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* 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.fetch.depth;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import jakarta.persistence.EntityManagerFactory;
|
||||||
|
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.JiraKey;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||||
|
import org.jboss.shrinkwrap.api.classloader.ShrinkWrapClassLoader;
|
||||||
|
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||||
|
|
||||||
|
import static jakarta.persistence.Persistence.createEntityManagerFactory;
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@JiraKey( "HHH-14993" )
|
||||||
|
public class NoDepthTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithMax() {
|
||||||
|
testIt( true );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoMax() {
|
||||||
|
testIt( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testIt(boolean configureMaxDepth) {
|
||||||
|
try ( final SessionFactoryImplementor sf = buildSessionFactory( configureMaxDepth ) ) {
|
||||||
|
inTransaction( sf, (session) -> {
|
||||||
|
session.createSelectionQuery( "from SysModule" ).getResultList();
|
||||||
|
session.createSelectionQuery( "from SysModule2" ).getResultList();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SessionFactoryImplementor buildSessionFactory(boolean configureMax) {
|
||||||
|
final StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
|
||||||
|
registryBuilder.applySetting( AvailableSettings.URL, "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;" );
|
||||||
|
registryBuilder.applySetting( AvailableSettings.USER, "sa" );
|
||||||
|
registryBuilder.applySetting( AvailableSettings.POOL_SIZE, "5" );
|
||||||
|
registryBuilder.applySetting( AvailableSettings.FORMAT_SQL, "true" );
|
||||||
|
registryBuilder.applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
|
||||||
|
|
||||||
|
if ( configureMax ) {
|
||||||
|
registryBuilder.applySetting( AvailableSettings.MAX_FETCH_DEPTH, "10" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
registryBuilder.applySetting( AvailableSettings.MAX_FETCH_DEPTH, "" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MetadataSources( registryBuilder.build() )
|
||||||
|
.addAnnotatedClasses( SysModule.class, SysModule2.class )
|
||||||
|
.buildMetadata()
|
||||||
|
.buildSessionFactory()
|
||||||
|
.unwrap( SessionFactoryImplementor.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithMaxJpa() {
|
||||||
|
testItJpa( "with-max" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoMaxJpa() {
|
||||||
|
testItJpa( "no-max" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testItJpa(String unitName) {
|
||||||
|
final JavaArchive par = ShrinkWrap.create( JavaArchive.class, unitName + ".par" );
|
||||||
|
par.addClasses( SysModule.class );
|
||||||
|
par.addAsResource( "units/many2many/fetch-depth.xml", "META-INF/persistence.xml" );
|
||||||
|
|
||||||
|
try ( final ShrinkWrapClassLoader classLoader = new ShrinkWrapClassLoader( par ) ) {
|
||||||
|
final Map<String, ?> settings = CollectionHelper.toMap(
|
||||||
|
AvailableSettings.CLASSLOADERS,
|
||||||
|
Arrays.asList( classLoader, getClass().getClassLoader() )
|
||||||
|
);
|
||||||
|
|
||||||
|
final EntityManagerFactory emf = createEntityManagerFactory( unitName, settings );
|
||||||
|
try ( final SessionFactoryImplementor sf = emf.unwrap( SessionFactoryImplementor.class ) ) {
|
||||||
|
// play around with the SF and make sure it is operable
|
||||||
|
inTransaction( sf, (s) -> {
|
||||||
|
s.createSelectionQuery( "from SysModule" ).list();
|
||||||
|
s.createSelectionQuery( "from SysModule2" ).list();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException( "re-throw", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.fetch.depth;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.JoinTable;
|
||||||
|
import jakarta.persistence.ManyToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "sys_mod")
|
||||||
|
public class SysModule {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Column(name = "name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
// @OneToMany( cascade = CascadeType.PERSIST, fetch = FetchType.EAGER )
|
||||||
|
// @JoinColumn( name = "target_mod_fk" )
|
||||||
|
@ManyToMany( targetEntity = SysModule.class, cascade = { CascadeType.PERSIST }, fetch = FetchType.EAGER )
|
||||||
|
@JoinTable(
|
||||||
|
name = "sys_group_mod",
|
||||||
|
joinColumns = @JoinColumn(name = "src_fk", referencedColumnName = "id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "target_fk", referencedColumnName = "id")
|
||||||
|
)
|
||||||
|
private Set<SysModule> targetModules;
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.fetch.depth;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "sys_mod")
|
||||||
|
public class SysModule2 {
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Column(name = "name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@OneToMany( cascade = CascadeType.PERSIST, fetch = FetchType.EAGER )
|
||||||
|
@JoinColumn( name = "target_mod_fk" )
|
||||||
|
private Set<SysModule2> targetModules;
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<persistence version="3.0" xmlns="https://jakarta.ee/xml/ns/persistence"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd">
|
||||||
|
|
||||||
|
<persistence-unit name="no-max" transaction-type="RESOURCE_LOCAL" >
|
||||||
|
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
|
||||||
|
|
||||||
|
<class>org.hibernate.orm.test.mapping.fetch.depth.SysModule</class>
|
||||||
|
<class>org.hibernate.orm.test.mapping.fetch.depth.SysModule2</class>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<property name="hibernate.connection.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;"/>
|
||||||
|
<property name="hibernate.connection.username" value="sa"/>
|
||||||
|
<property name="hibernate.connection.pool_size" value="5"/>
|
||||||
|
<property name="hibernate.show_sql" value="true"/>
|
||||||
|
<property name="hibernate.format_sql" value="true"/>
|
||||||
|
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
|
||||||
|
|
||||||
|
<property name="hibernate.max_fetch_depth" value="" />
|
||||||
|
</properties>
|
||||||
|
</persistence-unit>
|
||||||
|
|
||||||
|
<persistence-unit name="with-max" transaction-type="RESOURCE_LOCAL" >
|
||||||
|
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
|
||||||
|
|
||||||
|
<class>org.hibernate.orm.test.mapping.fetch.depth.SysModule</class>
|
||||||
|
<class>org.hibernate.orm.test.mapping.fetch.depth.SysModule2</class>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<property name="hibernate.connection.url" value="jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;"/>
|
||||||
|
<property name="hibernate.connection.username" value="sa"/>
|
||||||
|
<property name="hibernate.connection.pool_size" value="5"/>
|
||||||
|
<property name="hibernate.format_sql" value="true"/>
|
||||||
|
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
|
||||||
|
|
||||||
|
<property name="hibernate.max_fetch_depth" value="10" />
|
||||||
|
</properties>
|
||||||
|
</persistence-unit>
|
||||||
|
|
||||||
|
</persistence>
|
Loading…
Reference in New Issue