jpa pass reserved words in #Param String - spring

My goal is to write a query like:
select * from Book where author = any(select author from book)
and(genre='comedy') order by ( '' )ASC, ( pages )DESC;
Where 'any(select author from book)' loses the single quotes so I can pass it thus
#Query("select b from Book b where b.author =:author and b.genre =:genre")
List<Book> findAllBySearch(#Param("author") String author,
#Param("genre") String genre);
The reason for doing this is because I have a form with 5 input criteria that may or may not be present and I don't want to write a separate query for each permutation. I know that one query with either the 'stringInput' or 'any(select criteria from book)' in case or null or empty string inserted before running the query.
I suppose using criteria API or something like that would allow building dynamic Query or inserting reserved sql words but I don't know how to implement it easily since I'm extending Spring data CrudRepository not using entitymanager...yet.... I suppose I will have to.
Does anyone know how to escape the '' imposed by string input #Param or what approach would easily work... such as Criteria API, Stored Procedure, Function ?
Please excuse my inexperience here...
Thanks !
It looks like the only way to do this with 5 possible inputs is to have the runtime strategy choose from 14 different queries depending on what inputs are present but I was trying to avoid this !!

So... here's the solution !
/**
* A JPA-based implementation of the Book Service. Delegates to a JPA entity
* manager to issue data access calls against the backing repository. The
* EntityManager reference is provided by the managing container (Spring)
* automatically.
*/
#Service("bookService")
#Repository
public class JpaBookService implements BookService {
private EntityManager em;
#PersistenceContext
public void setEntityManager(EntityManager em) {
this.em = em;
}
public List<Book> searchBooks(String author, String genre, String pages, String year, String rating){
List<Predicate> predList = new LinkedList<Predicate>();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> c =cb.createQuery(Book.class);
Root<Book> b = c.from(Book.class);
if(author!="")
predList.add(cb.equal(b.get("author"), author));
}
if(genre!=""){
predList.add(cb.equal(b.get("genre"), genre));
}
if(pages!=""){
predList.add(cb.equal(b.get("pages"), pages));
}
if(year!=""){
predList.add(cb.equal(b.get("year"), year));
}
if(rating!=""){
predList.add(cb.equal(b.get("rating"), rating));
}
Predicate[] predArray = new Predicate[predList.size()];
predList.toArray(predArray);
c.where(predArray);
TypedQuery<Book> q = em.createQuery(c);
List<Book> result = q.getResultList();
return result;
}
}
Works good !
Johnny O.

Related

Dynamic JPA query

I have two entities Questions and UserAnswers. I need to make an api in spring boot which returns all the columns from both the entities based on some conditions.
Conditions are:
I will be give a comparator eg: >, <, =, >=, <=
A column name eg: last_answered_at, last_seen_at
A value of the above column eg: 28-09-2020 06:00:18
I will need to return an inner join of the two entities and filter based on the above conditions.
Sample sql query based on above conditions will be like:
SELECT q,ua from questions q INNER JOIN
user_answers ua on q.id = ua.question_id
WHERE ua.last_answered_at > 28-09-2020 06:00:18
The problem I am facing is that the column name and the comparator for the query needs to be dynamic.
Is there an efficient way to do this using spring boot and JPA as I do not want to make jpa query methods for all possible combinations of columns and operators as it can be a very large number and there will be extensive use of if else?
I have developed a library called spring-dynamic-jpa to make it easier to implement dynamic queries with JPA.
You can use it to write the query templates. The query template will be built into different query strings before execution depending on your parameters when you invoke the method.
This sounds like a clear custom implementation of a repository method. Firstly, I will make some assumptions about the implementation of your entities. Afterwards, I will present an idea on how to solve your challenge.
I assume that the entities look basically like this (getters, setters, equals, hachCode... ignored).
#Entity
#Table(name = "questions")
public class Question {
#Id
#GeneratedValue
private Long id;
private LocalDateTime lastAnsweredAt;
private LocalDateTime lastSeenAt;
// other attributes you mentioned...
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserAnswer> userAnswers = new ArrayList();
// Add and remove methods added to keep bidirectional relationship synchronised
public void addUserAnswer(UserAnswer userAnswer) {
userAnswers.add(userAnswer);
userAnswer.setQuestion(this);
}
public void removeUserAnswer(UserAnswer userAnswer) {
userAnswers.remove(userAnswer);
userAnswer.setQuestion(null);
}
}
#Entity
#Table(name = "user_answers")
public class UserAnswer {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "task_release_id")
private Question question;
}
I will write the code with the knowledge about the JPA of Hibernate. For other JPAs, it might work similarly or the same.
Hibernate often needs the name of attributes as a String. To circumvent the issue of undetected mistakes (especially when refactoring), I suggest the module hibernate-jpamodelgen (see the class names suffixed with an underscore). You can also use it to pass the names of the attributes as arguments to your repository method.
Repository methods try to communicate with the database. In JPA, there are different ways of implementing database requests: JPQL as a query language and the Criteria API (easier to refactor, less error prone). As I am a fan of the Criteria API, I will use the Criteria API together with the modelgen to tell the ORM Hibernate to talk to the database to retrieve the relevant objects.
public class QuestionRepositoryCustomImpl implements QuestionRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Question> dynamicFind(String comparator, String attribute, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Question> cq = cb.createQuery(Question.class);
// Root gets constructed for first, main class in the request (see return of method)
Root<Question> root = cq.from(Question.class);
// Join happens based on respective attribute within root
root.join(Question_.USER_ANSWER);
// The following ifs are not the nicest solution.
// The ifs check what comparator String contains and adds respective where clause to query
// This .where() is like WHERE in SQL
if("==".equals(comparator)) {
cq.where(cb.equal(root.get(attribute), value));
}
if(">".equals(comparator)) {
cq.where(cb.gt(root.get(attribute), value));
}
if(">=".equals(comparator)) {
cq.where(cb.ge(root.get(attribute), value));
}
if("<".equals(comparator)) {
cq.where(cb.lt(root.get(attribute), value));
}
if("<=".equals(comparator)) {
cq.where(cb.le(root.get(attribute), value));
}
// Finally, query gets created and result collected and returned as List
// Hint for READ_ONLY is added as lists are often just for read and performance is better.
return entityManager.createQuery(cq).setHint(QueryHints.READ_ONLY, true).getResultList();
}
}

Replacing entire contents of spring-data Page, while maintaining paging info

Using spring-data-jpa and working on getting data out of table where there are about a dozen columns which are used in queries to find particular rows, and then a payload column of clob type which contains the actual data that is marshalled into java objects to be returned.
Entity object very roughly would be something like
#Entity
#Table(name = "Person")
public class Person {
#Column(name="PERSON_ID", length=45) #Id private String personId;
#Column(name="NAME", length=45) private String name;
#Column(name="ADDRESS", length=45) private String address;
#Column(name="PAYLOAD") #Lob private String payload;
//Bunch of other stuff
}
(Whether this approach is sensible or not is a topic for a different discussion)
The clob column causes performance to suffer on large queries ...
In an attempt to improve things a bit, I've created a separate entity object ... sans payload ...
#Entity
#Table(name = "Person")
public class NotQuiteAWholePerson {
#Column(name="PERSON_ID", length=45) #Id private String personId;
#Column(name="NAME", length=45) private String name;
#Column(name="ADDRESS", length=45) private String address;
//Bunch of other stuff
}
This gets me a page of NotQuiteAPerson ... I then query for the page of full person objects via the personIds.
The hope is that in not using the payload in the original query, which could filtering data over a good bit of the backing table, I only concern myself with the payload when I'm retrieving the current page of objects to be viewed ... a much smaller chunk.
So I'm at the point where I want to map the contents of the original returned Page of NotQuiteAWholePerson to my List of Person, while keeping all the Paging info intact, the map method however only takes a Converter which will iterate over the NotQuiteAWholePerson objects ... which doesn't quite fit what I'm trying to do.
Is there a sensible way to achieve this ?
Additional clarification for #itsallas as to why existing map() will not suffice..
PageImpl::map has
#Override
public <S> Page<S> map(Converter<? super T, ? extends S> converter) {
return new PageImpl<S>(getConvertedContent(converter), pageable, total);
}
Chunk::getConvertedContent has
protected <S> List<S> getConvertedContent(Converter<? super T, ? extends S> converter) {
Assert.notNull(converter, "Converter must not be null!");
List<S> result = new ArrayList<S>(content.size());
for (T element : this) {
result.add(converter.convert(element));
}
return result;
}
So the original List of contents is iterated through ... and a supplied convert method applied, to build a new list of contents to be inserted into the existing Pageable.
However I cannot convert a NotQuiteAWholePerson to a Person individually, as I cannot simply construct the payload... well I could, if I called out to the DB for each Person by Id in the convert... but calling out individually is not ideal from a performance perspective ...
After getting my Page of NotQuiteAWholePerson I am querying for the entire List of Person ... by Id ... in one call ... and now I am looking for a way to substitute the entire content list ... not interively, as the existing map() does, but in a simple replacement.
This particular use case would also assist where the payload, which is json, is more appropriately persisted in a NoSql datastore like Mongo ... as opposed to the sql datastore clob ...
Hope that clarifies it a bit better.
You can avoid the problem entirely with Spring Data JPA features.
The most sensible way would be to use Spring Data JPA projections, which have good extensive documentation.
For example, you would first need to ensure lazy fetching for your attribute, which you can achieve with an annotation on the attribute itself.
i.e. :
#Basic(fetch = FetchType.LAZY) #Column(name="PAYLOAD") #Lob private String payload;
or through Fetch/Load Graphs, which are neatly supported at repository-level.
You need to define this one way or another, because, as taken verbatim from the docs :
The query execution engine creates proxy instances of that interface at runtime for each element returned and forwards calls to the exposed methods to the target object.
You can then define a projection like so :
interface NotQuiteAWholePerson {
String getPersonId();
String getName();
String getAddress();
//Bunch of other stuff
}
And add a query method to your repository :
interface PersonRepository extends Repository<Person, String> {
Page<NotQuiteAWholePerson> findAll(Pageable pageable);
// or its dynamic equivalent
<T> Page<T> findAll(Pageable pageable, Class<T>);
}
Given the same pageable, a page of projections would refer back to the same entities in the same session.
If you cannot use projections for whatever reason (namely if you're using JPA < 2.1 or a version of Spring Data JPA before projections), you could define an explicit JPQL query with the columns and relationships you want, or keep the 2-entity setup. You could then map Persons and NotQuiteAWholePersons to a PersonDTO class, either manually or (preferably) using your object mapping framework of choice.
NB. : There are a variety of ways to use and setup lazy/eager relations. This covers more in detail.

Spring JPA Repository Custom Query

This custom query works(this is just a basic query to illustrate the problem):
public interface BookQueryRepositoryExample extends Repository<Book, Long> {
#Query(value = "select * from Book b where b.name=?1", nativeQuery = true)
List<Book> findByName(String name);
}
but I need another custom query where the where clause will be constructed dynamically before calling the method.
public interface BookQueryRepositoryExample extends Repository<Book, Long> {
#Query(value = "select * from Book b where ?1", nativeQuery = true)
List<Book> findByWhatever(String qry);
}
But I am not able to make it work. Is there any workaround?
Updated: 6/16/2017
Just want to mention this that the field I am searching is 'denormalized' form. The values can look like these(below). So my query has a series of like statements
Sample 1:
name:John Smith;address1:123 Xyz St;city:New York;zip:12345;country:USA;id:ABC1234;email:js#abc.com;
Sample 2:Rearranged
address1:123 Xyz St;zip:12345;email:js#abc.com;name:John Smith;country:USA;id:ABC1234;city:New York;
Sample 3:Missing strings/text
zip:12345;email:js#abc.com;name:John Smith;id:ABC1234;city:New York;
This won't work, at least not with this approach.
The placeholders in a query don't just get replaced with some arbitrary String, but are variables, that can only stand in for something you would provide as a literal otherwise.
But as #M. Deinum pointed out there are alternatives: You can write a custom method and use
JPA Criteria API
JPQL
Specifications
QueryDSL
See this article for some examples: https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
For Example If you want to find the Book based on combination of the attribute like authorName,title and cost then You can use the following query
public interface BookQueryRepositoryExample extends Repository<Book, Long>
{
#Query(value = "select * from Book b where (?1 or null) and (?2 or null) and (?3 or null) ",
nativeQuery = true
)
List<Book> findByWhatever(String authorName,String title,Double cost);
}
Work around for this would be like, you can have a class to execute dynamic queries by injecting the EntityManager as shown below:
//Pseudo code
#Repository
public class SomeDao {
#PersistenceContext
private EntityManager entityManager;
public List<Book> findByWhatever(String qry){
Query q = entityManager.createNativeQuery(qry);
List<Object[]> books = q.getResultList();
// Your logic goes here
// return something
}
}
You can create dynamic where clauses using Specification interface that spring-data provides.
Here is a link for you: https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
#Query(value = "select * from Book b where ?1", nativeQuery = true)
List findByWhatever(String qry);
First of all, your approach is not recommended and most likely will lead to SQL injection vulnerability (if you do not handle 'qry' parameter in a proper way).
Secondly, you are trying to reinvent the wheel. There are a lot of possible ways of implementing dynamic queries as #Jens Schauder has already mentioned in his answer. I will add one more way which seems to be the easiest one if you do not need complex stuff. It's called "Query by Example".
public interface BookRepository extends JpaRepository<Book, Long>{
}
Then you create an instance of an object that looks like those that you are trying to find, meaning that you have to set properties that you would use for a dynamic query generation:
Book book = new Book();
book.setYear(2015);
book.setPublisher("O'Realy")
Example<Book> bookExample = Example.of(book);
The last step is to pass your example object to the Spring Data JPA repository:
List<Book> books = bookRepository.findAll(bookExample);
As a result, you will get a list of books published in 2015 by O'Realy. The nice thing about it is that you can add more fields to search for in runtime just by setting it in book instance.
And if you need something more complex than match by exact values, you could use matchers. In the sample below Spring Data JPA will search for all books with a name starting with "O" ignoring case.
Book book = new Book();
book.setName("O")
ExampleMatcher matcher = ExampleMatcher.matching().
.withMatcher("publisher", startsWith().ignoreCase());
Example<Book> bookExample = Example.of(book, matcher);
List<Book> books = bookRepository.findAll(bookExample);

Select fews columns (DTO) with specification JPA

I am using spring-data-jpa version 1.5.1.RELEASE .
My domain is :
public class MyDomain{
....
....
private String prop1;
private String prop2;
......
......
}
My JPA Specification is:
public final class MyDomainSpecs {
public static Specification<MyDomain> search(final String prop1,final String prop2) {
return new Specification<MyDomain>() {
public Predicate toPredicate(Root<MyDomain> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// Some tests if prop1 exist .....
Predicate predicate1 = cb.equal(root.get("prop1"), prop1);
Predicate predicate2 = cb.equal(root.get("prop2"), prop2);
return cb.and(predicate1, predicate2);
}
};
}
}
My Repository :
public interface MyDomainRepository extends JpaRepository<MyDomain, Long>, JpaSpecificationExecutor<MyDomain> {
List<MyDomain> findAll(Specification<MyDomain> spec);
}
All is Working .
But my need (For performance DB tunning) is to not return and select all fields of MyDomain from DB .
I need to select only for example tree properties (prop1, prop2, prop3) , idealy in a DTO Object .
I don't want to convert My List<MyDomain> to List<MyDto> because i am tunning DB request .
So , I don't find any way to do that with spring-data-Jpa and Specification .
Any Idea ?
Thanks
This is not possible as for now. There is a ticket for this but no idea if it will be ever implmented: https://jira.spring.io/browse/DATAJPA-51
Create a special version of MyDomain (e.g. MyDomainSummary or LightMyDomain) that only includes the fields you want to map.
Basic example
Borrowed from the excellent JPA WikiBook.
Assume a JPA entity (i.e. domain class) like so:
#Entity
#Table(name="EMPLOYEE")
public class BasicEmployee {
#Column(name="ID")
private long id;
#Column(name="F_NAME")
private String firstName;
#Column(name="L_NAME")
private String lastName;
// Any un-mapped field will be automatically mapped as basic and column name defaulted.
private BigDecimal salary;
}
The SQL query generated will be similar to
SELECT ID, F_NAME, L_NAME, SALARY FROM EMPLOYEE
if no conditions (where clause) are defined. So, to generalize the basic case one can say that the number of queried columns is equal to the number of mapped fields in your entity. Therefore, the fewer fields your entity, the fewer columns included in the SQL query.
You can have an Employee entity with e.g. 20 fields and a BasicEmployee as above with only 4 fields. Then you create different repositories or different repository methods for both.
Performance considerations
However, I seriously doubt you'll see noticeable performance improvements unless the fields you want to omit represent relationships to other entities. Before you start tweaking here log the SQL that is currently issued against the data base, then remove the columns you want to omit from that SQL, run it again and analyze what you gained.

Join 3 tables in Spring Jpa Data

I've been struggling lately to join 3 tables with spring data jpa. I have 3 entities, Series, Dossier and Item. Series has many Dossiers, and Dossier has many Items (Relationships). I do something like Series.join(Dossier_.series).join(Dossier_.items) and I end up with a Join set. I want to make the following query:
Select Items from Series,Dossier,Item
Where Series.Id=Dossier.seriesId
and Dossier.id=Item.dossierId
and series.projectId = :param
I can't express this statement with Spring Specifications and criteria api....Please shed some light
It is more a JPA question.
First, I always emphasize, you are not access "tables". You should view them as domain entities. Lot of misuse of JPA/Hibernate/other ORMs actually comes from direct "translate" of SQL or database concepts.
Back to your question, the answer is simple. First make sure you actually have the "relationships" in your domain entities. Storing IDs is not helping to build a concrete domain model. For example, you have something like :
#Entity
class Series {
#Id
Long id;
#OneToMany(mappedBy="series")
List<Dossier> dossiers;
}
#Entity
class Dossier{
#Id
Long id;
#ManyToOne
Series series;
#OneToMany(mappedBy="dossier"
List<Item> items;
}
#Entity
class Item{
#Id
Long id;
#ManyToOne
Dossier dossier;
}
The query is straight-forward:
select s.dossiers.items from Series s where s.projectId = :param
Or, if it is more reasonable to have only the #ManyToOnes and omit the #OneToManys, the query is still straight-forward:
from Item where i.dossier.series.projectId = :param
[Still rocky here] Maybe i didn't make myself clear.I know how to express the query in HQL.The problem is to use Spring Data's Specifications,with the help of the criteria api to build that query.
//Let's exampine the following piece of code
public class CustomItemSpecs {
public static Specification<Item> createSpecificationFromSearchForm(final SearchForm searchForm) {
return new Specification<Item>() {
#Override
public Predicate toPredicate(Root<Item> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<Item> cq = cb.createQuery(Item.class);
CriteriaQuery<Series> sb = cb.createQuery(Series.class);
Root<Series> series = sb.from(Series.class);
Join<Series,Dossier> join1 = series.join(Series_.dossiers);
Join<Dossier, Item> join2 = join1.join(Dossier_.items);
}
}
}
As you can see i manage to do two seperate joins.The problem is when i want to join Series,Dossier And Items to execute the above query.Notice that join2 is a Dossier-Item set.I can't make criteria like cb.equals(join2.get(Series_.projectId),)
im using spring data interface projection .
example like this
note items must be interface class
Repository Class
#Repository
public interface ItemRepository extends JpaRepository<Items,Long> {
#Query(nativeQuery = true,value = "Select Items from Series,Dossier,Item Where Series.Id=Dossier.seriesId and Dossier.id=Item.dossierId and series.projectId = :param")
public Items findTransaksisByAccountIdOrderById(#Param("param") Long projectId);
}

Resources