HHH-13135 Use FOR NO KEY UPDATE when locking in PostgreSQL
This commit is contained in:
parent
f048ea8205
commit
de21820f84
|
@ -65,8 +65,14 @@ public class PostgreSQLSqlAstTranslator<T extends JdbcOperation> extends Abstrac
|
||||||
return getDialect().getVersion().isSameOrAfter( 9, 4 );
|
return getDialect().getVersion().isSameOrAfter( 9, 4 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getForUpdate() {
|
||||||
|
return getDialect().getVersion().isSameOrAfter( 9, 3 ) ? " for no key update" : " for update";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getForShare(int timeoutMillis) {
|
protected String getForShare(int timeoutMillis) {
|
||||||
|
// Note that `for key share` is inappropriate as that only means "prevent PK changes"
|
||||||
return " for share";
|
return " for share";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//$Id$
|
||||||
|
package org.hibernate.orm.test.jpa.lock;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.LockModeType;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.NamedQuery;
|
||||||
|
import jakarta.persistence.QueryHint;
|
||||||
|
import jakarta.persistence.Version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Emmanuel Bernard
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class LockReference {
|
||||||
|
private Integer id;
|
||||||
|
private String name;
|
||||||
|
private Lock lock;
|
||||||
|
|
||||||
|
public LockReference() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public LockReference(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
public Lock getLock() {
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLock(Lock lock) {
|
||||||
|
this.lock = lock;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,9 +19,12 @@ import org.hibernate.LockOptions;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.TransactionException;
|
import org.hibernate.TransactionException;
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.dialect.AbstractHANADialect;
|
||||||
import org.hibernate.dialect.CockroachDialect;
|
import org.hibernate.dialect.CockroachDialect;
|
||||||
import org.hibernate.dialect.DerbyDialect;
|
import org.hibernate.dialect.DerbyDialect;
|
||||||
import org.hibernate.dialect.HSQLDialect;
|
import org.hibernate.dialect.HSQLDialect;
|
||||||
|
import org.hibernate.dialect.MariaDBDialect;
|
||||||
|
import org.hibernate.dialect.MySQLDialect;
|
||||||
import org.hibernate.dialect.OracleDialect;
|
import org.hibernate.dialect.OracleDialect;
|
||||||
import org.hibernate.dialect.PostgreSQLDialect;
|
import org.hibernate.dialect.PostgreSQLDialect;
|
||||||
import org.hibernate.dialect.SQLServerDialect;
|
import org.hibernate.dialect.SQLServerDialect;
|
||||||
|
@ -50,6 +53,7 @@ import static org.hibernate.jpa.SpecHints.HINT_SPEC_QUERY_TIMEOUT;
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@ -1156,11 +1160,91 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 5 * 1000) //5 seconds
|
||||||
|
@TestForIssue( jiraKey = "HHH-13135" )
|
||||||
|
@SkipForDialect(value = {
|
||||||
|
MySQLDialect.class,
|
||||||
|
MariaDBDialect.class
|
||||||
|
}, strictMatching = true, comment = "With InnoDB, a FK constraint check acquires a shared lock that isn't compatible with an exclusive lock")
|
||||||
|
@SkipForDialect(value = HSQLDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
|
||||||
|
@SkipForDialect(value = AbstractHANADialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
|
||||||
|
public void testLockInsertFkTarget() {
|
||||||
|
Lock lock = new Lock();
|
||||||
|
lock.setName( "name" );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.persist( lock );
|
||||||
|
} );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, _entityManager -> {
|
||||||
|
|
||||||
|
Lock lock2 = _entityManager.find( Lock.class, lock.getId(), LockModeType.PESSIMISTIC_WRITE );
|
||||||
|
assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( lock2 ) );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) );
|
||||||
|
LockReference ref = new LockReference( 1, "name" );
|
||||||
|
ref.setLock( entityManager.getReference( Lock.class, lock.getId() ) );
|
||||||
|
|
||||||
|
// Check that we can insert a LockReference, referring to a Lock that is PESSIMISTIC_WRITE locked
|
||||||
|
entityManager.persist( ref );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
LockReference lockReference = entityManager.find( LockReference.class, 1 );
|
||||||
|
assertNotNull( lockReference );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 5 * 1000) //5 seconds
|
||||||
|
@TestForIssue( jiraKey = "HHH-13135" )
|
||||||
|
@SkipForDialect(value = {
|
||||||
|
MySQLDialect.class,
|
||||||
|
MariaDBDialect.class
|
||||||
|
}, strictMatching = true, comment = "With InnoDB, a FK constraint check acquires a shared lock that isn't compatible with an exclusive lock")
|
||||||
|
@SkipForDialect(value = HSQLDialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
|
||||||
|
@SkipForDialect(value = AbstractHANADialect.class, comment = "Seems like FK constraint checks are not compatible with exclusive locks")
|
||||||
|
public void testLockUpdateFkTarget() {
|
||||||
|
Lock lock1 = new Lock();
|
||||||
|
lock1.setName( "l1" );
|
||||||
|
Lock lock2 = new Lock();
|
||||||
|
lock2.setName( "l2" );
|
||||||
|
LockReference ref = new LockReference( 1, "name" );
|
||||||
|
ref.setLock( lock1 );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.persist( lock1 );
|
||||||
|
entityManager.persist( lock2 );
|
||||||
|
entityManager.persist( ref );
|
||||||
|
} );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, _entityManager -> {
|
||||||
|
|
||||||
|
Lock l1 = _entityManager.find( Lock.class, lock1.getId(), LockModeType.PESSIMISTIC_WRITE );
|
||||||
|
assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( l1 ) );
|
||||||
|
Lock l2 = _entityManager.find( Lock.class, lock2.getId(), LockModeType.PESSIMISTIC_WRITE );
|
||||||
|
assertEquals( "lock mode should be PESSIMISTIC_WRITE ", LockModeType.PESSIMISTIC_WRITE, _entityManager.getLockMode( l2 ) );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
TransactionUtil.setJdbcTimeout( entityManager.unwrap( Session.class ) );
|
||||||
|
LockReference lockReference = entityManager.find( LockReference.class, ref.getId() );
|
||||||
|
|
||||||
|
// Check that we can update a LockReference, referring to a Lock that is PESSIMISTIC_WRITE locked
|
||||||
|
lockReference.setLock( entityManager.getReference( Lock.class, lock2.getId() ) );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
LockReference lockReference = entityManager.find( LockReference.class, 1 );
|
||||||
|
assertEquals( lock2.getId(), lockReference.getLock().getId() );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class[] getAnnotatedClasses() {
|
public Class[] getAnnotatedClasses() {
|
||||||
return new Class[] {
|
return new Class[] {
|
||||||
Lock.class,
|
Lock.class,
|
||||||
UnversionedLock.class
|
UnversionedLock.class,
|
||||||
|
LockReference.class
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue