HHH-15509 correctly support @NotFound @ManyToMany

1. error if a non-@ManyToMany collection is @NotFound
2. disable constraint generation for @NotFound @ManyToMany
3. allow lazy fetching for @NotFound @ManyToMany
4. rework a completely bogus test so it makes sense
This commit is contained in:
Gavin King 2022-09-26 13:11:27 +02:00
parent 5b907ae8b1
commit 7d34f86a95
4 changed files with 91 additions and 87 deletions

View File

@ -1771,8 +1771,8 @@ public final class AnnotationBinder {
final JavaType<?> domainJtd = jtdRegistry.resolveDescriptor( domainJavaClass );
final JavaType<?> relationalJtd = jtdRegistry.resolveDescriptor( relationalJavaClass );
@SuppressWarnings({ "unchecked", "rawtypes" })
final JpaAttributeConverterImpl valueConverter = new JpaAttributeConverterImpl(
@SuppressWarnings({"rawtypes", "unchecked"})
final JpaAttributeConverterImpl<?,?> valueConverter = new JpaAttributeConverterImpl(
bean,
converterJtd,
domainJtd,
@ -2474,10 +2474,7 @@ public final class AnnotationBinder {
&& isToManyAssociationWithinEmbeddableCollection(propertyHolder) ) {
throw new AnnotationException(
"@OneToMany, @ManyToMany or @ElementCollection cannot be used inside an @Embeddable that is also contained within an @ElementCollection: "
+ BinderHelper.getPath(
propertyHolder,
inferredData
)
+ BinderHelper.getPath(propertyHolder, inferredData)
);
}
@ -2485,10 +2482,7 @@ public final class AnnotationBinder {
&& manyToManyAnn != null && !manyToManyAnn.mappedBy().isEmpty() ) {
throw new AnnotationException(
"Explicit @OrderColumn on inverse side of @ManyToMany is illegal: "
+ BinderHelper.getPath(
propertyHolder,
inferredData
)
+ BinderHelper.getPath(propertyHolder, inferredData)
);
}
@ -2519,7 +2513,13 @@ public final class AnnotationBinder {
collectionBinder.setPropertyHolder(propertyHolder);
Cascade hibernateCascade = property.getAnnotation( Cascade.class );
NotFound notFound = property.getAnnotation( NotFound.class );
collectionBinder.setNotFoundAction( notFound == null ? null : notFound.action() );
if ( notFound != null ) {
if ( manyToManyAnn == null ) {
throw new AnnotationException("collection annotated @NotFound is not a @ManyToMany association: "
+ BinderHelper.getPath(propertyHolder, inferredData) );
}
collectionBinder.setNotFoundAction( notFound.action() );
}
collectionBinder.setCollectionType( inferredData.getProperty().getElementClass() );
collectionBinder.setAccessType( inferredData.getDefaultAccess() );
@ -3022,7 +3022,7 @@ public final class AnnotationBinder {
return null;
}
protected static Class<? extends CompositeUserType<?>> resolveTimeZoneStorageCompositeUserType(
static Class<? extends CompositeUserType<?>> resolveTimeZoneStorageCompositeUserType(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {

View File

@ -60,7 +60,6 @@ import org.hibernate.annotations.ListIndexJdbcType;
import org.hibernate.annotations.ListIndexJdbcTypeCode;
import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
@ -971,7 +970,6 @@ public abstract class CollectionBinder {
ManyToMany manyToMany = property.getAnnotation( ManyToMany.class );
ElementCollection elementCollection = property.getAnnotation( ElementCollection.class );
ManyToAny manyToAny = property.getAnnotation( ManyToAny.class );
NotFound notFound = property.getAnnotation( NotFound.class );
FetchType fetchType;
if ( oneToMany != null ) {
@ -991,27 +989,7 @@ public abstract class CollectionBinder {
"Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements"
);
}
if ( notFound != null ) {
collection.setLazy( false );
if ( lazy != null ) {
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
}
if ( fetch != null ) {
if ( fetch.value() != null ) {
collection.setFetchMode( fetch.value().getHibernateFetchMode() );
if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
collection.setSubselectLoadable( true );
collection.getOwner().setSubselectLoadableCollections( true );
}
}
}
else {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
}
else {
if ( lazy != null ) {
collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) );
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
@ -1020,6 +998,7 @@ public abstract class CollectionBinder {
collection.setLazy( fetchType == FetchType.LAZY );
collection.setExtraLazy( false );
}
if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
collection.setFetchMode( FetchMode.JOIN );
@ -1041,7 +1020,6 @@ public abstract class CollectionBinder {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
}
}
private XClass getCollectionType() {
if ( AnnotationBinder.isDefault( targetEntity, buildingContext ) ) {
@ -2254,7 +2232,7 @@ public abstract class CollectionBinder {
* collection element
* Otherwise delegates to the usual algorithm
*/
public static void bindManytoManyInverseFk(
public void bindManytoManyInverseFk(
PersistentClass referencedEntity,
AnnotatedJoinColumn[] columns,
SimpleValue value,
@ -2302,6 +2280,9 @@ public abstract class CollectionBinder {
}
else {
BinderHelper.createSyntheticPropertyReference( columns, referencedEntity, null, value, true, buildingContext );
if ( notFoundAction == NotFoundAction.IGNORE ) {
value.disableForeignKey();
}
TableBinder.bindFk( referencedEntity, null, columns, value, unique, buildingContext );
}
}
@ -2334,12 +2315,6 @@ public abstract class CollectionBinder {
this.notFoundAction = notFoundAction;
}
public void setIgnoreNotFound(boolean ignoreNotFound) {
this.notFoundAction = ignoreNotFound
? NotFoundAction.IGNORE
: null;
}
public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) {
this.mapKeyColumns = mapKeyColumns;
}

View File

@ -6,46 +6,40 @@
*/
package org.hibernate.orm.test.annotations.formula;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import org.hibernate.LazyInitializationException;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import org.hibernate.Hibernate;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.cfg.AnnotationBinder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.logger.LoggerInspectionRule;
import org.hibernate.testing.logger.Triggerable;
import org.hibernate.testing.transaction.TransactionUtil2;
import org.jboss.logging.Logger;
import org.junit.Rule;
import org.junit.Test;
import org.jboss.logging.Logger;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.hibernate.testing.transaction.TransactionUtil2.fromTransaction;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.assertTrue;
@TestForIssue(jiraKey = "HHH-12770")
public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase {
@TestForIssue(jiraKey = {"HHH-12770", "HHH-15545"})
public class ManyToManyNotIgnoreLazyFetchingTest extends BaseEntityManagerFunctionalTestCase {
@Rule
public LoggerInspectionRule logInspection = new LoggerInspectionRule(
@ -80,6 +74,9 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
Stock stock2 = new Stock();
stock2.setId( 2L );
entityManager.persist( stock2 );
entityManager.flush();
entityManager.remove(code);
} );
}
@ -88,8 +85,13 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
assertFalse( triggerable.wasTriggered() );
List<Stock> stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), (session) -> {
return session.createQuery("SELECT s FROM Stock s order by id", Stock.class ).getResultList();
List<Stock> stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), session -> {
List<Stock> list = session.createQuery("select s from Stock s order by id", Stock.class).getResultList();
for (Stock s: list) {
assertFalse( Hibernate.isInitialized( s.getCodes() ) );
Hibernate.initialize( s.getCodes() );
}
return list;
} );
assertThat( stocks ).hasSize( 2 );
@ -97,7 +99,30 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
final Stock firstStock = stocks.get( 0 );
final Stock secondStock = stocks.get( 1 );
assertThat( firstStock.getCodes() ).hasSize( 1 );
assertThat( firstStock.getCodes() ).hasSize( 0 );
assertThat( secondStock.getCodes() ).hasSize( 0 );
}
@Test
public void testEagerLoading() {
assertFalse( triggerable.wasTriggered() );
List<Stock> stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ),
session -> session.createQuery("select s from Stock s left join fetch s.codes order by s.id", Stock.class)
.getResultList()
);
assertThat( stocks ).hasSize( 2 );
for (Stock s: stocks) {
assertTrue( Hibernate.isInitialized( s.getCodes() ) );
}
final Stock firstStock = stocks.get( 0 );
final Stock secondStock = stocks.get( 1 );
assertThat( firstStock.getCodes() ).hasSize( 0 );
assertThat( secondStock.getCodes() ).hasSize( 0 );
}
@ -108,9 +133,14 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
@Column(name = "ID")
private Long id;
@OneToMany
@ManyToMany
@NotFound(action = NotFoundAction.IGNORE)
@JoinColumn(name = "CODE_ID", referencedColumnName = "ID")
@JoinTable(name = "STOCK_BY_CODE",
joinColumns = @JoinColumn(name = "STOCK_ID", referencedColumnName = "ID"),
inverseJoinColumns = {
@JoinColumn(name = "CODE_ID", referencedColumnName = "ID"),
@JoinColumn(name = "CODE_TYPE", referencedColumnName = "TYPE")
})
private List<StockCode> codes = new ArrayList<>();
public Long getId() {

View File

@ -36,8 +36,7 @@ public class OneToManyNotAuditedNullEntity implements Serializable {
private String data;
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@OneToMany(fetch = FetchType.LAZY)
@NotFound(action = NotFoundAction.IGNORE)
@OneToMany(fetch = FetchType.EAGER)
@JoinTable(joinColumns = @JoinColumn(name = "O2MNotAudited_id"))
private List<UnversionedStrTestEntity> references = new ArrayList<UnversionedStrTestEntity>();