JPA eclipselink global batch fetch? - jpa

when joining I get one select per row. Solution is batch fetch but I dont want that annotation everywhere...
http://eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_batchfetch.htm
Why do I even need this? One select per row is awful... How can I set this globally? Cheers

Maybe not the ideal solution, but you may try to use JPA hints along with Java generics:
public <T> TypedQuery<T>
createBatchQuery(String ql, Class<T> clazz, String type, String size, String relation) {
return em.createQuery(jpql, clazz)
.setHint(QueryHints.BATCH_TYPE, type)
.setHint(QueryHints.BATCH_SIZE, size)
.setHint(QueryHints.BATCH, relation);
}
The above query may then be used globally and extended with concrete query implementations according to you needs, i.e.
String jpql = "SELECT c FROM Country c WHERE c.name = :name"; // or #NamedQuery
TypedQuery<Country> q = createBatchQuery(jpql, Country.class, "JOIN", "64", "c.cities");
q.setParameter("name", "Australia");
Country c = q.getSingleResult();
Articles on this topic:
Batch fetching - optimizing object graph loading
EclipseLink/Examples/JPA/QueryOptimization

Related

Spring #QuerydslPredicate and QuerydslBinderCustomizer: is it possible to infuse default criteria into predicate generated from request params?

I am using Spring Data JPA and QueryDsl (v.4.2.2), Java 8. I can explicitly construct search predicates and pass them to the repository methods. However, I like the idea of using the #QuerydslPredicate annotation on a web/REST controller's method argument when the queried entities have more than a few properties, and I want the flexibility of filtering the search by any of them. So, something like this, generally, works very well:
#GetMapping("/accounts/summaries")
public PageDto<AccountSummaryDto> getAccountSummaries(#QuerydslPredicate(root = AccountSummary.class) Predicate accountSearchPredicate,
#RequestParam(name = "pageIndex", defaultValue = "0") int pageIndex,
#RequestParam(name = "pageSize", defaultValue = "25") int pageSize,
#RequestParam(name = "sortBy", defaultValue = "id") String sortBy,
#RequestParam(name = "sortOrder", defaultValue = "desc") String sortOrder) {
// delegating to web-agnostic service that:
// - creates Pageable pageRequest,
// - calls accountSummaryRepository.findAll(predicate, pageRequest),
// - constructs custom PageDto wrapper, etc.
return accountService.retrieveAccountSummaries(accountSearchPredicate, pageIndex, pageSize, sortBy, sortOrder);
}
My Spring Data JPA repository interface looks similar to this:
public interface AccountSummarySearchRepository
extends JpaRepository<AccountSummary, Integer>, QuerydslPredicateExecutor<AccountSummary>, QuerydslBinderCustomizer<QAccountSummary > {
#Override
default void customize(QuerydslBindings bindings, QAccountSummary acctSummary) {
bindings.bind(acctSummary.customer.firstName).first((path, value) -> path.isNull().or(path.startsWithIgnoreCase(value))) ;
bindings.bind(acctSummary.customer.lastName).first((path, value) -> path.isNull().or(path.startsWithIgnoreCase(value))) ;
// etc.
// default binding for String properties to be case insensitive "contains" match
bindings.bind(String.class).first(
(StringPath path, String value) -> path.isNull().or(path.containsIgnoreCase(value)));
}
My question:
The bindings in the customize method are set using the entity field
paths and the values of the request parameters that match those
paths. If the parameter is not specified, is there a way to bind the
path to some constant value or a value obtained dynamically?
For example, I want to always ONLY retrieve the entities where property deleted is set to false - without forcing the client to pass that as a query parameter? Similarly, I may want to set other default lookup values dynamically for each query. For example, I may want to "retrieve only those accounts where assignedTo == [current user ID available on a ThreadLocal]...
The following will not work
bindings.bind(acctSummary.deleted).first((path, value) -> path.eq(false));
because it, obviously, expects the first occurrence of the path/value pair for deleted=... in the Predicate (mapped from the incoming request params via the #QuerydslPredicate annotation. I don't want to pass that as a parameter because the requester does not even need to know about the existence of such field.
Is there a simple way to infuse the Predicate instance that is auto-populated via the #QuerydslPredicate annotation with any additional implicit/default criteria that are not explicitly passed in the web request? Could this be done in the customize method? I suppose, one (very ugly) way would be to intercept the HTTP request in a filter - before it is processed by the Spring-QueryDsl framework - and replace it with a new request with added parameters? That would be a horrible solution, and I feel there has to be a better way to do it via some hook/capability provided by the framework itself.
Unfortunately, there seem to be no comprehensive documentation for Spring QueryDsl support - other than some very simplistic examples.
Thanks for your help!
Answering my own question... I was hoping to find a hook in the framework where I could add the code to enhance the auto-generated predicate with criteria common for all my queries - before it arrives in the controller method, but wasn’t able to figure that out. Overriding QuerydslPredicateArgumentResolver doesn't seem a good or necessary option. And, quite frankly, I've come to the conclusion that this wasn't such a great idea to begin with. It seems that any modifications to the search criteria should be done in a more obvious way - in the business tier. So I decided to simply update the predicate in the service method:
public PageDto<AccountSummaryDto> retrieveByPredicate(Predicate predicate, int pageIndex, int pageSize, String sortBy, String sortOrder) {
Pageable pageRequest = PageRequest.of(pageIndex, pageSize, Sort.Direction.fromString(sortOrder), sortBy);
QAccountSummary accountSummary = QAccountSummary.accountSummary; //QueryDsl auto-generated query type for AccountSummary (path root)
// construct new enhanced search predicate w/added criteria common for all queries
// using original predicate generated by framework from request params as base
BooleanBuilder updatedPredicate = new BooleanBuilder(predicate)
.and(accountSummary.somethingNested.id.eq(SomeThreadContext.getSomethingId()))
.and(accountSummary.deleted.eq(false))
.and(accountSummary.someProperty.eq("xyz"));
Page<accountSummary> page = summarySearchRepository.findAll(updatedPredicate, pageRequest);
return toAccountSummaryPageDto(page); // custom method that converts results to page DTO w/entity dots and page stats
}
The construction of the updated predicate may be extracted into a separate private method on the service should it be desirable to use it in multiple search methods and/or if more logic is required to dynamically generate additional search criteria.

How to find top N elements in Spring Data Jpa?

In Spring Data Jpa to get first 10 rows I can do this findTop10By...(). In my case the number or rows is not defined and comes as a parameter.
Is there something like findTopNBy...(int countOfRowsToGet)?
Here is another way without native query. I added Pageable as a parameter to the method in the interface.
findAllBySomeField(..., Pageable pageable)
I call it like this:
findAllBySomeField(..., PageRequest.of(0, limit)) // get first N rows
findAllBySomeField(..., Pageable.unpaged()) // get all rows
I don't know of a way to do exactly what you want, but if you are open to using #Query in your JPA repository class, then a prepared statement is one alternative:
#Query("SELECT * FROM Entity e ORDER BY e.id LIMIT :limit", nativeQuery=true)
Entity getEntitiesByLimit(#Param("limit") int limit);
Did it by using pagination, as described in the first answer. Just adding a more explicit example.
This example will give you the first 50 records ordered by id.
Repository:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, String> {
Page<MyEntity> findAll(Pageable pageable);
}
Service:
#Service
public class MyDataService {
#Autowired
MyRepository myRepository;
private static final int LIMIT = 50;
public Optional<List<MyEntity>> getAllLimited() {
Page<MyEntity> page = myRepository.findAll(PageRequest.of(0, LIMIT, Sort.by(Sort.Order.asc("id"))));
return Optional.of(page.getContent());
}
}
Found the original idea here:
https://itqna.net/questions/16074/spring-data-jpa-does-not-recognize-sql-limit-command
(which will also link to another SO question btw)

Spring Data JPA: Work with Pageable but with a specific set of fields of the entity

I am working with Spring Data 2.0.6.RELEASE.
I am working about pagination for performance and presentation purposes.
Here about performance I am talking about that if we have a lot of records is better show them through pages
I have the following and works fine:
interface PersonaDataJpaCrudRepository extends PagingAndSortingRepository<Persona, String> {
}
The #Controller works fine with:
#GetMapping(produces=MediaType.TEXT_HTML_VALUE)
public String findAll(Pageable pageable, Model model){
Through Thymeleaf I am able to apply pagination. Therefore until here the goal has been accomplished.
Note: The Persona class is annotated with JPA (#Entity, Id, etc)
Now I am concerned about the following: even when pagination works in Spring Data about the amount the records, what about of the content of each record?.
I mean: let's assume that Persona class contains 20 fields (consider any entity you want for your app), thus for a view based in html where a report only uses 4 fields (id, firstname, lastname, date), thus we have 16 unnecessary fields for each entity in memory
I have tried the following:
interface PersonaDataJpaCrudRepository extends PagingAndSortingRepository<Persona, String> {
#Query("SELECT p.id, id.nombre, id.apellido, id.fecha FROM Persona p")
#Override
Page<Persona> findAll(Pageable pageable);
}
If I do a simple print in the #Controller it fails about the following:
java.lang.ClassCastException:
[Ljava.lang.Object; cannot be cast to com.manuel.jordan.domain.Persona
If I avoid that the view fails with:
Caused by:
org.springframework.expression.spel.SpelEvaluationException:
EL1008E:
Property or field 'id' cannot be found on object of type
'java.lang.Object[]' - maybe not public or not valid?
I have read many posts in SO such as:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to
I understand the answer and I am agree about the Object[] return type because I am working with specific set of fields.
Is mandatory work with the complete set of fields for each entity? Should I simply accept the cost of memory about the 16 fields in this case that never are used? It for each record retrieved?
Is there a solution to work around with a specific set of fields or Object[] with the current API of Spring Data?
Have a look at Spring data Projections. For example, interface-based projections may be used to expose certain attributes through specific getter methods.
Interface:
interface PersonaSubset {
long getId();
String getNombre();
String getApellido();
String getFecha();
}
Repository method:
Page<PersonaSubset> findAll(Pageable pageable);
If you only want to read a specific set of columns you don't need to fetch the whole entity. Create a class containing requested columns - for example:
public class PersonBasicData {
private String firstName;
private String lastName;
public PersonBasicData(String firstName, String lastName) {
this.firstName = fistName;
this.lastName = lastName;
}
// getters and setters if needed
}
Then you can specify query using #Query annotation on repository method using constructor expression like this:
#Query("SELECT NEW some.package.PersonBasicData(p.firstName, p.lastName) FROM Person AS p")
You could also use Criteria API to get it done programatically:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<PersonBasicData> query = cb.createQuery(PersonBasicData.class);
Root<Person> person = query.from(Person.class);
query.multiselect(person.get("firstName"), person.get("lastName"));
List<PersonBasicData> results = entityManager.createQuery(query).getResultList();
Be aware that instance of PersonBasicData being created just for read purposes - you won't be able to make changes to it and persist those back in your database as the class is not marked as entity and thus your JPA provider will not work with it.

Spring Data map results

I use Spring Data and I can't find a way to map a #Query results into a DTO. E.g on the query
#Query("select f.a f.b from Foo f")
List<FooStripped> find();
where
public class FooStripped {
String a;
String b;
...
}
I want all the results to be mapped to a list of FooStripped an Object[] is returned.
You can see an example for this in Is there a way to transform objects that spring data repositories return?
But I would not do that to be honest. I would advice you do to this manually in the service/controller level. You can use for this the following framework http://modelmapper.org/ or any other object mapping framework.

what is better: getSingleResult, or getResultList JPA

i need to retrieve single row from table, and i was interested what approach is better.
On the one side getSingleResult is designed for retrieving single result, but it raises exception. Does this method have benefit in performance related to getResultList with
query.setFirstResult(0);
query.setMaxResults(1);
According to Effective Java by Joshua Bloch:
Use checked exceptions for conditions from wich the caller can
reasonably be expected to recover. Use runtime exceptions to indicate
programming errors.
Credit to the source: Why you should never use getSingleResult() in JPA
#Entity
#NamedQuery(name = "Country.findByName",
query = "SELECT c FROM Country c WHERE c.name = :name"
public class Country {
#PersistenceContext
transient EntityManager entityManager;
public static Country findByName(String name) {
List<Country> results = entityManager
.createNamedQuery("Country.findByName", Country.class)
.setParameter("name", name).getResultList();
return results.isEmpty() ? null : results.get(0);
}
}
getSingleResult throws NonUniqueResultException, if there are multiple rows. It is designed to retrieve single result when there is truly a single result.
The way you did is fine and JPA is designed to handle this properly. At the same time, you cannot compare it against getSingleResult any way, since it won't work.
However, depend on the code you are working on, it is always better to refine the query to return single result, if that's all what you want - then you can just call getSingleResult.
There is an alternative which I would recommend:
Query query = em.createQuery("your query");
List<Element> elementList = query.getResultList();
return CollectionUtils.isEmpty(elementList ) ? null : elementList.get(0);
This safeguards against Null Pointer Exception, guarantees only 1 result is returned.
getSingleResult throws NonUniqueResultException, if there are multiple rows or no any rows . It is designed to retrieve single result when there is truly a single result.
In combination with fetch() the usage of setMaxResults(1) can lead to a partially initialised objects. For example,
CriteriaQuery<Individual> query = cb.createQuery(Individual.class);
Root<Individual> root = query.from(Individual.class);
root.fetch(Individual_.contacts);
query.where(cb.equal(root.get(Individual_.id), id));
Individual i = em.createQuery(query)
.setMaxResults(1) // assertion fails if individual has 2 contacts
.getResultList()
.get(0);
assertEquals(2, i.getContacts().size());
So, I am using getResultList() without limit -- a bit unsatisfying.