/*
* 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.lock;
import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.LockModeType;
import javax.persistence.Query;
import javax.persistence.Table;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Root;
import org.hibernate.LockMode;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.internal.SessionImpl;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.query.NativeQuery;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Steve Ebersole
*/
public class QueryLockingTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] {Person.class, Lockable.class, LocalEntity.class};
}
@Override
@SuppressWarnings({ "unchecked" })
protected void addConfigOptions(Map options) {
options.put( org.hibernate.cfg.AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" );
}
@Test
public void testOverallLockMode() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
org.hibernate.query.Query query = em.createQuery( "from Lockable l" ).unwrap( org.hibernate.query.Query.class );
assertEquals( LockMode.NONE, query.getLockOptions().getLockMode() );
assertNull( query.getLockOptions().getAliasSpecificLockMode( "l" ) );
assertEquals( LockMode.NONE, query.getLockOptions().getEffectiveLockMode( "l" ) );
// NOTE : LockModeType.READ should map to LockMode.OPTIMISTIC
query.setLockMode( LockModeType.READ );
assertEquals( LockMode.OPTIMISTIC, query.getLockOptions().getLockMode() );
assertNull( query.getLockOptions().getAliasSpecificLockMode( "l" ) );
assertEquals( LockMode.OPTIMISTIC, query.getLockOptions().getEffectiveLockMode( "l" ) );
query.setHint( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE+".l", LockModeType.PESSIMISTIC_WRITE );
assertEquals( LockMode.OPTIMISTIC, query.getLockOptions().getLockMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, query.getLockOptions().getAliasSpecificLockMode( "l" ) );
assertEquals( LockMode.PESSIMISTIC_WRITE, query.getLockOptions().getEffectiveLockMode( "l" ) );
em.getTransaction().commit();
em.close();
}
@Test
@TestForIssue( jiraKey = "HHH-8756" )
public void testNoneLockModeForNonSelectQueryAllowed() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
org.hibernate.query.Query query = em.createQuery( "delete from Lockable l" ).unwrap( org.hibernate.query.Query.class );
assertEquals( LockMode.NONE, query.getLockOptions().getLockMode() );
query.setLockMode( LockModeType.NONE );
em.getTransaction().commit();
em.clear();
// ensure other modes still throw the exception
em.getTransaction().begin();
query = em.createQuery( "delete from Lockable l" ).unwrap( org.hibernate.query.Query.class );
assertEquals( LockMode.NONE, query.getLockOptions().getLockMode() );
try {
// Throws IllegalStateException
query.setLockMode( LockModeType.PESSIMISTIC_WRITE );
fail( "IllegalStateException should have been thrown." );
}
catch (IllegalStateException e) {
// expected
}
finally {
em.getTransaction().rollback();
em.close();
}
}
@Test
public void testNativeSql() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
NativeQuery query = em.createNativeQuery( "select * from lockable l" ).unwrap( NativeQuery.class );
// the spec disallows calling setLockMode in a native SQL query
try {
query.setLockMode( LockModeType.READ );
fail( "Should have failed" );
}
catch (IllegalStateException expected) {
}
// however, we should be able to set it using hints
query.setHint( QueryHints.HINT_NATIVE_LOCKMODE, LockModeType.READ );
// NOTE : LockModeType.READ should map to LockMode.OPTIMISTIC
assertEquals( LockMode.OPTIMISTIC, query.getLockOptions().getLockMode() );
assertNull( query.getLockOptions().getAliasSpecificLockMode( "l" ) );
assertEquals( LockMode.OPTIMISTIC, query.getLockOptions().getEffectiveLockMode( "l" ) );
query.setHint( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE+".l", LockModeType.PESSIMISTIC_WRITE );
assertEquals( LockMode.OPTIMISTIC, query.getLockOptions().getLockMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, query.getLockOptions().getAliasSpecificLockMode( "l" ) );
assertEquals( LockMode.PESSIMISTIC_WRITE, query.getLockOptions().getEffectiveLockMode( "l" ) );
em.getTransaction().commit();
em.close();
}
@Test
public void testPessimisticForcedIncrementOverall() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable lock = new Lockable( "name" );
em.persist( lock );
em.getTransaction().commit();
em.close();
Integer initial = lock.getVersion();
assertNotNull( initial );
em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable reread = em.createQuery( "from Lockable", Lockable.class ).setLockMode( LockModeType.PESSIMISTIC_FORCE_INCREMENT ).getSingleResult();
assertFalse( reread.getVersion().equals( initial ) );
em.getTransaction().commit();
em.close();
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.remove( em.getReference( Lockable.class, reread.getId() ) );
em.getTransaction().commit();
em.close();
}
@Test
public void testPessimisticForcedIncrementSpecific() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable lock = new Lockable( "name" );
em.persist( lock );
em.getTransaction().commit();
em.close();
Integer initial = lock.getVersion();
assertNotNull( initial );
em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable reread = em.createQuery( "from Lockable l", Lockable.class )
.setHint( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE+".l", LockModeType.PESSIMISTIC_FORCE_INCREMENT )
.getSingleResult();
assertFalse( reread.getVersion().equals( initial ) );
em.getTransaction().commit();
em.close();
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.remove( em.getReference( Lockable.class, reread.getId() ) );
em.getTransaction().commit();
em.close();
}
@Test
public void testOptimisticForcedIncrementOverall() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable lock = new Lockable( "name" );
em.persist( lock );
em.getTransaction().commit();
em.close();
Integer initial = lock.getVersion();
assertNotNull( initial );
em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable reread = em.createQuery( "from Lockable", Lockable.class ).setLockMode( LockModeType.OPTIMISTIC_FORCE_INCREMENT ).getSingleResult();
assertEquals( initial, reread.getVersion() );
em.getTransaction().commit();
em.close();
assertFalse( reread.getVersion().equals( initial ) );
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.remove( em.getReference( Lockable.class, reread.getId() ) );
em.getTransaction().commit();
em.close();
}
@Test
public void testOptimisticForcedIncrementSpecific() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable lock = new Lockable( "name" );
em.persist( lock );
em.getTransaction().commit();
em.close();
Integer initial = lock.getVersion();
assertNotNull( initial );
em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable reread = em.createQuery( "from Lockable l", Lockable.class )
.setHint( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE+".l", LockModeType.OPTIMISTIC_FORCE_INCREMENT )
.getSingleResult();
assertEquals( initial, reread.getVersion() );
em.getTransaction().commit();
em.close();
assertFalse( reread.getVersion().equals( initial ) );
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.remove( em.getReference( Lockable.class, reread.getId() ) );
em.getTransaction().commit();
em.close();
}
@Test
public void testOptimisticOverall() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable lock = new Lockable( "name" );
em.persist( lock );
em.getTransaction().commit();
em.close();
Integer initial = lock.getVersion();
assertNotNull( initial );
em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable reread = em.createQuery( "from Lockable", Lockable.class )
.setLockMode( LockModeType.OPTIMISTIC )
.getSingleResult();
assertEquals( initial, reread.getVersion() );
assertTrue( em.unwrap( SessionImpl.class ).getActionQueue().hasBeforeTransactionActions() );
em.getTransaction().commit();
em.close();
assertEquals( initial, reread.getVersion() );
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.remove( em.getReference( Lockable.class, reread.getId() ) );
em.getTransaction().commit();
em.close();
}
@Test
@TestForIssue(jiraKey = "HHH-9419")
public void testNoVersionCheckAfterRemove() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable lock = new Lockable( "name" );
em.persist( lock );
em.getTransaction().commit();
em.close();
Integer initial = lock.getVersion();
assertNotNull( initial );
em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable reread = em.createQuery( "from Lockable", Lockable.class )
.setLockMode( LockModeType.OPTIMISTIC )
.getSingleResult();
assertEquals( initial, reread.getVersion() );
assertTrue( em.unwrap( SessionImpl.class ).getActionQueue().hasBeforeTransactionActions() );
em.remove( reread );
em.getTransaction().commit();
em.close();
assertEquals( initial, reread.getVersion() );
}
@Test
public void testOptimisticSpecific() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable lock = new Lockable( "name" );
em.persist( lock );
em.getTransaction().commit();
em.close();
Integer initial = lock.getVersion();
assertNotNull( initial );
em = getOrCreateEntityManager();
em.getTransaction().begin();
Lockable reread = em.createQuery( "from Lockable l", Lockable.class )
.setHint( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE+".l", LockModeType.OPTIMISTIC )
.getSingleResult();
assertEquals( initial, reread.getVersion() );
assertTrue( em.unwrap( SessionImpl.class ).getActionQueue().hasBeforeTransactionActions() );
em.getTransaction().commit();
em.close();
assertEquals( initial, reread.getVersion() );
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.remove( em.getReference( Lockable.class, reread.getId() ) );
em.getTransaction().commit();
em.close();
}
/**
* lock some entities via a query and check the resulting lock mode type via EntityManager
*/
@Test
@RequiresDialectFeature( value = DialectChecks.DoesNotSupportFollowOnLocking.class)
public void testEntityLockModeStateAfterQueryLocking() {
// Create some test data
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
em.persist( new LocalEntity( 1, "test" ) );
em.getTransaction().commit();
// em.close();
// issue the query with locking
// em = getOrCreateEntityManager();
em.getTransaction().begin();
Query query = em.createQuery( "select l from LocalEntity l" );
assertEquals( LockModeType.NONE, query.getLockMode() );
query.setLockMode( LockModeType.PESSIMISTIC_READ );
assertEquals( LockModeType.PESSIMISTIC_READ, query.getLockMode() );
List<LocalEntity> results = query.getResultList();
// and check the lock mode for each result
for ( LocalEntity e : results ) {
assertEquals( LockModeType.PESSIMISTIC_READ, em.getLockMode( e ) );
}
em.getTransaction().commit();
em.close();
// clean up test data
em = getOrCreateEntityManager();
em.getTransaction().begin();
em.createQuery( "delete from LocalEntity" ).executeUpdate();
em.getTransaction().commit();
em.close();
}
@Test
@TestForIssue(jiraKey = "HHH-11376")
@RequiresDialect( SQLServerDialect.class )
public void testCriteriaWithPessimisticLock() {
TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Person> criteria = builder.createQuery( Person.class );
Root<Person> personRoot = criteria.from( Person.class );
ParameterExpression<Long> personIdParameter = builder.parameter( Long.class );
// Eagerly fetch the parent
personRoot.fetch( "parent", JoinType.LEFT );
criteria.select( personRoot )
.where( builder.equal( personRoot.get( "id" ), personIdParameter ) );
final List<Person> resultList = entityManager.createQuery( criteria )
.setParameter( personIdParameter, 1L )
.setLockMode( LockModeType.PESSIMISTIC_WRITE )
.getResultList();
resultList.isEmpty();
} );
}
@Entity(name = "LocalEntity")
@Table(name = "LocalEntity")
public static class LocalEntity {
private Integer id;
private String name;
public LocalEntity() {
}
public LocalEntity(Integer id, String name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}