HHH-18166 introduce hibernate.jpa.compliance.cascade
Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
ffab0d8026
commit
e3cf006e76
|
@ -731,6 +731,13 @@ public interface SessionFactoryBuilder {
|
|||
@Deprecated( since = "6.0" )
|
||||
SessionFactoryBuilder enableJpaListCompliance(boolean enabled);
|
||||
|
||||
/**
|
||||
* @see JpaCompliance#isJpaCascadeComplianceEnabled()
|
||||
*
|
||||
* @see org.hibernate.cfg.AvailableSettings#JPA_CASCADE_COMPLIANCE
|
||||
*/
|
||||
SessionFactoryBuilder enableJpaCascadeCompliance(boolean enabled);
|
||||
|
||||
/**
|
||||
* @see JpaCompliance#isJpaClosedComplianceEnabled()
|
||||
*
|
||||
|
|
|
@ -424,6 +424,12 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionFactoryBuilder enableJpaCascadeCompliance(boolean enabled) {
|
||||
this.optionsBuilder.enableJpaCascadeCompliance( enabled );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionFactoryBuilder enableJpaClosedCompliance(boolean enabled) {
|
||||
this.optionsBuilder.enableJpaClosedCompliance( enabled );
|
||||
|
|
|
@ -1565,6 +1565,10 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
|||
mutableJpaCompliance().setListCompliance( enabled );
|
||||
}
|
||||
|
||||
public void enableJpaCascadeCompliance(boolean enabled) {
|
||||
mutableJpaCompliance().setCascadeCompliance( enabled );
|
||||
}
|
||||
|
||||
public void enableJpaClosedCompliance(boolean enabled) {
|
||||
mutableJpaCompliance().setClosedCompliance( enabled );
|
||||
}
|
||||
|
|
|
@ -374,6 +374,12 @@ public abstract class AbstractDelegatingSessionFactoryBuilder<T extends SessionF
|
|||
return getThis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionFactoryBuilder enableJpaCascadeCompliance(boolean enabled) {
|
||||
delegate.enableJpaCascadeCompliance( enabled );
|
||||
return getThis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionFactoryBuilder enableJpaListCompliance(boolean enabled) {
|
||||
delegate.enableJpaListCompliance( enabled );
|
||||
|
|
|
@ -62,6 +62,20 @@ public interface JpaComplianceSettings {
|
|||
*/
|
||||
String JPA_QUERY_COMPLIANCE = "hibernate.jpa.compliance.query";
|
||||
|
||||
/**
|
||||
* Controls whether Hibernate applies cascade
|
||||
* {@link jakarta.persistence.CascadeType#PERSIST} or
|
||||
* {@link org.hibernate.annotations.CascadeType#SAVE_UPDATE}
|
||||
* at flush time. If enabled, Hibernate will cascade the standard
|
||||
* JPA {@code PERSIST} operation. Otherwise, Hibernate will cascade
|
||||
* the legacy {@code SAVE_UPDATE} operation.
|
||||
*
|
||||
* @settingDefault {@link #JPA_COMPLIANCE}
|
||||
*
|
||||
* @since 6.6
|
||||
*/
|
||||
String JPA_CASCADE_COMPLIANCE = "hibernate.jpa.compliance.cascade";
|
||||
|
||||
/**
|
||||
* Controls whether Hibernate should treat what it would usually consider a
|
||||
* {@linkplain org.hibernate.collection.spi.PersistentBag "bag"}, that is, a
|
||||
|
|
|
@ -146,7 +146,7 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi
|
|||
|
||||
LOG.debug( "Processing flush-time cascades" );
|
||||
|
||||
final PersistContext context = getContext();
|
||||
final PersistContext context = getContext( session );
|
||||
//safe from concurrent modification because of how concurrentEntries() is implemented on IdentityMap
|
||||
for ( Map.Entry<Object,EntityEntry> me : persistenceContext.reentrantSafeEntityEntries() ) {
|
||||
// for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) {
|
||||
|
@ -169,19 +169,25 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi
|
|||
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
|
||||
persistenceContext.incrementCascadeLevel();
|
||||
try {
|
||||
Cascade.cascade( getCascadingAction(), CascadePoint.BEFORE_FLUSH, session, persister, object, anything );
|
||||
Cascade.cascade( getCascadingAction(session), CascadePoint.BEFORE_FLUSH, session, persister, object, anything );
|
||||
}
|
||||
finally {
|
||||
persistenceContext.decrementCascadeLevel();
|
||||
}
|
||||
}
|
||||
|
||||
protected PersistContext getContext() {
|
||||
return jpaBootstrap ? PersistContext.create() : null;
|
||||
protected PersistContext getContext(EventSource session) {
|
||||
return jpaBootstrap || isJpaCascadeComplianceEnabled( session ) ? PersistContext.create() : null;
|
||||
}
|
||||
|
||||
protected CascadingAction<PersistContext> getCascadingAction() {
|
||||
return jpaBootstrap ? CascadingActions.PERSIST_ON_FLUSH : CascadingActions.SAVE_UPDATE;
|
||||
protected CascadingAction<PersistContext> getCascadingAction(EventSource session) {
|
||||
return jpaBootstrap || isJpaCascadeComplianceEnabled( session )
|
||||
? CascadingActions.PERSIST_ON_FLUSH
|
||||
: CascadingActions.SAVE_UPDATE;
|
||||
}
|
||||
|
||||
private static boolean isJpaCascadeComplianceEnabled(EventSource session) {
|
||||
return session.getSessionFactory().getSessionFactoryOptions().getJpaCompliance().isJpaCascadeComplianceEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -245,7 +245,7 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
|
|||
mergedIntegrationSettings.putAll( integrationSettings );
|
||||
}
|
||||
|
||||
// Build the boot-strap service registry, which mainly handles class loader interactions
|
||||
// Build the bootstrap service registry, which mainly handles classloader interactions
|
||||
final BootstrapServiceRegistry bsr = buildBootstrapServiceRegistry(
|
||||
mergedIntegrationSettings != null ? mergedIntegrationSettings : integrationSettings,
|
||||
providedClassLoader,
|
||||
|
|
|
@ -21,6 +21,7 @@ public class JpaComplianceImpl implements JpaCompliance {
|
|||
private final boolean closedCompliance;
|
||||
private final boolean cachingCompliance;
|
||||
private final boolean loadByIdCompliance;
|
||||
private final boolean cascadeCompliance;
|
||||
|
||||
public JpaComplianceImpl(
|
||||
boolean listCompliance,
|
||||
|
@ -31,7 +32,8 @@ public class JpaComplianceImpl implements JpaCompliance {
|
|||
boolean transactionCompliance,
|
||||
boolean closedCompliance,
|
||||
boolean cachingCompliance,
|
||||
boolean loadByIdCompliance) {
|
||||
boolean loadByIdCompliance,
|
||||
boolean cascadeCompliance) {
|
||||
this.queryCompliance = queryCompliance;
|
||||
this.transactionCompliance = transactionCompliance;
|
||||
this.listCompliance = listCompliance;
|
||||
|
@ -41,6 +43,7 @@ public class JpaComplianceImpl implements JpaCompliance {
|
|||
this.globalGeneratorNameScopeCompliance = globalGeneratorNameScopeCompliance;
|
||||
this.orderByMappingCompliance = orderByMappingCompliance;
|
||||
this.loadByIdCompliance = loadByIdCompliance;
|
||||
this.cascadeCompliance = cascadeCompliance;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,6 +56,11 @@ public class JpaComplianceImpl implements JpaCompliance {
|
|||
return transactionCompliance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJpaCascadeComplianceEnabled() {
|
||||
return cascadeCompliance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJpaListComplianceEnabled() {
|
||||
return listCompliance;
|
||||
|
@ -98,10 +106,16 @@ public class JpaComplianceImpl implements JpaCompliance {
|
|||
private boolean transactionCompliance;
|
||||
private boolean closedCompliance;
|
||||
private boolean loadByIdCompliance;
|
||||
private boolean cascadeCompliance;
|
||||
|
||||
public JpaComplianceBuilder() {
|
||||
}
|
||||
|
||||
public JpaComplianceBuilder setCascadeCompliance(boolean cascadeCompliance) {
|
||||
this.cascadeCompliance = cascadeCompliance;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JpaComplianceBuilder setListCompliance(boolean listCompliance) {
|
||||
this.listCompliance = listCompliance;
|
||||
return this;
|
||||
|
@ -157,7 +171,8 @@ public class JpaComplianceImpl implements JpaCompliance {
|
|||
transactionCompliance,
|
||||
closedCompliance,
|
||||
cachingCompliance,
|
||||
loadByIdCompliance
|
||||
loadByIdCompliance,
|
||||
cascadeCompliance
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
|
|||
private boolean closedCompliance;
|
||||
private boolean cachingCompliance;
|
||||
private boolean loadByIdCompliance;
|
||||
private boolean cascadeCompliance;
|
||||
|
||||
public MutableJpaComplianceImpl(Map<?,?> configurationSettings) {
|
||||
this(
|
||||
|
@ -38,6 +39,11 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
|
|||
public MutableJpaComplianceImpl(Map<?,?> configurationSettings, boolean jpaByDefault) {
|
||||
final Object legacyQueryCompliance = configurationSettings.get( AvailableSettings.JPAQL_STRICT_COMPLIANCE );
|
||||
|
||||
cascadeCompliance = ConfigurationHelper.getBoolean(
|
||||
AvailableSettings.JPA_CASCADE_COMPLIANCE,
|
||||
configurationSettings,
|
||||
jpaByDefault
|
||||
);
|
||||
//noinspection deprecation
|
||||
listCompliance = ConfigurationHelper.getBoolean(
|
||||
AvailableSettings.JPA_LIST_COMPLIANCE,
|
||||
|
@ -96,6 +102,10 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
|
|||
return transactionCompliance;
|
||||
}
|
||||
|
||||
public boolean isJpaCascadeComplianceEnabled() {
|
||||
return cascadeCompliance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJpaListComplianceEnabled() {
|
||||
return listCompliance;
|
||||
|
@ -139,6 +149,11 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
|
|||
this.listCompliance = listCompliance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCascadeCompliance(boolean cascadeCompliance) {
|
||||
this.cascadeCompliance = cascadeCompliance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrderByMappingCompliance(boolean orderByMappingCompliance) {
|
||||
this.orderByMappingCompliance = orderByMappingCompliance;
|
||||
|
@ -182,6 +197,7 @@ public class MutableJpaComplianceImpl implements MutableJpaCompliance {
|
|||
public JpaCompliance immutableCopy() {
|
||||
JpaComplianceImpl.JpaComplianceBuilder builder = new JpaComplianceImpl.JpaComplianceBuilder();
|
||||
builder = builder.setListCompliance( listCompliance )
|
||||
.setCascadeCompliance( cascadeCompliance )
|
||||
.setProxyCompliance( proxyCompliance )
|
||||
.setOrderByMappingCompliance( orderByMappingCompliance )
|
||||
.setGlobalGeneratorNameCompliance( generatorNameScopeCompliance )
|
||||
|
|
|
@ -80,6 +80,17 @@ public interface JpaCompliance {
|
|||
*/
|
||||
boolean isJpaClosedComplianceEnabled();
|
||||
|
||||
/**
|
||||
* JPA specifies that a {@link jakarta.persistence.CascadeType#PERSIST}
|
||||
* operation should occur at flush time. The legacy behavior of Hibernate
|
||||
* was a {@link org.hibernate.annotations.CascadeType#SAVE_UPDATE}.
|
||||
*
|
||||
* @return {@code true} indicates to behave in the spec-defined way
|
||||
*
|
||||
* @see org.hibernate.cfg.AvailableSettings#JPA_CASCADE_COMPLIANCE
|
||||
*/
|
||||
boolean isJpaCascadeComplianceEnabled();
|
||||
|
||||
/**
|
||||
* JPA spec says that an {@link jakarta.persistence.EntityNotFoundException}
|
||||
* should be thrown when accessing an entity proxy which does not have
|
||||
|
|
|
@ -10,6 +10,8 @@ package org.hibernate.jpa.spi;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface MutableJpaCompliance extends JpaCompliance {
|
||||
void setCascadeCompliance(boolean cascadeCompliance);
|
||||
|
||||
void setListCompliance(boolean listCompliance);
|
||||
|
||||
void setOrderByMappingCompliance(boolean orderByCompliance);
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package org.hibernate.orm.test.annotations.cascade.persist;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Jpa;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Jpa(annotatedClasses =
|
||||
{CascadePersistJpaTest.Parent.class,
|
||||
CascadePersistJpaTest.Child.class},
|
||||
jpaComplianceEnabled = true)
|
||||
public class CascadePersistJpaTest {
|
||||
@Test void test(EntityManagerFactoryScope scope) {
|
||||
Parent p = new Parent();
|
||||
scope.inTransaction(s -> s.persist(p));
|
||||
scope.inTransaction(s -> {
|
||||
Parent parent = s.find(Parent.class, p.id);
|
||||
Child child = new Child();
|
||||
child.parent = parent;
|
||||
parent.children.add(child);
|
||||
});
|
||||
scope.inTransaction(s -> {
|
||||
Parent parent = s.find(Parent.class, p.id);
|
||||
assertEquals(1, parent.children.size());
|
||||
Child child = parent.children.iterator().next();
|
||||
s.remove(child);
|
||||
parent.children.remove(child);
|
||||
});
|
||||
}
|
||||
|
||||
@Entity
|
||||
static class Parent {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
@OneToMany(cascade = CascadeType.PERSIST,
|
||||
mappedBy = "parent")
|
||||
Set<Child> children = new HashSet<>();
|
||||
}
|
||||
@Entity
|
||||
static class Child {
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Basic(optional = false)
|
||||
String name = "child";
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
Parent parent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package org.hibernate.orm.test.annotations.cascade.persist;
|
||||
|
||||
import jakarta.persistence.Basic;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import org.hibernate.cfg.JpaComplianceSettings;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@SessionFactory
|
||||
@DomainModel(annotatedClasses =
|
||||
{CascadePersistSFTest.Parent.class,
|
||||
CascadePersistSFTest.Child.class})
|
||||
@ServiceRegistry(settings = @Setting(name=JpaComplianceSettings.JPA_CASCADE_COMPLIANCE,value="true"))
|
||||
public class CascadePersistSFTest {
|
||||
@Test void test(SessionFactoryScope scope) {
|
||||
Parent p = new Parent();
|
||||
scope.inTransaction(s -> s.persist(p));
|
||||
scope.inTransaction(s -> {
|
||||
Parent parent = s.find(Parent.class, p.id);
|
||||
Child child = new Child();
|
||||
child.parent = parent;
|
||||
parent.children.add(child);
|
||||
});
|
||||
scope.inTransaction(s -> {
|
||||
Parent parent = s.find(Parent.class, p.id);
|
||||
assertEquals(1, parent.children.size());
|
||||
Child child = parent.children.iterator().next();
|
||||
s.remove(child);
|
||||
parent.children.remove(child);
|
||||
});
|
||||
}
|
||||
|
||||
@Entity
|
||||
static class Parent {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
Long id;
|
||||
|
||||
@OneToMany(cascade = CascadeType.PERSIST,
|
||||
mappedBy = "parent")
|
||||
Set<Child> children = new HashSet<>();
|
||||
}
|
||||
@Entity
|
||||
static class Child {
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Basic(optional = false)
|
||||
String name = "child";
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
Parent parent;
|
||||
}
|
||||
}
|
|
@ -24,6 +24,11 @@ public class JpaComplianceStub implements JpaCompliance {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJpaCascadeComplianceEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJpaListComplianceEnabled() {
|
||||
return false;
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.hibernate.event.internal.DefaultFlushEventListener;
|
|||
import org.hibernate.event.internal.DefaultPersistEventListener;
|
||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||
import org.hibernate.event.spi.AutoFlushEventListener;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.event.spi.EventType;
|
||||
import org.hibernate.event.spi.FlushEntityEventListener;
|
||||
import org.hibernate.event.spi.FlushEventListener;
|
||||
|
@ -142,12 +143,12 @@ public abstract class AbstractJPATest extends BaseSessionFactoryFunctionalTest {
|
|||
public static final AutoFlushEventListener INSTANCE = new JPAAutoFlushEventListener();
|
||||
|
||||
@Override
|
||||
protected CascadingAction<PersistContext> getCascadingAction() {
|
||||
protected CascadingAction<PersistContext> getCascadingAction(EventSource session) {
|
||||
return CascadingActions.PERSIST_ON_FLUSH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PersistContext getContext() {
|
||||
protected PersistContext getContext(EventSource session) {
|
||||
return PersistContext.create();
|
||||
}
|
||||
}
|
||||
|
@ -157,12 +158,12 @@ public abstract class AbstractJPATest extends BaseSessionFactoryFunctionalTest {
|
|||
public static final FlushEventListener INSTANCE = new JPAFlushEventListener();
|
||||
|
||||
@Override
|
||||
protected CascadingAction<PersistContext> getCascadingAction() {
|
||||
protected CascadingAction<PersistContext> getCascadingAction(EventSource session) {
|
||||
return CascadingActions.PERSIST_ON_FLUSH;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PersistContext getContext() {
|
||||
protected PersistContext getContext(EventSource session) {
|
||||
return PersistContext.create();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue