Merge pull request #5616 from loicmathieu/panache/optionally-find

Provides Optional support inside Hibernate with Panache and MongoDB with Panache
This commit is contained in:
Stéphane Épardaud
2019-12-02 15:01:12 +01:00
committed by GitHub
18 changed files with 222 additions and 7 deletions

View File

@@ -161,6 +161,10 @@ List<Person> allPersons = Person.listAll();
// finding a specific person by ID
person = Person.findById(personId);
// finding a specific person by ID via an Optional
Optional<Person> optional = Person.findByIdOptional(personId);
person = optional.orElseThrow(() -> new NotFoundException());
// finding all living persons
List<Person> livingPersons = Person.list("status", Status.Alive);

View File

@@ -196,6 +196,10 @@ List<Person> allPersons = Person.listAll();
// finding a specific person by ID
person = Person.findById(personId);
// finding a specific person by ID via an Optional
Optional<Person> optional = Person.findByIdOptional(personId);
person = optional.orElseThrow(() -> new NotFoundException());
// finding all living persons
List<Person> livingPersons = Person.list("status", Status.Alive);

View File

@@ -2,6 +2,7 @@ package io.quarkus.hibernate.orm.panache;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.json.bind.annotation.JsonbTransient;
@@ -115,6 +116,29 @@ public abstract class PanacheEntityBase {
throw JpaOperations.implementationInjectionMissing();
}
/**
* Find an entity of this type by ID.
*
* @param id the ID of the entity to find.
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
*/
@GenerateBridge
public static <T extends PanacheEntityBase> Optional<T> findByIdOptional(Object id) {
throw JpaOperations.implementationInjectionMissing();
}
/**
* Find an entity of this type by ID.
*
* @param id the ID of the entity to find.
* @param lockModeType the locking strategy to be used when retrieving the entity.
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
*/
@GenerateBridge
public static <T extends PanacheEntityBase> Optional<T> findByIdOptional(Object id, LockModeType lockModeType) {
throw JpaOperations.implementationInjectionMissing();
}
/**
* Find entities using a query, with optional indexed parameters.
*

View File

@@ -1,6 +1,7 @@
package io.quarkus.hibernate.orm.panache;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.LockModeType;
@@ -172,6 +173,15 @@ public interface PanacheQuery<Entity> {
*/
public <T extends Entity> T firstResult();
/**
* Returns the first result of the current page index. This ignores the current page size to fetch
* a single result.
*
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
* @see #singleResultOptional()
*/
public <T extends Entity> Optional<T> firstResultOptional();
/**
* Executes this query for the current page and return a single result.
*
@@ -181,4 +191,13 @@ public interface PanacheQuery<Entity> {
* @see #firstResult()
*/
public <T extends Entity> T singleResult();
/**
* Executes this query for the current page and return a single result.
*
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
* @throws NonUniqueResultException if there are more than one result
* @see #firstResultOptional()
*/
public <T extends Entity> Optional<T> singleResultOptional();
}

View File

@@ -2,6 +2,7 @@ package io.quarkus.hibernate.orm.panache;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.LockModeType;
@@ -113,6 +114,28 @@ public interface PanacheRepositoryBase<Entity, Id> {
throw JpaOperations.implementationInjectionMissing();
}
/**
* Find an entity of this type by ID.
*
* @param id the ID of the entity to find.
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
*/
@GenerateBridge
public default Optional<Entity> findByIdOptional(Id id) {
throw JpaOperations.implementationInjectionMissing();
}
/**
* Find an entity of this type by ID.
*
* @param id the ID of the entity to find.
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
*/
@GenerateBridge
public default Optional<Entity> findByIdOptional(Id id, LockModeType lockModeType) {
throw JpaOperations.implementationInjectionMissing();
}
/**
* Find entities using a query, with optional indexed parameters.
*

View File

@@ -3,6 +3,7 @@ package io.quarkus.hibernate.orm.panache.runtime;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
@@ -201,6 +202,14 @@ public class JpaOperations {
return getEntityManager().find(entityClass, id, lockModeType);
}
public static Optional<?> findByIdOptional(Class<?> entityClass, Object id) {
return Optional.ofNullable(findById(entityClass, id));
}
public static Optional<?> findByIdOptional(Class<?> entityClass, Object id, LockModeType lockModeType) {
return Optional.ofNullable(findById(entityClass, id, lockModeType));
}
public static PanacheQuery<?> find(Class<?> entityClass, String query, Object... params) {
return find(entityClass, query, null, params);
}

View File

@@ -2,10 +2,12 @@ package io.quarkus.hibernate.orm.panache.runtime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;
import io.quarkus.hibernate.orm.panache.PanacheQuery;
@@ -145,10 +147,27 @@ public class PanacheQueryImpl<Entity> implements PanacheQuery<Entity> {
return list.isEmpty() ? null : list.get(0);
}
@Override
public <T extends Entity> Optional<T> firstResultOptional() {
return Optional.ofNullable(firstResult());
}
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> T singleResult() {
jpaQuery.setMaxResults(page.size);
return (T) jpaQuery.getSingleResult();
}
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> Optional<T> singleResultOptional() {
jpaQuery.setMaxResults(2);
List<T> list = jpaQuery.getResultList();
if (list.size() == 2) {
throw new NonUniqueResultException();
}
return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
}
}

View File

@@ -2,6 +2,7 @@ package io.quarkus.mongodb.panache;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.bson.Document;
@@ -85,6 +86,17 @@ public abstract class PanacheMongoEntityBase {
throw MongoOperations.implementationInjectionMissing();
}
/**
* Find an entity of this type by ID.
*
* @param id the ID of the entity to find.
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
*/
@GenerateBridge
public static <T extends PanacheMongoEntityBase> Optional<T> findByIdOptional(Object id) {
throw MongoOperations.implementationInjectionMissing();
}
/**
* Find entities using a query, with optional indexed parameters.
*

View File

@@ -2,6 +2,7 @@ package io.quarkus.mongodb.panache;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.bson.Document;
@@ -91,6 +92,17 @@ public interface PanacheMongoRepositoryBase<Entity, Id> {
throw MongoOperations.implementationInjectionMissing();
}
/**
* Find an entity of this type by ID.
*
* @param id the ID of the entity to find.
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
*/
@GenerateBridge
public default Optional<Entity> findByIdOptional(Id id) {
throw MongoOperations.implementationInjectionMissing();
}
/**
* Find entities using a query, with optional indexed parameters.
*

View File

@@ -1,6 +1,7 @@
package io.quarkus.mongodb.panache;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import io.quarkus.panache.common.Page;
@@ -147,11 +148,30 @@ public interface PanacheQuery<Entity> {
*/
public <T extends Entity> T firstResult();
/**
* Returns the first result of the current page index. This ignores the current page size to fetch
* a single result.
*
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
* @see #singleResultOptional()
*/
public <T extends Entity> Optional<T> firstResultOptional();
/**
* Executes this query for the current page and return a single result.
*
* @return the single result (throws if there is not exactly one)
* @return the single result
* @throws PanacheQueryException if there is not exactly one result.
* @see #firstResult()
*/
public <T extends Entity> T singleResult();
/**
* Executes this query for the current page and return a single result.
*
* @return if found, an optional containing the entity, else <code>Optional.empty()</code>.
* @throws PanacheQueryException if there is more than one result.
* @see #firstResultOptional()
*/
public <T extends Entity> Optional<T> singleResultOptional();
}

View File

@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -282,6 +283,10 @@ public class MongoOperations {
return collection.find(new Document(ID, id)).first();
}
public static Optional findByIdOptional(Class<?> entityClass, Object id) {
return Optional.ofNullable(findById(entityClass, id));
}
public static PanacheQuery<?> find(Class<?> entityClass, String query, Object... params) {
return find(entityClass, query, null, params);
}

View File

@@ -2,6 +2,7 @@ package io.quarkus.mongodb.panache.runtime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.bson.Document;
@@ -12,6 +13,7 @@ import com.mongodb.client.MongoCursor;
import io.quarkus.mongodb.panache.PanacheQuery;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.exception.PanacheQueryException;
public class PanacheQueryImpl<Entity> implements PanacheQuery<Entity> {
private MongoCollection collection;
@@ -133,14 +135,30 @@ public class PanacheQueryImpl<Entity> implements PanacheQuery<Entity> {
return list.isEmpty() ? null : list.get(0);
}
@Override
public <T extends Entity> Optional<T> firstResultOptional() {
return Optional.ofNullable(firstResult());
}
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> T singleResult() {
List<T> list = list();
if (list.isEmpty() || list.size() > 1) {
throw new RuntimeException("There should be only one result");//TODO use proper exception
throw new PanacheQueryException("There should be only one result");
}
return list.get(0);
}
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> Optional<T> singleResultOptional() {
List<T> list = list();
if (list.size() > 1) {
throw new PanacheQueryException("There should be no more than one result");
}
return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
}
}

View File

@@ -0,0 +1,7 @@
package io.quarkus.panache.common.exception;
public class PanacheQueryException extends RuntimeException {
public PanacheQueryException(String s) {
super(s);
}
}

View File

@@ -144,10 +144,18 @@ public class TestEndpoint {
Assertions.assertEquals(person, byId);
Assertions.assertEquals("Person<" + person.id + ">", byId.toString());
byId = Person.<Person> findByIdOptional(person.id).get();
Assertions.assertEquals(person, byId);
Assertions.assertEquals("Person<" + person.id + ">", byId.toString());
byId = Person.findById(person.id, LockModeType.PESSIMISTIC_READ);
Assertions.assertEquals(person, byId);
Assertions.assertEquals("Person<" + person.id + ">", byId.toString());
byId = Person.<Person> findByIdOptional(person.id, LockModeType.PESSIMISTIC_READ).get();
Assertions.assertEquals(person, byId);
Assertions.assertEquals("Person<" + person.id + ">", byId.toString());
person.delete();
Assertions.assertEquals(0, Person.count());
@@ -191,6 +199,8 @@ public class TestEndpoint {
Assertions.assertNotNull(Person.findAll().firstResult());
Assertions.assertNotNull(Person.findAll().firstResultOptional().get());
Assertions.assertEquals(7, Person.deleteAll());
// persistAndFlush
@@ -339,8 +349,12 @@ public class TestEndpoint {
} catch (NoResultException x) {
}
Assertions.assertFalse(personDao.findAll().singleResultOptional().isPresent());
Assertions.assertNull(personDao.findAll().firstResult());
Assertions.assertFalse(personDao.findAll().firstResultOptional().isPresent());
Person person = makeSavedPersonDao();
Assertions.assertNotNull(person.id);
@@ -369,6 +383,7 @@ public class TestEndpoint {
Assertions.assertEquals(person, personDao.findAll().firstResult());
Assertions.assertEquals(person, personDao.findAll().singleResult());
Assertions.assertEquals(person, personDao.findAll().singleResultOptional().get());
persons = personDao.find("name = ?1", "stef").list();
Assertions.assertEquals(1, persons.size());
@@ -419,13 +434,20 @@ public class TestEndpoint {
Assertions.assertEquals(person, personDao.find("name", "stef").firstResult());
Assertions.assertEquals(person, personDao.find("name", "stef").singleResult());
Assertions.assertEquals(person, personDao.find("name", "stef").singleResultOptional().get());
Person byId = personDao.findById(person.id);
Assertions.assertEquals(person, byId);
byId = personDao.findByIdOptional(person.id).get();
Assertions.assertEquals(person, byId);
byId = personDao.findById(person.id, LockModeType.PESSIMISTIC_READ);
Assertions.assertEquals(person, byId);
byId = personDao.findByIdOptional(person.id, LockModeType.PESSIMISTIC_READ).get();
Assertions.assertEquals(person, byId);
personDao.delete(person);
Assertions.assertEquals(0, personDao.count());

View File

@@ -69,6 +69,12 @@ public class BookEntityResource {
return BookEntity.findById(new ObjectId(id));
}
@GET
@Path("/optional/{id}")
public BookEntity getBookOptional(@PathParam("id") String id) {
return BookEntity.<BookEntity> findByIdOptional(new ObjectId(id)).orElseThrow(() -> new NotFoundException());
}
@GET
@Path("/search/{author}")
public List<BookEntity> getBooksByAuthor(@PathParam("author") String author) {
@@ -86,7 +92,7 @@ public class BookEntityResource {
return BookEntity
.find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom),
LocalDate.parse(dateTo))
.firstResult();
.<BookEntity> firstResultOptional().orElseThrow(() -> new NotFoundException());
}
@GET

View File

@@ -72,6 +72,12 @@ public class BookRepositoryResource {
return bookRepository.findById(new ObjectId(id));
}
@GET
@Path("/optional/{id}")
public Book getBookOptional(@PathParam("id") String id) {
return bookRepository.findByIdOptional(new ObjectId(id)).orElseThrow(() -> new NotFoundException());
}
@GET
@Path("/search/{author}")
public List<Book> getBooksByAuthor(@PathParam("author") String author) {
@@ -89,7 +95,7 @@ public class BookRepositoryResource {
return bookRepository
.find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom),
LocalDate.parse(dateTo))
.firstResult();
.firstResultOptional().orElseThrow(() -> new NotFoundException());
}
@GET

View File

@@ -42,8 +42,8 @@ import io.restassured.parsing.Parser;
import io.restassured.response.Response;
@QuarkusTest
class BookResourceTest {
private static final Logger LOGGER = Logger.getLogger(BookResourceTest.class);
class MongodbPanacheResourceTest {
private static final Logger LOGGER = Logger.getLogger(MongodbPanacheResourceTest.class);
private static final TypeRef<List<BookDTO>> LIST_OF_BOOK_TYPE_REF = new TypeRef<List<BookDTO>>() {
};
private static final TypeRef<List<Person>> LIST_OF_PERSON_TYPE_REF = new TypeRef<List<Person>>() {
@@ -195,9 +195,14 @@ class BookResourceTest {
//check that the title has been updated and the transient description ignored
book = get(endpoint + "/" + book.getId().toString()).as(BookDTO.class);
Assertions.assertNotNull(book);
Assertions.assertEquals("Notre-Dame de Paris 2", book.getTitle());
Assertions.assertNull(book.getTransientDescription());
//test findByIdOptional
book = get(endpoint + "/optional/" + book.getId().toString()).as(BookDTO.class);
Assertions.assertNotNull(book);
//delete a book
response = RestAssured
.given()

View File

@@ -3,6 +3,6 @@ package io.quarkus.it.mongodb.panache;
import io.quarkus.test.junit.NativeImageTest;
@NativeImageTest
class NativeBookResourceIT extends BookResourceTest {
class NativeBookResourceIT extends MongodbPanacheResourceTest {
}