consistently pass along session-level lock options
previously, these were respected by a random subset of session methods Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
335f7bea9c
commit
5c89079f2e
|
@ -106,7 +106,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//return a proxy if appropriate
|
//return a proxy if appropriate
|
||||||
Object result = event.getLockMode() == LockMode.NONE
|
final Object result = event.getLockMode() == LockMode.NONE
|
||||||
? proxyOrLoad( event, persister, keyToLoad, loadType )
|
? proxyOrLoad( event, persister, keyToLoad, loadType )
|
||||||
: lockAndLoad( event, persister, keyToLoad, loadType );
|
: lockAndLoad( event, persister, keyToLoad, loadType );
|
||||||
event.setResult( result );
|
event.setResult( result );
|
||||||
|
@ -123,16 +123,15 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
// we may have the jpa requirement of allowing find-by-id where id is the "simple pk value" of a
|
// we may have the jpa requirement of allowing find-by-id where id is the "simple pk value" of a
|
||||||
// dependent objects parent. This is part of its generally goofy derived identity "feature"
|
// dependent objects parent. This is part of its generally goofy derived identity "feature"
|
||||||
final EntityIdentifierMapping idMapping = persister.getIdentifierMapping();
|
final EntityIdentifierMapping idMapping = persister.getIdentifierMapping();
|
||||||
if ( idMapping instanceof CompositeIdentifierMapping ) {
|
if ( idMapping instanceof CompositeIdentifierMapping compositeIdMapping ) {
|
||||||
final CompositeIdentifierMapping compositeIdMapping = (CompositeIdentifierMapping) idMapping;
|
|
||||||
final EmbeddableMappingType partMappingType = compositeIdMapping.getPartMappingType();
|
final EmbeddableMappingType partMappingType = compositeIdMapping.getPartMappingType();
|
||||||
if ( partMappingType.getNumberOfAttributeMappings() == 1 ) {
|
if ( partMappingType.getNumberOfAttributeMappings() == 1 ) {
|
||||||
final AttributeMapping singleIdAttribute = partMappingType.getAttributeMapping( 0 );
|
final AttributeMapping singleIdAttribute = partMappingType.getAttributeMapping( 0 );
|
||||||
if ( singleIdAttribute.getMappedType() instanceof EntityMappingType ) {
|
if ( singleIdAttribute.getMappedType() instanceof EntityMappingType parentIdTargetMapping ) {
|
||||||
final EntityMappingType parentIdTargetMapping = (EntityMappingType) singleIdAttribute.getMappedType();
|
|
||||||
final EntityIdentifierMapping parentIdTargetIdMapping = parentIdTargetMapping.getIdentifierMapping();
|
final EntityIdentifierMapping parentIdTargetIdMapping = parentIdTargetMapping.getIdentifierMapping();
|
||||||
final MappingType parentIdType = parentIdTargetIdMapping instanceof CompositeIdentifierMapping
|
final MappingType parentIdType =
|
||||||
? ((CompositeIdentifierMapping) parentIdTargetIdMapping).getMappedIdEmbeddableTypeDescriptor()
|
parentIdTargetIdMapping instanceof CompositeIdentifierMapping compositeMapping
|
||||||
|
? compositeMapping.getMappedIdEmbeddableTypeDescriptor()
|
||||||
: parentIdTargetIdMapping.getMappedType();
|
: parentIdTargetIdMapping.getMappedType();
|
||||||
if ( parentIdType.getMappedJavaType().getJavaTypeClass().isInstance( event.getEntityId() ) ) {
|
if ( parentIdType.getMappedJavaType().getJavaTypeClass().isInstance( event.getEntityId() ) ) {
|
||||||
// yep that's what we have...
|
// yep that's what we have...
|
||||||
|
@ -309,8 +308,8 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
// else {
|
// else {
|
||||||
// if the entity defines a HibernateProxy factory, see if there is an
|
// if the entity defines a HibernateProxy factory, see if there is an
|
||||||
// existing proxy associated with the PC - and if so, use it
|
// existing proxy associated with the PC - and if so, use it
|
||||||
final Object proxy;
|
final Object proxy = holder == null ? null : holder.getProxy();
|
||||||
if ( holder != null && ( proxy = holder.getProxy() ) != null ) {
|
if ( proxy != null ) {
|
||||||
LOG.trace( "Entity proxy found in session cache" );
|
LOG.trace( "Entity proxy found in session cache" );
|
||||||
if ( LOG.isDebugEnabled() && HibernateProxy.extractLazyInitializer( proxy ).isUnwrap() ) {
|
if ( LOG.isDebugEnabled() && HibernateProxy.extractLazyInitializer( proxy ).isUnwrap() ) {
|
||||||
LOG.debug( "Ignoring NO_PROXY to honor laziness" );
|
LOG.debug( "Ignoring NO_PROXY to honor laziness" );
|
||||||
|
@ -372,7 +371,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
if ( LOG.isTraceEnabled() ) {
|
if ( LOG.isTraceEnabled() ) {
|
||||||
LOG.trace( "Entity proxy found in session cache" );
|
LOG.trace( "Entity proxy found in session cache" );
|
||||||
}
|
}
|
||||||
LazyInitializer li = HibernateProxy.extractLazyInitializer( proxy );
|
final LazyInitializer li = HibernateProxy.extractLazyInitializer( proxy );
|
||||||
if ( li.isUnwrap() ) {
|
if ( li.isUnwrap() ) {
|
||||||
return li.getImplementation();
|
return li.getImplementation();
|
||||||
}
|
}
|
||||||
|
@ -389,7 +388,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object proxyImplementation(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
|
private Object proxyImplementation(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
|
||||||
Object entity = load( event, persister, keyToLoad, options );
|
final Object entity = load( event, persister, keyToLoad, options );
|
||||||
if ( entity != null ) {
|
if ( entity != null ) {
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
@ -415,7 +414,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
* @param persister The persister corresponding to the entity to be loaded
|
* @param persister The persister corresponding to the entity to be loaded
|
||||||
* @param keyToLoad The key of the entity to be loaded
|
* @param keyToLoad The key of the entity to be loaded
|
||||||
* @param options The defined load options
|
* @param options The defined load options
|
||||||
* @param holder
|
* @param holder an {@link EntityHolder} for the key
|
||||||
*
|
*
|
||||||
* @return The created/existing proxy
|
* @return The created/existing proxy
|
||||||
*/
|
*/
|
||||||
|
@ -467,7 +466,7 @@ public class DefaultLoadEventListener implements LoadEventListener {
|
||||||
* @return The loaded entity
|
* @return The loaded entity
|
||||||
*/
|
*/
|
||||||
private Object lockAndLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
|
private Object lockAndLoad(LoadEvent event, EntityPersister persister, EntityKey keyToLoad, LoadType options) {
|
||||||
final SessionImplementor source = event.getSession();;
|
final SessionImplementor source = event.getSession();
|
||||||
final EntityDataAccess cache = persister.getCacheAccessStrategy();
|
final EntityDataAccess cache = persister.getCacheAccessStrategy();
|
||||||
|
|
||||||
final SoftLock lock;
|
final SoftLock lock;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Set;
|
||||||
import javax.naming.NamingException;
|
import javax.naming.NamingException;
|
||||||
|
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.JDBCException;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
import org.hibernate.cache.CacheException;
|
import org.hibernate.cache.CacheException;
|
||||||
import org.hibernate.dialect.Dialect;
|
import org.hibernate.dialect.Dialect;
|
||||||
|
@ -964,4 +965,9 @@ public interface CoreMessageLogger extends BasicLogger {
|
||||||
@Message(value = "Failed to discover types for enhancement from class: %s",
|
@Message(value = "Failed to discover types for enhancement from class: %s",
|
||||||
id = 516)
|
id = 516)
|
||||||
void enhancementDiscoveryFailed(String className, @Cause Throwable cause);
|
void enhancementDiscoveryFailed(String className, @Cause Throwable cause);
|
||||||
|
|
||||||
|
@LogMessage(level = DEBUG)
|
||||||
|
@Message(value = "JDBCException was thrown for a transaction marked for rollback. " +
|
||||||
|
" This is probably due to an operation failing fast due to the transaction being marked for rollback.")
|
||||||
|
void jdbcExceptionThrownWithTransactionRolledBack(@Cause JDBCException e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -309,14 +309,14 @@ public class SessionImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
private LockOptions getLockOptionsForRead() {
|
private LockOptions getLockOptionsForRead() {
|
||||||
return this.lockOptions == null ? fastSessionServices.defaultLockOptions : this.lockOptions;
|
return lockOptions == null ? fastSessionServices.defaultLockOptions : lockOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LockOptions getLockOptionsForWrite() {
|
private LockOptions getLockOptionsForWrite() {
|
||||||
if ( this.lockOptions == null ) {
|
if ( lockOptions == null ) {
|
||||||
this.lockOptions = new LockOptions();
|
lockOptions = new LockOptions();
|
||||||
}
|
}
|
||||||
return this.lockOptions;
|
return lockOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void applyQuerySettingsAndHints(SelectionQuery<?> query) {
|
protected void applyQuerySettingsAndHints(SelectionQuery<?> query) {
|
||||||
|
@ -647,7 +647,9 @@ public class SessionImpl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void lock(Object object, LockMode lockMode) throws HibernateException {
|
public void lock(Object object, LockMode lockMode) throws HibernateException {
|
||||||
fireLock( new LockEvent( object, lockMode, this ) );
|
final LockOptions lockOptions = copySessionLockOptions();
|
||||||
|
lockOptions.setLockMode( lockMode );
|
||||||
|
fireLock( new LockEvent( object, lockOptions, this ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireLock(LockEvent event) {
|
private void fireLock(LockEvent event) {
|
||||||
|
@ -852,13 +854,7 @@ public class SessionImpl
|
||||||
logRemoveOrphanBeforeUpdates( "before continuing", entityName, object );
|
logRemoveOrphanBeforeUpdates( "before continuing", entityName, object );
|
||||||
}
|
}
|
||||||
fireDelete(
|
fireDelete(
|
||||||
new DeleteEvent(
|
new DeleteEvent( entityName, object, isCascadeDeleteEnabled, removingOrphanBeforeUpates, this ),
|
||||||
entityName,
|
|
||||||
object,
|
|
||||||
isCascadeDeleteEnabled,
|
|
||||||
removingOrphanBeforeUpates,
|
|
||||||
this
|
|
||||||
),
|
|
||||||
transientEntities
|
transientEntities
|
||||||
);
|
);
|
||||||
if ( traceEnabled && removingOrphanBeforeUpates ) {
|
if ( traceEnabled && removingOrphanBeforeUpates ) {
|
||||||
|
@ -1159,7 +1155,9 @@ public class SessionImpl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh(Object object, LockMode lockMode) throws HibernateException {
|
public void refresh(Object object, LockMode lockMode) throws HibernateException {
|
||||||
fireRefresh( new RefreshEvent( object, lockMode, this ) );
|
final LockOptions lockOptions = copySessionLockOptions();
|
||||||
|
lockOptions.setLockMode( lockMode );
|
||||||
|
fireRefresh( new RefreshEvent( object, lockOptions, this ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2260,11 +2258,10 @@ public class SessionImpl
|
||||||
public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockModeType, Map<String, Object> properties) {
|
public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockModeType, Map<String, Object> properties) {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
|
|
||||||
LockOptions lockOptions = null;
|
final LockOptions lockOptions = lockModeType == null ? null : buildLockOptions( lockModeType, properties );
|
||||||
try {
|
try {
|
||||||
if ( lockModeType != null ) {
|
if ( lockModeType != null ) {
|
||||||
checkTransactionNeededForLock( LockModeTypeHelper.getLockMode( lockModeType ) );
|
checkTransactionNeededForLock( LockModeTypeHelper.getLockMode( lockModeType ) );
|
||||||
lockOptions = buildLockOptions( lockModeType, properties );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final EffectiveEntityGraph effectiveEntityGraph = loadQueryInfluencers.getEffectiveEntityGraph();
|
final EffectiveEntityGraph effectiveEntityGraph = loadQueryInfluencers.getEffectiveEntityGraph();
|
||||||
|
@ -2276,33 +2273,34 @@ public class SessionImpl
|
||||||
.with( lockOptions )
|
.with( lockOptions )
|
||||||
.load( primaryKey );
|
.load( primaryKey );
|
||||||
}
|
}
|
||||||
catch ( EntityNotFoundException enfe ) {
|
catch ( FetchNotFoundException e ) {
|
||||||
// This may happen if the entity has an associations mapped with
|
// This may happen if the entity has an associations mapped with
|
||||||
// @NotFound(action = NotFoundAction.EXCEPTION) and this associated
|
// @NotFound(action = NotFoundAction.EXCEPTION) and this associated
|
||||||
// entity is not found
|
// entity is not found
|
||||||
if ( enfe instanceof FetchNotFoundException ) {
|
throw e;
|
||||||
throw enfe;
|
|
||||||
}
|
}
|
||||||
|
catch ( EntityFilterException e ) {
|
||||||
// This may happen if the entity has an associations which is
|
// This may happen if the entity has an associations which is
|
||||||
// filtered by a FilterDef and this associated entity is not found
|
// filtered by a FilterDef and this associated entity is not found
|
||||||
if ( enfe instanceof EntityFilterException ) {
|
throw e;
|
||||||
throw enfe;
|
|
||||||
}
|
|
||||||
// DefaultLoadEventListener#returnNarrowedProxy() may throw ENFE (see HHH-7861 for details),
|
|
||||||
// which find() should not throw. Find() should return null if the entity was not found.
|
|
||||||
if ( log.isDebugEnabled() ) {
|
|
||||||
String entityName = entityClass != null ? entityClass.getName(): null;
|
|
||||||
String identifierValue = primaryKey != null ? primaryKey.toString() : null ;
|
|
||||||
log.ignoringEntityNotFound( entityName, identifierValue );
|
|
||||||
}
|
}
|
||||||
|
catch ( EntityNotFoundException e ) {
|
||||||
|
// We swallow other sorts of EntityNotFoundException and return null
|
||||||
|
// For example, DefaultLoadEventListener.proxyImplementation() throws
|
||||||
|
// EntityNotFoundException if there's an existing proxy in the session,
|
||||||
|
// but the underlying database row has been deleted (see HHH-7861)
|
||||||
|
logIgnoringEntityNotFound( entityClass, primaryKey );
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch ( ObjectDeletedException e ) {
|
catch ( ObjectDeletedException e ) {
|
||||||
//the spec is silent about people doing remove() find() on the same PC
|
// the spec is silent about people doing remove() find() on the same PC
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch ( ObjectNotFoundException e ) {
|
catch ( ObjectNotFoundException e ) {
|
||||||
//should not happen on the entity itself with get
|
// should not happen on the entity itself with get
|
||||||
|
// TODO: in fact this will occur instead of EntityNotFoundException
|
||||||
|
// when using StandardEntityNotFoundDelegate, so probably we
|
||||||
|
// should return null here, as we do above
|
||||||
throw new IllegalArgumentException( e.getMessage(), e );
|
throw new IllegalArgumentException( e.getMessage(), e );
|
||||||
}
|
}
|
||||||
catch ( MappingException | TypeMismatchException | ClassCastException e ) {
|
catch ( MappingException | TypeMismatchException | ClassCastException e ) {
|
||||||
|
@ -2310,13 +2308,9 @@ public class SessionImpl
|
||||||
}
|
}
|
||||||
catch ( JDBCException e ) {
|
catch ( JDBCException e ) {
|
||||||
if ( accessTransaction().isActive() && accessTransaction().getRollbackOnly() ) {
|
if ( accessTransaction().isActive() && accessTransaction().getRollbackOnly() ) {
|
||||||
// Assume this is similar to the WildFly / IronJacamar "feature" described under HHH-12472.
|
// Assume situation HHH-12472 running on WildFly
|
||||||
// Just log the exception and return null.
|
// Just log the exception and return null
|
||||||
if ( log.isDebugEnabled() ) {
|
log.jdbcExceptionThrownWithTransactionRolledBack( e );
|
||||||
log.debug( "JDBCException was thrown for a transaction marked for rollback; " +
|
|
||||||
"this is probably due to an operation failing fast due to the " +
|
|
||||||
"transaction marked for rollback.", e );
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -2332,11 +2326,20 @@ public class SessionImpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static <T> void logIgnoringEntityNotFound(Class<T> entityClass, Object primaryKey) {
|
||||||
|
if ( log.isDebugEnabled() ) {
|
||||||
|
log.ignoringEntityNotFound(
|
||||||
|
entityClass != null ? entityClass.getName(): null,
|
||||||
|
primaryKey != null ? primaryKey.toString() : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private <T> IdentifierLoadAccessImpl<T> loadAccessWithOptions(Class<T> entityClass, FindOption[] options) {
|
private <T> IdentifierLoadAccessImpl<T> loadAccessWithOptions(Class<T> entityClass, FindOption[] options) {
|
||||||
final IdentifierLoadAccessImpl<T> loadAccess = byId(entityClass);
|
final IdentifierLoadAccessImpl<T> loadAccess = byId( entityClass );
|
||||||
CacheStoreMode storeMode = getCacheStoreMode();
|
CacheStoreMode storeMode = getCacheStoreMode();
|
||||||
CacheRetrieveMode retrieveMode = getCacheRetrieveMode();
|
CacheRetrieveMode retrieveMode = getCacheRetrieveMode();
|
||||||
LockOptions lockOptions = new LockOptions();
|
LockOptions lockOptions = copySessionLockOptions();
|
||||||
for ( FindOption option : options ) {
|
for ( FindOption option : options ) {
|
||||||
if ( option instanceof CacheStoreMode cacheStoreMode ) {
|
if ( option instanceof CacheStoreMode cacheStoreMode ) {
|
||||||
storeMode = cacheStoreMode;
|
storeMode = cacheStoreMode;
|
||||||
|
@ -2481,8 +2484,9 @@ public class SessionImpl
|
||||||
lock( entity, buildLockOptions( lockMode, options ) );
|
lock( entity, buildLockOptions( lockMode, options ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LockOptions buildLockOptions(LockModeType lockMode, LockOption[] options) {
|
private LockOptions buildLockOptions(LockModeType lockModeType, LockOption[] options) {
|
||||||
final LockOptions lockOptions = new LockOptions( LockModeTypeHelper.getLockMode( lockMode ) );
|
final LockOptions lockOptions = copySessionLockOptions();
|
||||||
|
lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) );
|
||||||
for ( LockOption option : options ) {
|
for ( LockOption option : options ) {
|
||||||
if ( option instanceof PessimisticLockScope lockScope ) {
|
if ( option instanceof PessimisticLockScope lockScope ) {
|
||||||
lockOptions.setLockScope( lockScope );
|
lockOptions.setLockScope( lockScope );
|
||||||
|
@ -2494,6 +2498,23 @@ public class SessionImpl
|
||||||
return lockOptions;
|
return lockOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LockOptions buildLockOptions(LockModeType lockModeType, Map<String, Object> properties) {
|
||||||
|
final LockOptions lockOptions = copySessionLockOptions();
|
||||||
|
lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) );
|
||||||
|
if ( properties != null ) {
|
||||||
|
applyPropertiesToLockOptions( properties, () -> lockOptions );
|
||||||
|
}
|
||||||
|
return lockOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LockOptions copySessionLockOptions() {
|
||||||
|
final LockOptions copiedLockOptions = new LockOptions();
|
||||||
|
if ( lockOptions != null ) {
|
||||||
|
LockOptions.copy( lockOptions, copiedLockOptions );
|
||||||
|
}
|
||||||
|
return copiedLockOptions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh(Object entity, LockModeType lockModeType) {
|
public void refresh(Object entity, LockModeType lockModeType) {
|
||||||
refresh( entity, LockModeTypeHelper.getLockMode( lockModeType ) );
|
refresh( entity, LockModeTypeHelper.getLockMode( lockModeType ) );
|
||||||
|
@ -2526,7 +2547,7 @@ public class SessionImpl
|
||||||
@Override
|
@Override
|
||||||
public void refresh(Object entity, RefreshOption... options) {
|
public void refresh(Object entity, RefreshOption... options) {
|
||||||
CacheStoreMode storeMode = getCacheStoreMode();
|
CacheStoreMode storeMode = getCacheStoreMode();
|
||||||
LockOptions lockOptions = new LockOptions();
|
LockOptions lockOptions = copySessionLockOptions();
|
||||||
for ( RefreshOption option : options ) {
|
for ( RefreshOption option : options ) {
|
||||||
if ( option instanceof CacheStoreMode cacheStoreMode ) {
|
if ( option instanceof CacheStoreMode cacheStoreMode ) {
|
||||||
storeMode = cacheStoreMode;
|
storeMode = cacheStoreMode;
|
||||||
|
@ -2560,18 +2581,6 @@ public class SessionImpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private LockOptions buildLockOptions(LockModeType lockModeType, Map<String, Object> properties) {
|
|
||||||
final LockOptions lockOptions = new LockOptions();
|
|
||||||
if ( this.lockOptions != null ) { //otherwise the default LockOptions constructor is the same as DEFAULT_LOCK_OPTIONS
|
|
||||||
LockOptions.copy( this.lockOptions, lockOptions );
|
|
||||||
}
|
|
||||||
lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) );
|
|
||||||
if ( properties != null ) {
|
|
||||||
applyPropertiesToLockOptions( properties, () -> lockOptions );
|
|
||||||
}
|
|
||||||
return lockOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void detach(Object entity) {
|
public void detach(Object entity) {
|
||||||
checkOpen();
|
checkOpen();
|
||||||
|
|
Loading…
Reference in New Issue