implements optimistic locking for PackageEntity
This commit is contained in:
parent
5ea8069608
commit
a04929453c
@ -19,6 +19,9 @@ public class PackageEntity {
|
|||||||
|
|
||||||
private @Id UUID uuid;
|
private @Id UUID uuid;
|
||||||
|
|
||||||
|
@Version
|
||||||
|
private int version;
|
||||||
|
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
@JoinColumn(name = "customeruuid")
|
@JoinColumn(name = "customeruuid")
|
||||||
private CustomerEntity customer;
|
private CustomerEntity customer;
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
create table if not exists package
|
create table if not exists package
|
||||||
(
|
(
|
||||||
uuid uuid unique references RbacObject (uuid),
|
uuid uuid unique references RbacObject (uuid),
|
||||||
|
version int not null default 0,
|
||||||
customerUuid uuid references customer (uuid),
|
customerUuid uuid references customer (uuid),
|
||||||
name varchar(5),
|
name varchar(5),
|
||||||
description varchar(96)
|
description varchar(96)
|
||||||
|
@ -2,11 +2,13 @@ package net.hostsharing.hsadminng.hs.hspackage;
|
|||||||
|
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.hs.hscustomer.CustomerRepository;
|
import net.hostsharing.hsadminng.hs.hscustomer.CustomerRepository;
|
||||||
|
import net.hostsharing.test.JpaAttempt;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.orm.ObjectOptimisticLockingFailureException;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
import org.springframework.test.annotation.DirtiesContext;
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -18,7 +20,7 @@ import static net.hostsharing.test.JpaAttempt.attempt;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class })
|
@ComponentScan(basePackageClasses = { Context.class, CustomerRepository.class, JpaAttempt.class })
|
||||||
@DirtiesContext
|
@DirtiesContext
|
||||||
class PackageRepositoryIntegrationTest {
|
class PackageRepositoryIntegrationTest {
|
||||||
|
|
||||||
@ -28,7 +30,11 @@ class PackageRepositoryIntegrationTest {
|
|||||||
@Autowired
|
@Autowired
|
||||||
PackageRepository packageRepository;
|
PackageRepository packageRepository;
|
||||||
|
|
||||||
@Autowired EntityManager em;
|
@Autowired
|
||||||
|
EntityManager em;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
JpaAttempt jpaAttempt;
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
class FindAllByOptionalNameLike {
|
class FindAllByOptionalNameLike {
|
||||||
@ -88,13 +94,13 @@ class PackageRepositoryIntegrationTest {
|
|||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = attempt(
|
final var result = attempt(
|
||||||
em,
|
em,
|
||||||
() -> packageRepository.findAllByOptionalNameLike(null));
|
() -> packageRepository.findAllByOptionalNameLike(null));
|
||||||
|
|
||||||
// then
|
// then
|
||||||
result.assertExceptionWithRootCauseMessage(
|
result.assertExceptionWithRootCauseMessage(
|
||||||
JpaSystemException.class,
|
JpaSystemException.class,
|
||||||
"[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
|
"[403] user admin@aaa.example.com", "has no permission to assume role package#aab00#admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -102,12 +108,12 @@ class PackageRepositoryIntegrationTest {
|
|||||||
currentUser("unknown@example.org");
|
currentUser("unknown@example.org");
|
||||||
|
|
||||||
final var result = attempt(
|
final var result = attempt(
|
||||||
em,
|
em,
|
||||||
() -> packageRepository.findAllByOptionalNameLike(null));
|
() -> packageRepository.findAllByOptionalNameLike(null));
|
||||||
|
|
||||||
result.assertExceptionWithRootCauseMessage(
|
result.assertExceptionWithRootCauseMessage(
|
||||||
JpaSystemException.class,
|
JpaSystemException.class,
|
||||||
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
|
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -117,16 +123,59 @@ class PackageRepositoryIntegrationTest {
|
|||||||
assumedRoles("customer#aaa.admin");
|
assumedRoles("customer#aaa.admin");
|
||||||
|
|
||||||
final var result = attempt(
|
final var result = attempt(
|
||||||
em,
|
em,
|
||||||
() -> packageRepository.findAllByOptionalNameLike(null));
|
() -> packageRepository.findAllByOptionalNameLike(null));
|
||||||
|
|
||||||
result.assertExceptionWithRootCauseMessage(
|
result.assertExceptionWithRootCauseMessage(
|
||||||
JpaSystemException.class,
|
JpaSystemException.class,
|
||||||
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
|
"hsadminng.currentUser defined as unknown@example.org, but does not exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class OptimisticLocking {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportsOptimisticLocking() throws InterruptedException {
|
||||||
|
// given
|
||||||
|
hostsharingAdminWithAssumedRole("package#aaa00.admin");
|
||||||
|
final var pac = packageRepository.findAllByOptionalNameLike("%").get(0);
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var result1 = jpaAttempt.transacted(() -> {
|
||||||
|
hostsharingAdminWithAssumedRole("package#aaa00.admin");
|
||||||
|
pac.setDescription("description set by thread 1");
|
||||||
|
packageRepository.save(pac);
|
||||||
|
});
|
||||||
|
final var result2 = jpaAttempt.transacted(() -> {
|
||||||
|
hostsharingAdminWithAssumedRole("package#aaa00.admin");
|
||||||
|
pac.setDescription("description set by thread 2");
|
||||||
|
packageRepository.save(pac);
|
||||||
|
sleep(1500);
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
em.refresh(pac);
|
||||||
|
assertThat(pac.getDescription()).isEqualTo("description set by thread 1");
|
||||||
|
assertThat(result1.caughtException()).isNull();
|
||||||
|
assertThat(result2.caughtException()).isInstanceOf(ObjectOptimisticLockingFailureException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sleep(final int millis) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hostsharingAdminWithAssumedRole(final String assumedRoles) {
|
||||||
|
currentUser("mike@hostsharing.net");
|
||||||
|
assumedRoles(assumedRoles);
|
||||||
|
}
|
||||||
|
|
||||||
void currentUser(final String currentUser) {
|
void currentUser(final String currentUser) {
|
||||||
context.setCurrentUser(currentUser);
|
context.setCurrentUser(currentUser);
|
||||||
assertThat(context.getCurrentUser()).as("precondition").isEqualTo(currentUser);
|
assertThat(context.getCurrentUser()).as("precondition").isEqualTo(currentUser);
|
||||||
@ -139,14 +188,14 @@ class PackageRepositoryIntegrationTest {
|
|||||||
|
|
||||||
void noPackagesAreReturned(final List<PackageEntity> actualResult) {
|
void noPackagesAreReturned(final List<PackageEntity> actualResult) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
.extracting(PackageEntity::getName)
|
.extracting(PackageEntity::getName)
|
||||||
.isEmpty();
|
.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void exactlyThesePackagesAreReturned(final List<PackageEntity> actualResult, final String... packageNames) {
|
void exactlyThesePackagesAreReturned(final List<PackageEntity> actualResult, final String... packageNames) {
|
||||||
assertThat(actualResult)
|
assertThat(actualResult)
|
||||||
.extracting(PackageEntity::getName)
|
.extracting(PackageEntity::getName)
|
||||||
.containsExactlyInAnyOrder(packageNames);
|
.containsExactlyInAnyOrder(packageNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,6 @@ public class TestPackage {
|
|||||||
public static final PackageEntity xxx02 = hsPackage(TestCustomer.xxx, "xxx02");
|
public static final PackageEntity xxx02 = hsPackage(TestCustomer.xxx, "xxx02");
|
||||||
|
|
||||||
public static PackageEntity hsPackage(final CustomerEntity customer, final String name) {
|
public static PackageEntity hsPackage(final CustomerEntity customer, final String name) {
|
||||||
return new PackageEntity(randomUUID(), customer, name, "initial description of package " + name);
|
return new PackageEntity(randomUUID(), 0, customer, name, "initial description of package " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user