Spring JPA Repository Custom Query - spring-boot

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);

Related

How can I return map collections as jpa query

I have a question. when I use spring data jpa, I want to it return Map Collections, but it wrong. Then I search on the internet found a solution. Flowing.
#Transactional(readOnly = true)
public interface GoodsRepository extends JpaRepository<TbGoodsEntity, Integer> {
#Query(value = "select new map(t.id as id, t.goodsName as goodsName) from TbGoodsEntity t group by t.goodsName")
public List<Map<String, Object>> getGoodsNames();// it`s ok,
#Query(value = "select * from tb_goods t group by t.goodsName", nativeQuery = true)
public List<Map<String, Object>> getGoods();//it`s error
}
But I don't think to use new map method its best solution, I`d like to ask if there any other solutions. Thanks.
If use use "native" query then each row will be returned as "List" since spring doesn't have row transformer. So your output becomes List<List<Object>>.
If you try the below query then you should get List<Map<String,Object>>
#Query(value = "select t from tb_goods t group by t.goodsName")
public List<Map<String, Object>> getGoods();
Note: I am guessing your DB column name is "goodsName" so not commenting if query is correct or not.

How can I use Spring's pagination (using Pageable) while writing a dynamic query using QueryDSL?

I am trying to use pagination with QueryDSL - using the com.mysema.querydsl package.
All my Querydsl query types look like this -
#Generated("com.mysema.query.codegen.EntitySerializer")
public class QCountry extends EntityPathBase<Country> {...}
Currently, my repository implementation class looks something like this -
#Override
public Page<Country> findPaginatedCountries(String country, Optional<String> status, Pageable pageable) {
QCountry qCountry= QCountry.someObject;
QActiveCountry qActiveCountry = QActiveCountry.activeCountry;
JPAQuery jpaQuery = new JPAQuery(entityManager);
QueryBase queryBase = jpaQuery.from(qCountry).innerJoin(qActiveCountry).fetch()
.where(qCountry.codeLeft.country.upper().eq(country.toUpperCase()))
.where(qCountry.codeRight.country.upper().eq(country.toUpperCase()));
if(status.isPresent()){
queryBase = queryBase.where(qActiveCountry.id(qCountry.active.id))
.where(qActiveCountry.status.upper().eq(status.get().toUpperCase()));
}
.......}
Now, I want this dynamic query to return a paginated response. I want to use Spring's pagination to do that and not manually set offset, size etc.
I know I can use QueryDslRepositorySupport class - as implemented here - https://github.com/keke77/spring-data-jpa-sample/blob/master/spring-data-jpa/src/main/java/com/gmind7/bakery/employee/EmployeeRepositoryImpl.java
Sample code from the above link -
#Override
public Page<Employees> QFindByOfficeCode(long officeCode, Pageable pageable) {
//JPAQuery query = new JPAQuery(em);
JPQLQuery query = from(QEmployees.employees).where(QEmployees.employees.officeCode.eq(officeCode));
query = super.getQuerydsl().applyPagination(pageable, query);
SearchResults<Employees> entitys = query.listResults(QEmployees.employees);
return new PageImpl<Employees>(entitys.getResults(), pageable, entitys.getTotal());
}
However, to do that -
I need to pass JPQLQuery object to the applyPagination method. How can I do that without changing my code (Ofcourse, the repository class will extend QueryDslRepositorySupport class). Currently, I am using JPAQuery as you can see.
OR
I probably need to change my QueryDSL types by having them extend EntityPath instead of EntityPathBase so that I can use JPQLQuery.from() to generate the query and then use the applyPagination method, which requires a JPQLQuery object. However, my Q classes are extending EntityPathBase class instead. Should I be use com.querydsl package instead of com.mysemsa.querydsl package to generate query types?
OR
Other option is to use the following - http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/querydsl/QueryDslPredicateExecutor.html#findAll-com.querydsl.core.types.Predicate-org.springframework.data.domain.Pageable-
Code snippet below -
Page<T> page = QueryDslPredicateExecutor.findAll(org.springframework.data.querydsl.Predicate predicate, Pageable pageable)
However, I am making joins between two tables and then filtering results with a where clause (as you can see above in my code). How can I pass a predicate object in the findAll method above? Not sure how to include a join in it.
Please let me know if the problem is not clear, I can add more details.
EDIT: There is a many to one relationship between Country and ActiveCountry.
Country class has an ActiveCountry reference. And we have to do a join between both ids. Is is possible that Country can have null ActiveCountry. Therefore, we want an inner join - only non null values for active country
#ManyToOne
#JoinColumn(name="id")
ActiveCountry active;
Step 1: Annotate the entity class with #QueryEntity
#Entity
#QueryEntity
public class Country {}
This seems to have been addressed already since the question shows Q classes.
Step 2: Have the repository interface extend QueryDslPredicateExecutor
public interface CountryRepository
extends PagingAndSortingRepository<Country, Long>
, QueryDslPredicateExecutor<Country> {
}
Step 3: Invoke the Page<T> findAll(Predicate query, Pageable page) method provided by QueryDslPredicateExecutor
public Page<Country> getCountries(String country, Optional<String> status, Pageable page) {
QCountry root = QCountry.country;
BooleanExpression query = root.codeLeft.country.equalsIgnoreCase(country);
query = query.and(root.codeRight.country.equalsIgnoreCase(country));
if (status.isPresent()) {
query = query.and(root.active.status.equalsIgnoreCase(status));
}
return countryRepository.findAll(query, page);
}

jpa pass reserved words in #Param String

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.

Spring Data: "delete by" is supported?

I am using Spring JPA for database access. I am able to find examples such as findByName and countByName, for which I dont have to write any method implementation. I am hoping to find examples for delete a group of records based on some condition.
Does Spring JPA support deleteByName-like delete? Any pointer is appreciated.
Regards and thanks.
Deprecated answer (Spring Data JPA <=1.6.x):
#Modifying annotation to the rescue. You will need to provide your custom SQL behaviour though.
public interface UserRepository extends JpaRepository<User, Long> {
#Modifying
#Query("delete from User u where u.firstName = ?1")
void deleteUsersByFirstName(String firstName);
}
Update:
In modern versions of Spring Data JPA (>=1.7.x) query derivation for delete, remove and count operations is accessible.
public interface UserRepository extends CrudRepository<User, Long> {
Long countByFirstName(String firstName);
Long deleteByFirstName(String firstName);
List<User> removeByFirstName(String firstName);
}
Derivation of delete queries using given method name is supported starting with version 1.6.0.RC1 of Spring Data JPA. The keywords remove and delete are supported. As return value one can choose between the number or a list of removed entities.
Long removeByLastname(String lastname);
List<User> deleteByLastname(String lastname);
2 ways:-
1st one Custom Query
#Modifying
#Query("delete from User where firstName = :firstName")
void deleteUsersByFirstName(#Param("firstName") String firstName);
2nd one JPA Query by method
List<User> deleteByLastname(String lastname);
When you go with query by method (2nd way) it will first do a get call
select * from user where last_name = :firstName
Then it will load it in a List
Then it will call delete id one by one
delete from user where id = 18
delete from user where id = 19
First fetch the list of object, then for loop to delete id one by one
But, the 1st option (custom query),
It's just a single query
It will delete wherever the value exists.
Since in 2nd option it is making multiple DB query, try to use the first option.
Go through this link too https://www.baeldung.com/spring-data-jpa-deleteby
If you take a look at the source code of Spring Data JPA, and particularly the PartTreeJpaQuery class, you will see that is tries to instantiate PartTree.
Inside that class the following regular expression
private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(find|read|get|count|query)(\\p{Lu}.*?)??By")
should indicate what is allowed and what's not.
Of course if you try to add such a method you will actually see that is does not work and you get the full stacktrace.
I should note that I was using looking at version 1.5.0.RELEASE of Spring Data JPA
If you will use pre defined delete methods as directly provided by spring JPA then below two queries will be execute by the framework.
First collect data(like id and other column) using by execute select query with delete query where clause.
then after getting resultSet of first query, second delete queries will be execute for all id(one by one)
Note : This is not optimized way for your application because many queries will be execute for single MYSQL delete query.
This is another optimized way for delete query code because only one delete query will execute by using below customized methods.
#NamedNativeQueries({
#NamedNativeQuery(name = "Abc.deleteByCreatedTimeBetween",
query = "DELETE FROM abc WHERE create_time BETWEEN ?1 AND ?2")
,
#NamedNativeQuery(name = "Abc.getByMaxId",
query = "SELECT max(id) from abc")
})
#Entity
public class Abc implements Serializable {
}
#Repository
public interface AbcRepository extends CrudRepository {
int getByMaxId();
#Transactional
#Modifying
void deleteByCreatedTimeBetween(String startDate, String endDate);
}
It works just
import org.springframework.transaction.annotation.Transactional;
#Transactional
Long removeAddressByCity(String city);
Yes , deleteBy method is supported
To use it you need to annotate method with #Transactional
here follows my 2 cents. You can also use native queries, like:
#Modifying
#Query(value="delete from rreo r where r.cod_ibge = ?1 and r.exercicio= ?2", nativeQuery = true)
void deleteByParameters(Integer codIbge, Integer exercicio);
#Query(value = "delete from addresses u where u.ADDRESS_ID LIKE %:addressId%", nativeQuery = true)
void deleteAddressByAddressId(#Param("addressId") String addressId);

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