Improve return value semantices for tm().load() (#576)

Since we rarely (if ever) want to check the result of a single element load,
make TransactionManager.load(VKey) return non-optional, non-nullable and just
throw a NoSuchElementException if the entity is not found.

Also add a maybeLoad() that does return an optional in case we ever want to do
this (exists() should suffice for an existence check).
This commit is contained in:
Michael Muller 2020-05-04 10:49:36 -04:00 committed by GitHub
parent c5aa0125ab
commit e2dfb6488d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 68 additions and 25 deletions

View file

@ -22,6 +22,7 @@ import com.googlecode.objectify.Key;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.persistence.transaction.TransactionManager; import google.registry.persistence.transaction.TransactionManager;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -136,10 +137,19 @@ public class DatastoreTransactionManager implements TransactionManager {
// VKey instead of by ofy Key. But ideally, there should be one set of TransactionManager // VKey instead of by ofy Key. But ideally, there should be one set of TransactionManager
// interface tests that are applied to both the datastore and SQL implementations. // interface tests that are applied to both the datastore and SQL implementations.
@Override @Override
public <T> Optional<T> load(VKey<T> key) { public <T> Optional<T> maybeLoad(VKey<T> key) {
return Optional.of(getOfy().load().key(key.getOfyKey()).now()); return Optional.of(getOfy().load().key(key.getOfyKey()).now());
} }
@Override
public <T> T load(VKey<T> key) {
T result = getOfy().load().key(key.getOfyKey()).now();
if (result == null) {
throw new NoSuchElementException(key.toString());
}
return result;
}
@Override @Override
public <T> ImmutableList<T> load(Iterable<VKey<T>> keys) { public <T> ImmutableList<T> load(Iterable<VKey<T>> keys) {
Iterator<Key<T>> iter = Iterator<Key<T>> iter =

View file

@ -232,12 +232,23 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
} }
@Override @Override
public <T> Optional<T> load(VKey<T> key) { public <T> Optional<T> maybeLoad(VKey<T> key) {
checkArgumentNotNull(key, "key must be specified"); checkArgumentNotNull(key, "key must be specified");
assertInTransaction(); assertInTransaction();
return Optional.ofNullable(getEntityManager().find(key.getKind(), key.getSqlKey())); return Optional.ofNullable(getEntityManager().find(key.getKind(), key.getSqlKey()));
} }
@Override
public <T> T load(VKey<T> key) {
checkArgumentNotNull(key, "key must be specified");
assertInTransaction();
T result = getEntityManager().find(key.getKind(), key.getSqlKey());
if (result == null) {
throw new NoSuchElementException(key.toString());
}
return result;
}
@Override @Override
public <T> ImmutableList<T> load(Iterable<VKey<T>> keys) { public <T> ImmutableList<T> load(Iterable<VKey<T>> keys) {
checkArgumentNotNull(keys, "keys must be specified"); checkArgumentNotNull(keys, "keys must be specified");

View file

@ -108,7 +108,10 @@ public interface TransactionManager {
<T> boolean checkExists(VKey<T> key); <T> boolean checkExists(VKey<T> key);
/** Loads the entity by its id, returns empty if the entity doesn't exist. */ /** Loads the entity by its id, returns empty if the entity doesn't exist. */
<T> Optional<T> load(VKey<T> key); <T> Optional<T> maybeLoad(VKey<T> key);
/** Loads the entity by its id, throws NoSuchElementException if it doesn't exist. */
<T> T load(VKey<T> key);
/** /**
* Leads the set of entities by their key id. * Leads the set of entities by their key id.

View file

@ -140,8 +140,7 @@ public class ContactResourceTest extends EntityTestCase {
.transact( .transact(
() -> () ->
jpaTm() jpaTm()
.load(VKey.createSql(ContactResource.class, originalContact.getRepoId()))) .load(VKey.createSql(ContactResource.class, originalContact.getRepoId())));
.get();
// TODO(b/153378849): Remove the hard code for postal info after resolving the issue that // TODO(b/153378849): Remove the hard code for postal info after resolving the issue that
// @PostLoad doesn't work in Address // @PostLoad doesn't work in Address
ContactResource fixed = ContactResource fixed =

View file

@ -69,14 +69,14 @@ public class EntityCallbacksListenerTest {
checkAll(updated, 0, 1, 0, 1); checkAll(updated, 0, 1, 0, 1);
TestEntity testLoad = TestEntity testLoad =
jpaTm().transact(() -> jpaTm().load(VKey.createSql(TestEntity.class, "id"))).get(); jpaTm().transact(() -> jpaTm().load(VKey.createSql(TestEntity.class, "id")));
checkAll(testLoad, 0, 0, 0, 1); checkAll(testLoad, 0, 0, 0, 1);
TestEntity testRemove = TestEntity testRemove =
jpaTm() jpaTm()
.transact( .transact(
() -> { () -> {
TestEntity removed = jpaTm().load(VKey.createSql(TestEntity.class, "id")).get(); TestEntity removed = jpaTm().load(VKey.createSql(TestEntity.class, "id"));
jpaTm().getEntityManager().remove(removed); jpaTm().getEntityManager().remove(removed);
return removed; return removed;
}); });

View file

@ -26,6 +26,7 @@ import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import google.registry.testing.FakeClock; import google.registry.testing.FakeClock;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.NoSuchElementException;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.Id; import javax.persistence.Id;
@ -147,7 +148,7 @@ public class JpaTransactionManagerImplTest {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
jpaTm().transact(() -> jpaTm().saveNew(theEntity)); jpaTm().transact(() -> jpaTm().saveNew(theEntity));
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isTrue(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isTrue();
assertThat(jpaTm().transact(() -> jpaTm().load(theEntityKey).get())).isEqualTo(theEntity); assertThat(jpaTm().transact(() -> jpaTm().load(theEntityKey))).isEqualTo(theEntity);
} }
@Test @Test
@ -155,7 +156,7 @@ public class JpaTransactionManagerImplTest {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
jpaTm().transact(() -> jpaTm().saveNew(theEntity)); jpaTm().transact(() -> jpaTm().saveNew(theEntity));
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isTrue(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isTrue();
assertThat(jpaTm().transact(() -> jpaTm().load(theEntityKey).get())).isEqualTo(theEntity); assertThat(jpaTm().transact(() -> jpaTm().load(theEntityKey))).isEqualTo(theEntity);
assertThrows(RollbackException.class, () -> jpaTm().transact(() -> jpaTm().saveNew(theEntity))); assertThrows(RollbackException.class, () -> jpaTm().transact(() -> jpaTm().saveNew(theEntity)));
} }
@ -164,7 +165,7 @@ public class JpaTransactionManagerImplTest {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(compoundIdEntity))).isFalse(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(compoundIdEntity))).isFalse();
jpaTm().transact(() -> jpaTm().saveNew(compoundIdEntity)); jpaTm().transact(() -> jpaTm().saveNew(compoundIdEntity));
assertThat(jpaTm().transact(() -> jpaTm().checkExists(compoundIdEntity))).isTrue(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(compoundIdEntity))).isTrue();
assertThat(jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey).get())) assertThat(jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey)))
.isEqualTo(compoundIdEntity); .isEqualTo(compoundIdEntity);
} }
@ -196,17 +197,17 @@ public class JpaTransactionManagerImplTest {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
jpaTm().transact(() -> jpaTm().saveNewOrUpdate(theEntity)); jpaTm().transact(() -> jpaTm().saveNewOrUpdate(theEntity));
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isTrue(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isTrue();
assertThat(jpaTm().transact(() -> jpaTm().load(theEntityKey).get())).isEqualTo(theEntity); assertThat(jpaTm().transact(() -> jpaTm().load(theEntityKey))).isEqualTo(theEntity);
} }
@Test @Test
public void saveNewOrUpdate_updatesExistingEntity() { public void saveNewOrUpdate_updatesExistingEntity() {
jpaTm().transact(() -> jpaTm().saveNew(theEntity)); jpaTm().transact(() -> jpaTm().saveNew(theEntity));
TestEntity persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey)).get(); TestEntity persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey));
assertThat(persisted.data).isEqualTo("foo"); assertThat(persisted.data).isEqualTo("foo");
theEntity.data = "bar"; theEntity.data = "bar";
jpaTm().transact(() -> jpaTm().saveNewOrUpdate(theEntity)); jpaTm().transact(() -> jpaTm().saveNewOrUpdate(theEntity));
persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey)).get(); persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey));
assertThat(persisted.data).isEqualTo("bar"); assertThat(persisted.data).isEqualTo("bar");
} }
@ -225,23 +226,22 @@ public class JpaTransactionManagerImplTest {
public void update_succeeds() { public void update_succeeds() {
jpaTm().transact(() -> jpaTm().saveNew(theEntity)); jpaTm().transact(() -> jpaTm().saveNew(theEntity));
TestEntity persisted = TestEntity persisted =
jpaTm().transact(() -> jpaTm().load(VKey.createSql(TestEntity.class, "theEntity"))).get(); jpaTm().transact(() -> jpaTm().load(VKey.createSql(TestEntity.class, "theEntity")));
assertThat(persisted.data).isEqualTo("foo"); assertThat(persisted.data).isEqualTo("foo");
theEntity.data = "bar"; theEntity.data = "bar";
jpaTm().transact(() -> jpaTm().update(theEntity)); jpaTm().transact(() -> jpaTm().update(theEntity));
persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey)).get(); persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey));
assertThat(persisted.data).isEqualTo("bar"); assertThat(persisted.data).isEqualTo("bar");
} }
@Test @Test
public void updateCompoundIdEntity_succeeds() { public void updateCompoundIdEntity_succeeds() {
jpaTm().transact(() -> jpaTm().saveNew(compoundIdEntity)); jpaTm().transact(() -> jpaTm().saveNew(compoundIdEntity));
TestCompoundIdEntity persisted = TestCompoundIdEntity persisted = jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey));
jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey)).get();
assertThat(persisted.data).isEqualTo("foo"); assertThat(persisted.data).isEqualTo("foo");
compoundIdEntity.data = "bar"; compoundIdEntity.data = "bar";
jpaTm().transact(() -> jpaTm().update(compoundIdEntity)); jpaTm().transact(() -> jpaTm().update(compoundIdEntity));
persisted = jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey).get()); persisted = jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey));
assertThat(persisted.data).isEqualTo("bar"); assertThat(persisted.data).isEqualTo("bar");
} }
@ -285,17 +285,38 @@ public class JpaTransactionManagerImplTest {
public void load_succeeds() { public void load_succeeds() {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
jpaTm().transact(() -> jpaTm().saveNew(theEntity)); jpaTm().transact(() -> jpaTm().saveNew(theEntity));
TestEntity persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey)).get(); TestEntity persisted = jpaTm().transact(() -> jpaTm().load(theEntityKey));
assertThat(persisted.name).isEqualTo("theEntity"); assertThat(persisted.name).isEqualTo("theEntity");
assertThat(persisted.data).isEqualTo("foo"); assertThat(persisted.data).isEqualTo("foo");
} }
@Test
public void load_throwsOnMissingElement() {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
assertThrows(
NoSuchElementException.class, () -> jpaTm().transact(() -> jpaTm().load(theEntityKey)));
}
@Test
public void maybeLoad_succeeds() {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
jpaTm().transact(() -> jpaTm().saveNew(theEntity));
TestEntity persisted = jpaTm().transact(() -> jpaTm().maybeLoad(theEntityKey).get());
assertThat(persisted.name).isEqualTo("theEntity");
assertThat(persisted.data).isEqualTo("foo");
}
@Test
public void maybeLoad_nonExistentObject() {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
assertThat(jpaTm().transact(() -> jpaTm().maybeLoad(theEntityKey)).isPresent()).isFalse();
}
@Test @Test
public void loadCompoundIdEntity_succeeds() { public void loadCompoundIdEntity_succeeds() {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(compoundIdEntity))).isFalse(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(compoundIdEntity))).isFalse();
jpaTm().transact(() -> jpaTm().saveNew(compoundIdEntity)); jpaTm().transact(() -> jpaTm().saveNew(compoundIdEntity));
TestCompoundIdEntity persisted = TestCompoundIdEntity persisted = jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey));
jpaTm().transact(() -> jpaTm().load(compoundIdEntityKey)).get();
assertThat(persisted.name).isEqualTo("compoundIdEntity"); assertThat(persisted.name).isEqualTo("compoundIdEntity");
assertThat(persisted.age).isEqualTo(10); assertThat(persisted.age).isEqualTo(10);
assertThat(persisted.data).isEqualTo("foo"); assertThat(persisted.data).isEqualTo("foo");

View file

@ -78,7 +78,7 @@ public class RegistrarDaoTest {
@Test @Test
public void update_worksSuccessfully() { public void update_worksSuccessfully() {
jpaTm().transact(() -> jpaTm().saveNew(testRegistrar)); jpaTm().transact(() -> jpaTm().saveNew(testRegistrar));
Registrar persisted = jpaTm().transact(() -> jpaTm().load(registrarKey)).get(); Registrar persisted = jpaTm().transact(() -> jpaTm().load(registrarKey));
assertThat(persisted.getRegistrarName()).isEqualTo("registrarName"); assertThat(persisted.getRegistrarName()).isEqualTo("registrarName");
jpaTm() jpaTm()
.transact( .transact(
@ -86,7 +86,7 @@ public class RegistrarDaoTest {
jpaTm() jpaTm()
.update( .update(
persisted.asBuilder().setRegistrarName("changedRegistrarName").build())); persisted.asBuilder().setRegistrarName("changedRegistrarName").build()));
Registrar updated = jpaTm().transact(() -> jpaTm().load(registrarKey)).get(); Registrar updated = jpaTm().transact(() -> jpaTm().load(registrarKey));
assertThat(updated.getRegistrarName()).isEqualTo("changedRegistrarName"); assertThat(updated.getRegistrarName()).isEqualTo("changedRegistrarName");
} }
@ -102,7 +102,7 @@ public class RegistrarDaoTest {
public void load_worksSuccessfully() { public void load_worksSuccessfully() {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(testRegistrar))).isFalse(); assertThat(jpaTm().transact(() -> jpaTm().checkExists(testRegistrar))).isFalse();
jpaTm().transact(() -> jpaTm().saveNew(testRegistrar)); jpaTm().transact(() -> jpaTm().saveNew(testRegistrar));
Registrar persisted = jpaTm().transact(() -> jpaTm().load(registrarKey)).get(); Registrar persisted = jpaTm().transact(() -> jpaTm().load(registrarKey));
assertThat(persisted.getClientId()).isEqualTo("registrarId"); assertThat(persisted.getClientId()).isEqualTo("registrarId");
assertThat(persisted.getRegistrarName()).isEqualTo("registrarName"); assertThat(persisted.getRegistrarName()).isEqualTo("registrarName");

View file

@ -53,7 +53,6 @@ public class UpdateRegistrarCommandTest extends CommandTestCase<UpdateRegistrarC
assertThat( assertThat(
jpaTm() jpaTm()
.transact(() -> jpaTm().load(VKey.createSql(Registrar.class, "NewRegistrar"))) .transact(() -> jpaTm().load(VKey.createSql(Registrar.class, "NewRegistrar")))
.get()
.verifyPassword("some_password")) .verifyPassword("some_password"))
.isTrue(); .isTrue();
} }