HHH-12257 : Refreshing an entity clears the lock mode

(cherry picked from commit a286232da3)
This commit is contained in:
Gail Badner 2018-02-27 21:41:37 -08:00
parent 2c15a5f587
commit 771c8fdf82
2 changed files with 89 additions and 2 deletions

View File

@ -10,7 +10,11 @@ import java.io.Serializable;
import java.util.IdentityHashMap;
import java.util.Map;
import javax.persistence.LockModeType;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.PersistentObjectException;
import org.hibernate.UnresolvableObjectException;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
@ -173,7 +177,15 @@ public class DefaultRefreshEventListener implements RefreshEventListener {
String previousFetchProfile = source.getLoadQueryInfluencers().getInternalFetchProfile();
source.getLoadQueryInfluencers().setInternalFetchProfile( "refresh" );
Object result = persister.load( id, object, event.getLockOptions(), source );
// Use the entity's current LockMode if it is greater than event.getLockMode()
final LockMode currentLockMode = e == null ? null : e.getLockMode();
LockOptions lockOptionsToUse = event.getLockOptions();
if ( currentLockMode != null && currentLockMode.greaterThan( event.getLockMode() ) ) {
lockOptionsToUse = LockOptions.copy( event.getLockOptions(), new LockOptions() );
lockOptionsToUse.setLockMode( currentLockMode );
}
Object result = persister.load( id, object, lockOptionsToUse, source );
// Keep the same read-only/modifiable setting for the entity that it had before refreshing;
// If it was transient, then set it to the default for the source.
if ( result != null ) {

View File

@ -6,12 +6,17 @@
*/
package org.hibernate.test.locking;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
import javax.persistence.LockModeType;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.testing.SkipForDialect;
import org.hibernate.testing.TestForIssue;
@ -21,6 +26,7 @@ import org.hibernate.testing.util.ExceptionUtil;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@ -136,7 +142,7 @@ public class LockModeTest extends BaseCoreFunctionalTestCase {
// shouldn't throw an exception
session.createQuery( "SELECT a.value FROM A a where a.id = :id" )
.setLockMode( "a", LockMode.NONE )
.setParameter( "id", 1L )
.setParameter( "id", id )
.list();
} );
}
@ -153,6 +159,75 @@ public class LockModeTest extends BaseCoreFunctionalTestCase {
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12257")
public void testRefreshLockedEntity() {
doInHibernate( this::sessionFactory, session -> {
A a = session.get( A.class, id, LockMode.PESSIMISTIC_READ );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( a );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( A.class.getName(), a );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( a, Collections.emptyMap() );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( a, null, Collections.emptyMap() );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12257")
public void testRefreshWithExplicitLowerLevelLockMode() {
doInHibernate( this::sessionFactory, session -> {
A a = session.get( A.class, id, LockMode.PESSIMISTIC_READ );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( a, LockMode.READ );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( a, LockModeType.READ );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( a, LockModeType.READ, Collections.emptyMap() );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12257")
public void testRefreshWithExplicitHigherLevelLockMode() {
doInHibernate( this::sessionFactory, session -> {
A a = session.get( A.class, id );
checkLockMode( a, LockMode.READ, session );
session.refresh( a, LockMode.UPGRADE_NOWAIT );
checkLockMode( a, LockMode.UPGRADE_NOWAIT, session );
session.refresh( a, LockModeType.PESSIMISTIC_READ );
checkLockMode( a, LockMode.PESSIMISTIC_READ, session );
session.refresh( a, LockModeType.PESSIMISTIC_WRITE, Collections.emptyMap() );
checkLockMode( a, LockMode.PESSIMISTIC_WRITE, session );
} );
}
@Test
@TestForIssue(jiraKey = "HHH-12257")
public void testRefreshAfterUpdate() {
doInHibernate( this::sessionFactory, session -> {
A a = session.get( A.class, id );
checkLockMode( a, LockMode.READ, session );
a.setValue( "new value" );
session.flush();
checkLockMode( a, LockMode.WRITE, session );
session.refresh( a );
checkLockMode( a, LockMode.WRITE, session );
} );
}
private void checkLockMode(Object entity, LockMode expectedLockMode, Session session) {
final LockMode lockMode =
( (SharedSessionContractImplementor) session ).getPersistenceContext().getEntry( entity ).getLockMode();
assertEquals( expectedLockMode, lockMode );
}
private void nowAttemptToUpdateRow() {
// here we just need to open a new connection (database session and transaction) and make sure that
// we are not allowed to acquire exclusive locks to that row and/or write to that row. That may take