Implement partial null key handling and respect lazy flag regardless of fetch style

This commit is contained in:
Christian Beikov 2022-02-11 09:31:25 +01:00
parent 3b6d25425a
commit 0f02279f10
14 changed files with 49 additions and 26 deletions

View File

@ -754,6 +754,7 @@ public class MappingModelCreationHelper {
final FetchTiming timing = FetchOptionsHelper.determineFetchTiming(
style,
collectionDescriptor.getCollectionType(),
collectionDescriptor.isLazy(),
sessionFactory
);
@ -1583,23 +1584,22 @@ public class MappingModelCreationHelper {
final boolean lazy = value.isLazy();
if ( lazy && entityPersister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) {
if ( value.isUnwrapProxy() ) {
fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, sessionFactory );
fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, lazy, sessionFactory );
}
else if ( value instanceof ManyToOne && value.isNullable() && ( (ManyToOne) value ).isIgnoreNotFound() ) {
fetchTiming = FetchTiming.IMMEDIATE;
}
else {
fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, sessionFactory );
fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, lazy, sessionFactory );
}
}
else if ( fetchStyle == FetchStyle.JOIN
|| !lazy
else if ( !lazy
|| value instanceof OneToOne && value.isNullable()
|| value instanceof ManyToOne && value.isNullable() && ( (ManyToOne) value ).isIgnoreNotFound() ) {
fetchTiming = FetchTiming.IMMEDIATE;
}
else {
fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, sessionFactory );
fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, lazy, sessionFactory );
}
final ToOneAttributeMapping attributeMapping = new ToOneAttributeMapping(

View File

@ -126,6 +126,25 @@ public final class FetchOptionsHelper {
}
}
public static FetchTiming determineFetchTiming(
FetchStyle style,
AssociationType type,
boolean lazy,
SessionFactoryImplementor sessionFactory) {
switch ( style ) {
case JOIN: {
return lazy ? FetchTiming.DELAYED : FetchTiming.IMMEDIATE;
}
case BATCH:
case SUBSELECT:
default: {
return isSubsequentSelectDelayed( type, sessionFactory )
? FetchTiming.DELAYED
: FetchTiming.IMMEDIATE;
}
}
}
private static boolean isSubsequentSelectDelayed(AssociationType type, SessionFactoryImplementor sessionFactory) {
if ( type.isAnyType() ) {
// we'd need more context here. this is only kept as part of the property state on the owning entity

View File

@ -254,6 +254,8 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
private void extractRowState(RowProcessingState processingState) {
stateAllNull = true;
final boolean isKey = ForeignKeyDescriptor.PART_NAME.equals( navigablePath.getLocalName() )
|| EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( embedded.getFetchableName() );
for ( int i = 0; i < assemblers.size(); i++ ) {
final DomainResultAssembler<?> assembler = assemblers.get( i );
final Object contributorValue = assembler.assemble(
@ -265,6 +267,11 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
if ( contributorValue != null ) {
stateAllNull = false;
}
else if ( isKey ) {
// If this is a foreign key and there is a null part, the whole thing has to be turned into null
stateAllNull = true;
break;
}
}
applyMapsId( processingState );

View File

@ -91,15 +91,9 @@ public class JoinFormulaManyToOneLazyFetchingTest extends BaseEntityManagerFunct
assertEquals( 2, stocks.size() );
assertEquals( "ABC", stocks.get( 0 ).getCode().getRefNumber() );
try {
stocks.get( 1 ).getCode().getRefNumber();
fail( "Should have thrown EntityNotFoundException" );
}
catch (EntityNotFoundException expected) {
}
// In 5.x, for some reason, we didn't understand that a component is a FK,
// hence a partial null was wrongly handled, but in 6.0 we handle this in a unified way
assertNull( stocks.get( 1 ).getCode() );
} );
}

View File

@ -19,7 +19,7 @@
<class name="Product" table="t_product">
<id name="name"/>
<map name="parts" table="Parts" cascade="all,delete-orphan" fetch="join">
<map name="parts" table="Parts" cascade="all,delete-orphan" fetch="join" lazy="false">
<key column="productName" not-null="true"/>
<composite-map-key class="MapKey">
<key-property name="role" column="role_"/>

View File

@ -29,7 +29,7 @@
<property name="type" column="permissionType"/>
</composite-element>
</list>
<list name="emailAddresses" fetch="join">
<list name="emailAddresses" fetch="join" lazy="false">
<key column="userName"/>
<list-index column="displayOrder" base="1"/>
<composite-element class="Email">

View File

@ -33,7 +33,7 @@
<one-to-many class="LineItem"/>
</set>
<set cascade="all" inverse="false" name="categories" fetch="join" table="PROD_CAT" >
<set cascade="all" inverse="false" name="categories" fetch="join" lazy="false" table="PROD_CAT" >
<key column="PROD_ID"/>
<many-to-many class="Category" column="CAT_ID" fetch="join" >
<filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/>

View File

@ -47,7 +47,7 @@
<many-to-one name="person" column="PERS_ID" class="Person" cascade="all"/>
<many-to-one name="oldPerson" column="OLD_PERS_ID" class="Person" cascade="all" fetch="select"/>
<many-to-one name="veryOldPerson" column="VERY_OLD_PERS_ID" class="Person" cascade="all" fetch="join"/>
<many-to-one name="veryOldPerson" column="VERY_OLD_PERS_ID" class="Person" cascade="all" fetch="join" lazy="false"/>
</class>

View File

@ -20,7 +20,7 @@
<class name="User" table="UC_BSC_USER">
<id name="id"/>
<property name="userName"/>
<list name="emailAddresses" fetch="join" cascade="all, delete-orphan" collection-type="MyListType">
<list name="emailAddresses" fetch="join" lazy="false" cascade="all, delete-orphan" collection-type="MyListType">
<key column="userName"/>
<list-index column="displayOrder" base="1"/>
<one-to-many class="Email"/>

View File

@ -20,7 +20,7 @@
<class name="User" table="UC_BSC_USER">
<id name="id"/>
<property name="userName"/>
<list name="emailAddresses" fetch="join" cascade="all, delete-orphan" collection-type="HeadListType">
<list name="emailAddresses" fetch="join" lazy="false" cascade="all, delete-orphan" collection-type="HeadListType">
<key column="userName"/>
<list-index column="displayOrder" base="1"/>
<one-to-many class="Email"/>

View File

@ -17,7 +17,7 @@
<class name="Entity">
<id name="name" type="string"/>
<list name="values" fetch="join" table="ENT_VAL" collection-type="DefaultableList">
<list name="values" fetch="join" lazy="false" table="ENT_VAL" collection-type="DefaultableList">
<key column="ENT_ID"/>
<list-index column="POS"/>
<element type="string" column="VAL"/>

View File

@ -28,10 +28,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@SessionFactory
public class TypedManyToOneTest {
@Test
@NotImplementedYet(
strict = false,
reason = "fetch=\"join\" in `hbm.xml` mappings for collection (lazy v. eager)"
)
public void testCreateQuery(SessionFactoryScope scope) {
final Customer cust = new Customer();
cust.setCustomerId("abc123");

View File

@ -18,7 +18,7 @@
<class name="Product" table="t_product">
<id name="name"/>
<set name="parts" cascade="all,delete-orphan" fetch="join">
<set name="parts" cascade="all,delete-orphan" fetch="join" lazy="false">
<key column="productName" not-null="true"/>
<one-to-many class="Part"/>
</set>

View File

@ -700,3 +700,10 @@ The SQL generation is now fully handled through the `SqlAstTranslator` which a `
Legacy `hbm.xml` mapping format is considered deprecated and will no longer supported beyond 6.x.
== Association laziness now respected
Prior to Hibernate 6.0, lazy associations that used `fetch="join"` or `@Fetch(FetchMode.JOIN)` were considered eager
when loaded by-id i.e. through `Session#get`/`EntityManager#find`, even though for queries the association was treated as lazy.
Starting with Hibernate 6.0, the laziness of such associations is properly respected, regardless of the fetch mechanism.
Backwards compatibility can be achieved by specifying `lazy="false"` or `@ManyToOne(fetch = EAGER)`/`@OneToOne(fetch = EAGER)`/`@OneToMany(fetch = EAGER)`/`@ManyToMany(fetch = EAGER)`.