Implement partial null key handling and respect lazy flag regardless of fetch style
This commit is contained in:
parent
3b6d25425a
commit
0f02279f10
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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() );
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
@ -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_"/>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)`.
|
Loading…
Reference in New Issue