mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-11-12 22:41:35 +00:00
Merge pull request #2090 from ebean-orm/feature/2089
#2089 - Postgres - Use NO KEY with FOR UPDATE clauses with Postgres
This commit is contained in:
6 files changed
+169
-12
No files matched your search
@@ -73,6 +73,7 @@ public class ServerConfigTest {
|
||||
props.setProperty("caseSensitiveCollation", "false");
|
||||
props.setProperty("loadModuleInfo", "true");
|
||||
props.setProperty("collectQueryPlanThresholdMicros", "10000");
|
||||
props.setProperty("lockWithKey", "true");
|
||||
|
||||
serverConfig.loadFromProperties(props);
|
||||
|
||||
@@ -84,6 +85,7 @@ public class ServerConfigTest {
|
||||
|
||||
assertTrue(serverConfig.isIdGeneratorAutomatic());
|
||||
assertFalse(serverConfig.getPlatformConfig().isCaseSensitiveCollation());
|
||||
assertTrue(serverConfig.getPlatformConfig().isLockWithKey());
|
||||
|
||||
assertThat(serverConfig.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.Query;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import org.junit.Test;
|
||||
@@ -20,4 +21,33 @@ public class PostgresPlatformTest {
|
||||
assertThat(columnDefn).isEqualTo("uuid");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void default_forUpdate_expect_noKeyUsed() {
|
||||
|
||||
DatabasePlatform platform = new PostgresPlatform();
|
||||
|
||||
PlatformConfig config = new PlatformConfig();
|
||||
platform.configure(config);
|
||||
|
||||
assertThat(config.isLockWithKey()).isFalse();
|
||||
assertThat(platform.withForUpdate("X", Query.ForUpdate.SKIPLOCKED)).isEqualTo("X for no key update skip locked");
|
||||
assertThat(platform.withForUpdate("X", Query.ForUpdate.NOWAIT)).isEqualTo("X for no key update nowait");
|
||||
assertThat(platform.withForUpdate("X", Query.ForUpdate.BASE)).isEqualTo("X for no key update");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lockWithKey_forUpdate() {
|
||||
|
||||
DatabasePlatform platform = new PostgresPlatform();
|
||||
|
||||
PlatformConfig config = new PlatformConfig();
|
||||
config.setLockWithKey(true);
|
||||
platform.configure(config);
|
||||
|
||||
assertThat(config.isLockWithKey()).isTrue();
|
||||
assertThat(platform.withForUpdate("X", Query.ForUpdate.SKIPLOCKED)).isEqualTo("X for update skip locked");
|
||||
assertThat(platform.withForUpdate("X", Query.ForUpdate.NOWAIT)).isEqualTo("X for update nowait");
|
||||
assertThat(platform.withForUpdate("X", Query.ForUpdate.BASE)).isEqualTo("X for update");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TestQueryForUpdate extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
|
||||
public void testForUpdate() {
|
||||
@@ -35,6 +34,8 @@ public class TestQueryForUpdate extends BaseTestCase {
|
||||
query.findList();
|
||||
if (isSqlServer()) {
|
||||
assertThat(sqlOf(query)).contains("with (updlock)");
|
||||
} else if (isPostgres()) {
|
||||
assertThat(sqlOf(query)).contains("for no key update");
|
||||
} else {
|
||||
assertThat(sqlOf(query)).contains("for update");
|
||||
}
|
||||
@@ -67,7 +68,11 @@ public class TestQueryForUpdate extends BaseTestCase {
|
||||
if (isH2() || isPostgres()) {
|
||||
assertSql(sql.get(0)).contains("from e_basic t0 where t0.id =");
|
||||
assertSql(sql.get(1)).contains("from e_basic t0 where t0.id =");
|
||||
assertSql(sql.get(1)).contains("for update");
|
||||
if (isPostgres()) {
|
||||
assertSql(sql.get(1)).contains("for no key update");
|
||||
} else {
|
||||
assertSql(sql.get(1)).contains("for update");
|
||||
}
|
||||
}
|
||||
|
||||
transaction.end();
|
||||
@@ -91,6 +96,8 @@ public class TestQueryForUpdate extends BaseTestCase {
|
||||
assertThat(sqlOf(query)).contains("for update");
|
||||
} else if (isSqlServer()) {
|
||||
assertThat(sqlOf(query)).contains("with (updlock,nowait)");
|
||||
} else if (isPostgres()) {
|
||||
assertThat(sqlOf(query)).contains("for no key update nowait");
|
||||
} else {
|
||||
assertThat(sqlOf(query)).contains("for update nowait");
|
||||
}
|
||||
@@ -116,6 +123,8 @@ public class TestQueryForUpdate extends BaseTestCase {
|
||||
assertThat(sqlOf(query)).contains("with (updlock,nowait)");
|
||||
} else if (isH2()) {
|
||||
assertThat(sqlOf(query)).contains("for update");
|
||||
} else if (isPostgres()) {
|
||||
assertThat(sqlOf(query)).contains("for no key update nowait");
|
||||
} else {
|
||||
assertThat(sqlOf(query)).contains("for update nowait");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.tests.basic;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.annotation.ForPlatform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.annotation.Transactional;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.tests.model.basic.Article;
|
||||
import org.tests.model.basic.Section;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestQueryForUpdatePostgresLock extends BaseTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestQueryForUpdatePostgresLock.class);
|
||||
|
||||
private long timePreInsert;
|
||||
private long timePostInsert;
|
||||
private long timePreLock;
|
||||
private long timePostLock;
|
||||
|
||||
@Test
|
||||
@ForPlatform(Platform.POSTGRES)
|
||||
public void testForUpdatePostgresLock() throws InterruptedException {
|
||||
|
||||
Article article = new Article("lockTest", "auth");
|
||||
DB.save(article);
|
||||
final Integer id = article.getId();
|
||||
|
||||
ExecutorService exec = Executors.newFixedThreadPool(2);
|
||||
exec.submit(() -> lockArticle(id));
|
||||
exec.submit(() -> insertSection(id));
|
||||
exec.awaitTermination(2, TimeUnit.SECONDS);
|
||||
exec.shutdown();
|
||||
|
||||
// assert the lock was obtained before the insert was attempted
|
||||
assertThat(timePreLock).isLessThan(timePreInsert);
|
||||
// assert that the insert wasn't waiting on the lock to complete
|
||||
assertThat(timePostInsert).isLessThan(timePostLock);
|
||||
}
|
||||
|
||||
/**
|
||||
* This holds row lock on article for 1 second.
|
||||
* With FOR NO KEY UPDATE this does not block the insert.
|
||||
*/
|
||||
@Transactional
|
||||
private void lockArticle(Integer id) {
|
||||
timePreLock = System.currentTimeMillis();
|
||||
log.info("lock start");
|
||||
DB.find(Article.class).setId(id).forUpdate().findOne();
|
||||
sleep(1000);
|
||||
timePostLock = System.currentTimeMillis();
|
||||
log.info("lock done");
|
||||
}
|
||||
|
||||
/**
|
||||
* This inserts with FK to the article that is locked.
|
||||
* With FOR NO KEY UPDATE this insert does not wait on the lock.
|
||||
*/
|
||||
@Transactional
|
||||
private void insertSection(Integer id) {
|
||||
sleep(100);
|
||||
log.info("insert start");
|
||||
timePreInsert = System.currentTimeMillis();
|
||||
Section section = new Section();
|
||||
section.setArticle(DB.getReference(Article.class, id));
|
||||
DB.save(section);
|
||||
timePostInsert = System.currentTimeMillis();
|
||||
log.info("inserted");
|
||||
}
|
||||
|
||||
private void sleep(int millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user