mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.quarkus.panache.common.exception;
|
||||
|
||||
public class PanacheQueryException extends RuntimeException {
|
||||
public PanacheQueryException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user