HHH-11188 - SessionImpl.refresh() throws IllegalArgumentException, 'Entity not managed' for detached instances when JPA bootstrapped
This commit is contained in:
parent
957ec3fa79
commit
bf2bc323f6
|
@ -772,5 +772,7 @@ false:: does not allow
|
|||
|
||||
|`hibernate.collection_join_subquery`| `true` (default value) or `false` | Setting which indicates whether or not the new JOINS over collection tables should be rewritten to subqueries.
|
||||
|
||||
|`hibernate.allow_refresh_detached_entity`| `true` (default value when using Hibernate native bootstrapping) or `false` (default value when using JPA bootstrapping) | Setting that allows to call `javax.persistence.EntityManager#refresh()` or `org.hibernate.Session#refresh()` on a detached instance even when the `org.hibernate.Session` is obtained from a JPA `javax.persistence.EntityManager`.
|
||||
|
||||
|=====================================================================================================================================================================================================================================================
|
||||
|
||||
|
|
|
@ -285,6 +285,21 @@ However, please note that Hibernate has the capability to handle this automatica
|
|||
See the discussion of non-identifier <<chapters/domain/basic_types.adoc#mapping-generated,generated attributes>>.
|
||||
====
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
Traditionally, Hibernate has been allowing detached entities to be refreshed.
|
||||
Unfortunately, JPA prohibits this practice and specifies that an `IllegalArgumentException` should be thrown instead.
|
||||
|
||||
For this reason, when bootstrapping the Hibernate `SessionFactory` using the native API, the legacy detached entity refresh behavior is going to be preserved.
|
||||
On the other hand, when bootstrapping Hibernate through JPA `EntityManagerFactory` building process, detached entities are not allowed to be refreshed by default.
|
||||
|
||||
However, this default behavior can be overwritten through the `hibernate.allow_refresh_detached_entity` configuration property.
|
||||
If this property is explicitly set to `true`, then you can refresh detached entities even when using the JPA bootstraps mechanism, therefore bypassing the JPA specification restriction.
|
||||
|
||||
For more about the `hibernate.allow_refresh_detached_entity` configuration property,
|
||||
check out the <<appendices/Configurations.adoc#misc,Configurations>> section as well.
|
||||
====
|
||||
|
||||
[[pc-detach]]
|
||||
=== Working with detached data
|
||||
|
||||
|
|
|
@ -450,6 +450,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
|
|||
this.options.jpaBootstrap = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableRefreshDetachedEntity() {
|
||||
this.options.allowRefreshDetachedEntity = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableJtaTransactionAccess() {
|
||||
this.options.jtaTransactionAccessEnabled = false;
|
||||
|
@ -482,6 +487,7 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
|
|||
private boolean jtaTransactionAccessEnabled;
|
||||
private boolean allowOutOfTransactionUpdateOperations;
|
||||
private boolean releaseResourcesOnCloseEnabled;
|
||||
private boolean allowRefreshDetachedEntity;
|
||||
|
||||
// (JTA) transaction handling
|
||||
private boolean jtaTrackByThread;
|
||||
|
@ -581,6 +587,12 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
|
|||
true
|
||||
);
|
||||
|
||||
this.allowRefreshDetachedEntity = cfgService.getSetting(
|
||||
ALLOW_REFRESH_DETACHED_ENTITY,
|
||||
BOOLEAN,
|
||||
true
|
||||
);
|
||||
|
||||
this.flushBeforeCompletionEnabled = cfgService.getSetting( FLUSH_BEFORE_COMPLETION, BOOLEAN, true );
|
||||
this.autoCloseSessionEnabled = cfgService.getSetting( AUTO_CLOSE_SESSION, BOOLEAN, false );
|
||||
|
||||
|
@ -871,6 +883,10 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
|
|||
return jtaTransactionAccessEnabled;
|
||||
}
|
||||
|
||||
public boolean isAllowRefreshDetachedEntity() {
|
||||
return allowRefreshDetachedEntity;
|
||||
}
|
||||
|
||||
public boolean isAllowOutOfTransactionUpdateOperations() {
|
||||
return allowOutOfTransactionUpdateOperations;
|
||||
}
|
||||
|
@ -1181,6 +1197,10 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilderImplement
|
|||
return options.isJtaTransactionAccessEnabled();
|
||||
}
|
||||
|
||||
public boolean isAllowRefreshDetachedEntity() {
|
||||
return options.isAllowRefreshDetachedEntity();
|
||||
}
|
||||
|
||||
public boolean isAllowOutOfTransactionUpdateOperations() {
|
||||
return options.isAllowOutOfTransactionUpdateOperations();
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
|
|||
private final boolean flushBeforeCompletionEnabled;
|
||||
private final boolean autoCloseSessionEnabled;
|
||||
private boolean jtaTransactionAccessEnabled;
|
||||
private boolean allowRefreshDetachedEntity;
|
||||
|
||||
private boolean allowOutOfTransactionUpdateOperations;
|
||||
private boolean releaseResourcesOnCloseEnabled;
|
||||
|
@ -132,6 +133,7 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
|
|||
|
||||
this.jpaBootstrap = state.isJpaBootstrap();
|
||||
this.jtaTransactionAccessEnabled = state.isJtaTransactionAccessEnabled();
|
||||
this.allowRefreshDetachedEntity = state.isAllowRefreshDetachedEntity();
|
||||
this.allowOutOfTransactionUpdateOperations = state.isAllowOutOfTransactionUpdateOperations();
|
||||
this.sessionFactoryName = state.getSessionFactoryName();
|
||||
this.sessionFactoryNameAlsoJndiName = state.isSessionFactoryNameAlsoJndiName();
|
||||
|
@ -215,6 +217,11 @@ public class SessionFactoryOptionsImpl implements SessionFactoryOptions {
|
|||
return jtaTransactionAccessEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowRefreshDetachedEntity() {
|
||||
return allowRefreshDetachedEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBeanManagerReference() {
|
||||
return beanManagerReference;
|
||||
|
|
|
@ -49,6 +49,8 @@ public interface SessionFactoryOptionsState {
|
|||
|
||||
boolean isJtaTransactionAccessEnabled();
|
||||
|
||||
boolean isAllowRefreshDetachedEntity();
|
||||
|
||||
boolean isAllowOutOfTransactionUpdateOperations();
|
||||
|
||||
boolean isReleaseResourcesOnCloseEnabled();
|
||||
|
|
|
@ -59,6 +59,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp
|
|||
return delegate.isJtaTransactionAccessEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowRefreshDetachedEntity() {
|
||||
return delegate.isAllowRefreshDetachedEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getBeanManagerReference() {
|
||||
return delegate.getBeanManagerReference();
|
||||
|
|
|
@ -30,6 +30,9 @@ public interface SessionFactoryBuilderImplementor extends SessionFactoryBuilder
|
|||
|
||||
void disableJtaTransactionAccess();
|
||||
|
||||
default void disableRefreshDetachedEntity() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the SessionFactoryOptions that will ultimately be passed to SessionFactoryImpl constructor.
|
||||
*
|
||||
|
|
|
@ -61,6 +61,10 @@ public interface SessionFactoryOptions {
|
|||
|
||||
boolean isJtaTransactionAccessEnabled();
|
||||
|
||||
default boolean isAllowRefreshDetachedEntity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name to be used for the SessionFactory. This is use both in:<ul>
|
||||
* <li>in-VM serialization</li>
|
||||
|
|
|
@ -1558,4 +1558,18 @@ public interface AvailableSettings {
|
|||
* @since 5.2
|
||||
*/
|
||||
String COLLECTION_JOIN_SUBQUERY = "hibernate.collection_join_subquery";
|
||||
|
||||
/**
|
||||
* Setting that allows to call {@link javax.persistence.EntityManager#refresh(Object)} *
|
||||
* (or {@link org.hibernate.Session#refresh(Object)} on a detached entity instance when the {@link org.hibernate.Session} is obtained from
|
||||
* a JPA {@link javax.persistence.EntityManager}).
|
||||
* <p>
|
||||
* <p/>
|
||||
* Values are {@code true} permits the refresh, {@code false} does not permit the detached instance refresh and an {@link IllegalArgumentException} is thrown.
|
||||
* <p/>
|
||||
* The default value is {@code false} when the Session is bootstrapped via JPA {@link javax.persistence.EntityManagerFactory}, otherwise is {@code true}
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
String ALLOW_REFRESH_DETACHED_ENTITY = "hibernate.allow_refresh_detached_entity";
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ import org.hibernate.TypeHelper;
|
|||
import org.hibernate.TypeMismatchException;
|
||||
import org.hibernate.UnknownProfileException;
|
||||
import org.hibernate.UnresolvableObjectException;
|
||||
import org.hibernate.boot.spi.SessionFactoryOptions;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.criterion.NaturalIdentifier;
|
||||
import org.hibernate.engine.internal.StatefulPersistenceContext;
|
||||
|
@ -1264,7 +1265,7 @@ public final class SessionImpl
|
|||
|
||||
private void fireRefresh(RefreshEvent event) {
|
||||
try {
|
||||
if ( getSessionFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
|
||||
if ( !getSessionFactory().getSessionFactoryOptions().isAllowRefreshDetachedEntity() ) {
|
||||
if ( !contains( event.getObject() ) ) {
|
||||
throw new IllegalArgumentException( "Entity not managed" );
|
||||
}
|
||||
|
|
|
@ -103,7 +103,6 @@ import static org.hibernate.internal.HEMLogging.messageLogger;
|
|||
import static org.hibernate.jpa.AvailableSettings.CFG_FILE;
|
||||
import static org.hibernate.jpa.AvailableSettings.CLASS_CACHE_PREFIX;
|
||||
import static org.hibernate.jpa.AvailableSettings.COLLECTION_CACHE_PREFIX;
|
||||
import static org.hibernate.jpa.AvailableSettings.DISCARD_PC_ON_CLOSE;
|
||||
import static org.hibernate.jpa.AvailableSettings.PERSISTENCE_UNIT_NAME;
|
||||
|
||||
/**
|
||||
|
@ -901,6 +900,11 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil
|
|||
( ( SessionFactoryBuilderImplementor ) sfBuilder ).disableJtaTransactionAccess();
|
||||
}
|
||||
|
||||
final boolean allowRefreshDetachedEntity = readBooleanConfigurationValue( org.hibernate.cfg.AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY );
|
||||
if ( !allowRefreshDetachedEntity ) {
|
||||
( (SessionFactoryBuilderImplementor) sfBuilder ).disableRefreshDetachedEntity();
|
||||
}
|
||||
|
||||
// Locate and apply any requested SessionFactoryObserver
|
||||
final Object sessionFactoryObserverSetting = configurationValues.remove( AvailableSettings.SESSION_FACTORY_OBSERVER );
|
||||
if ( sessionFactoryObserverSetting != null ) {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.jpa.test.refresh;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-11188")
|
||||
public class RefreshDetachedInstanceWhenIsAllowedTest extends BaseEntityManagerFunctionalTestCase {
|
||||
private TestEntity testEntity;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] {TestEntity.class};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConfigOptions(Map options) {
|
||||
options.put( AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY, "true" );
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testEntity = new TestEntity();
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
entityManager.persist( testEntity );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnwrappedSessionRefreshDetachedInstance() {
|
||||
final EntityManager entityManager = createEntityManager();
|
||||
final Session session = entityManager.unwrap( Session.class );
|
||||
session.refresh( testEntity );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshDetachedInstance() {
|
||||
final EntityManager entityManager = createEntityManager();
|
||||
entityManager.refresh( testEntity );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.jpa.test.refresh;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
public class RefreshDetachedInstanceWhenIsNotAllowedTest extends BaseEntityManagerFunctionalTestCase {
|
||||
private TestEntity testEntity;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] {TestEntity.class};
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testEntity = new TestEntity();
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
entityManager.persist( testEntity );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testUnwrappedSessionRefreshDetachedInstance() {
|
||||
final EntityManager entityManager = createEntityManager();
|
||||
final Session session = entityManager.unwrap( Session.class );
|
||||
session.refresh( testEntity );
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRefreshDetachedInstance() {
|
||||
final EntityManager entityManager = createEntityManager();
|
||||
entityManager.refresh( testEntity );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.jpa.test.refresh;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
@Entity
|
||||
public class TestEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public long id;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.test.refresh;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.jpa.test.refresh.TestEntity;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-11188")
|
||||
public class RefreshDetachedInstanceWhenIsAllowedTest extends BaseCoreFunctionalTestCase {
|
||||
private TestEntity testEntity;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] {TestEntity.class};
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testEntity = new TestEntity();
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
session.save( testEntity );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefreshDetachedInstance() {
|
||||
final Session session = openSession();
|
||||
session.refresh( testEntity );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.test.refresh;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.jpa.test.refresh.TestEntity;
|
||||
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
public class RefreshDetachedInstanceWhenIsNotAllowedTest extends BaseCoreFunctionalTestCase {
|
||||
private TestEntity testEntity;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] {TestEntity.class};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(Configuration configuration) {
|
||||
configuration.setProperty( AvailableSettings.ALLOW_REFRESH_DETACHED_ENTITY, "false" );
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
testEntity = new TestEntity();
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
session.save( testEntity );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRefreshDetachedInstance() {
|
||||
final Session session = openSession();
|
||||
session.refresh( testEntity );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.test.refresh;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
@Entity
|
||||
public class TestEntity {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public long id;
|
||||
}
|
Loading…
Reference in New Issue