query with map parameter in spring data jpa - spring

I am using spring data jpa. I noticed spring data jpa supports spel as query parameter recently. It works when using object property as parameter, but Map didn't work.
it's code snippet below
#Query(value = "select item from BoardItem item " +
"where item.board.id = :#{#search.addition['boardId']} " +
"and ( " +
" item.title like concat('%', :#{#search.value}, '%') " +
" or item.contents like concat('%', :#{#search.value}, '%') " +
" or item.writer like concat('%', :#{#search.value}, '%') " +
")"
)
#Override
Page<BoardItem> findAll(#Param("search") SearchVo searchVo, Pageable pageable);
public class SearchVo {
private String value;
private Boolean regex;
private Map<String, String> addition;
....
new SearchVo().getAddition().put("boardId", "1000");
#{#search.value} works, but #{#search.addition['boardId']} isn't.
Can anyone let me know if it works with using spel like #{#search.addition['boardId']} ?
Thanks in advance.

Related

Adding and removing dynamically SQL WHERE clause using JPA [duplicate]

Is there a way in Spring data to dynamically form the where clause?
What I want to do is have a method (which is like the findBy / get method) which runs a WHERE and AND using the mentioned properties which are NOT NULL.
For example,
Consider the object Person [firstName, lastName, age, gender]
Our method looks something like this
findBy_IfNotNullFirstName_AndIfNotNullLastName_AndIfNotNullAge_AndIfNotNullGender(String firstName, String lastName, Integer age, String gender)
Thanks.
A simpler option is to test if the parameter is null right in the JPQL query:
Exemple from my project:
#Query("select m from MessageEntity m " +
"join fetch m.demandeAnalyseEntities d " +
"where (:patientId is null or d.noPtn= :patientId) " +
" and " +
" ( :labNbr is null or d.noLab= :labNbr) " +
" and " +
" ( :reqDate is null or d.dteReq= :reqDate) " +
" and " +
" ( :reqNum is null or d.noReq= :reqNum) "
)
List<MessageEntity> findMessagesWithDemandesOnly(#Param("patientId") Long pid,
#Param("labNbr") Integer labNo,
#Param("reqDate") String reqDate,
#Param("reqNum") Integer reqNum,
Pageable pageable);
Take a look at JPA Specification and Predicate, and Even better QueryDSL, there both supported by spring data repositories.
This article provide an example:
http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
Another solution: You can extend your JPA repo interface using custom fragment interfaces.
Define your custom methods on a new interface
public interface PersonFragRepository {
List<User> findPersonByWhatever(
String firstName, String lastName, String age, String gender);
}
Provide the implementation
public class PersonFragRepositoryImpl implements PersonFragRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
List<User> findPersonByWhatever(
String firstName, String lastName, String age, String gender) {
...
}
}
Extends your JPA interface
public interface PersonRepository
extends JpaRepository<Person, Integer>, PersonFragRepository

Java Spring MongoDB Custom Filter ignore Null Parameters

I am trying to filter with a custom Expression and multiple fields.
For the name Field i use a regular expression.
And all the other fields i check if they are contained in a List of Parameters.
#Query(value =
"{$and:["
+ "{'name': {$regex: ?0, $options: 'i'}},"
+ "{'types': {$in: ?1}},"
+ "{'customer': {$in: ?2}}"
+ "]}")
Optional<List<Object>> findByFilter(
#Param("name") String name,
#Param("types") Collection<String> types,
#Param("customers") Collection<String> customers,
Pageable pageable);
How can i ignore if a Parameter is null?
I tryed the spring expression language but this is not working.
#Query(value =
"{$and:["
+ "{'name': {$regex: ?0, $options: 'i'}},"
+ "{'types': {$in: ?1}},"
+ "?#{ [2] != null ? {'customer': {$in: [2]}} }"
+ "]}")
Optional<List<Object>> findByFilter(
#Param("name") String name,
#Param("types") Collection<String> types,
#Param("customers") Collection<String> customers,
Pageable pageable);
I'm not sure if it's possible, but recommend you use MongoTemplate which is more flexible than MongoRepository syntax.
#Autowired
private MongoTemplate mongotemplate;
Criteria criteria = Criteria.where("name").regex(name).and("types").in(types);
if (customers != null || !customers.isEmpty()) {
criteria.and("customers").in(customers);
}
List<YourClass> result = mongotemplate.find(Query.query(criteria).with(pageable), YourClass.class);

Annotation Query and return only specific nested field with spring data elasticsearch

Annotation Query and return only specific nested field with spring data elasticsearch
Version:
springboot:2.1.7.RELEASE
spring-data-elasticsearch: 2.1.7.RELEASE
elasticsearch: 6.5.4
document:
#Data
#Document(indexName = "test_book", type = "test_book")
public class Book {
#Id
private String id;
private String name;
private LocalDateTime time;
/**
*
*/
private Publishing publishing;
/**
*
*/
private Author author;
}
Repository:
public interface BookRepository extends ElasticsearchRepository<Book,String> {
#Query("{" +
"\"_source\": {" +
" \"includes\": " +
" [ \"name\"]" +
"}," +
"\"bool\" : {" +
" \"must\" : [{" +
" \"term\" : {" +
" \"id.keyword\" : \"?0\"" +
" }" +
" }]" +
" }" +
"}")
Book queryBookNameById(String id);
}
I just want to get the data of the name, which can relatively save memory resources. But I got an error, can’t I use it like this? Or can only use elasticsearchTemplate?
ParsingException[no [query] registered for [_source]
]
at org.elasticsearch.index.query.AbstractQueryBuilder.parseInnerQueryBuilder(AbstractQueryBuilder.java:332)
at org.elasticsearch.index.query.WrapperQueryBuilder.doRewrite(WrapperQueryBuilder.java:165)
at org.elasticsearch.index.query.AbstractQueryBuilder.rewrite(AbstractQueryBuilder.java:279)
at org.elasticsearch.search.builder.SearchSourceBuilder.rewrite(SearchSourceBuilder.java:921)
at org.elasticsearch.search.builder.SearchSourceBuilder.rewrite(SearchSourceBuilder.java:80)
at org.elasticsearch.index.query.Rewriteable.rewriteAndFetch(Rewriteable.java:97)
at org.elasticsearch.index.query.Rewriteable.rewriteAndFetch(Rewriteable.java:87)
at org.elasticsearch.action.search.TransportSearchAction.doExecute(TransportSearchAction.java:215)
at org.elasticsearch.action.search.TransportSearchAction.doExecute(TransportSearchAction.java:68)
at org.elasticsearch.action.support.TransportAction$RequestFilterChain.proceed(TransportAction.java:167)
at org.elasticsearch.xpack.security.action.filter.SecurityActionFilter.apply(SecurityActionFilter.java:126)
......
This currently does not work with the #Query annotation for repositories. Spring Data Elasticsearch will wrap the value of the annotation as the query value and so includes the _source part into the query.
We would need to add additional parameters to the annotation (includes and excludes) to be able to build a correct query and make this work. I will create an Jira issue for this to keep track of this feature request.
Jira issue

spring boot data #query to DTO

I want to assign the result of a query to a DTO object. The DTO looks like this:
#Getter
#Setter
#NoArgsConstructor
public class Metric {
private int share;
private int shareholder;
public Metric(int share, int shareholder) {
this.share = share;
this.shareholder = shareholder;
}
}
And the query looks like the following:
#RepositoryRestResource(collectionResourceRel = "shareholders", path = "shareholders")
public interface ShareholderRepository extends PagingAndSortingRepository<Shareholder, Integer> {
#Query(value = "SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM shareholders s WHERE s.attend=true")
Metric getMetrics();
}
However, this didn't work, as I got the following exception:
Caused by:org.hibernate.QueryException: could not resolve property: no_of_shares of:com.company.shareholders.sh.Shareholder[SELECT new com.company.shareholders.sh.Metric(SUM(s.no_of_shares),COUNT(*)) FROM com.company.shareholders.sh.Shareholder s WHERE s.attend=true]
In my project I've used projections to this like shown below:
#Repository
public interface PeopleRepository extends JpaRepository<People, Long> {
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
List<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId);
#Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
"FROM people p INNER JOIN dream_people dp " +
"ON p.id = dp.people_id " +
"WHERE p.user_id = :userId " +
"GROUP BY dp.people_id " +
"ORDER BY p.name", nativeQuery = true)
Page<PeopleDTO> findByPeopleAndCountByUserId(#Param("userId") Long userId, Pageable pageable);
}
The interface to which the result is projected:
public interface PeopleDTO {
String getName();
Long getCount();
}
The fields from the projected interface must match the fields in this entity. Otherwise field mapping might break.
Also if you use SELECT table.column notation always define aliases matching names from entity as shown in example.
In your case change #Query like shown below:
#Query(value = "SELECT new " +
"SUM(s.no_of_shares) AS sum,COUNT(*) AS count FROM " +
"shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDTO getMetrics();
And create interface MetricDTO like shown below:
public interface MetricDTO {
Integer getSum();
Long getCount();
}
Also make sure the return type of getSum() and getCount() is correct this may vary based not database.
First, you can have a look at the Spring Data JPA documentation, you can find some help at this section : Class-based Projections (DTOs).
There is also a paragraph titled Avoid boilerplate code for projection DTOs, where they advise you to use Lombok's #Value annotation, to produce an immutable DTO. This is similar to Lombok's #Data annotation, but immutable.
If you apply it to your example, the source will look like :
#Value
public class MetricDto {
private int share;
private int shareholder;
}
Then, as your query is a NativeQuery, specifiy it in your Spring Data Repository.
You can find help in the documentation : Native Queries.
You will need something like :
#Query(value = "SELECT new
com.company.shareholders.sh.MetricDto(SUM(s.no_of_shares),COUNT(*)) FROM
shareholders s WHERE s.attend=true", nativeQuery = true)
MetricDto getMetrics();
Query query = sessionFactory.getCurrentSession()
.createNativeQuery(stringQuery).unwrap(org.hibernate.query.Query.class);
((NativeQueryImpl) query).setResultTransformer(new AliasToBeanResultTransformer(DtoClass.class));
You are writing a mixed query of native and jpql; no_of_shares is your column name in the database, but jpa is expecting you to provide not native syntax so try to replace no_of_shares with the corresponding field in your entity class. Or just add nativeQuery = true to make jpa understand it's a native query.

Spring Data repository case sensitive query

I created a simple query in a repository:
public interface UserRepository extends JpaRepository<User, Integer> {
[...]
#Query("SELECT user FROM User user "
+ "where user.userName= :userName "
+ "and user.password= :password "
+ "and user.organization.name= :organizationName")
User findByNamePasswordOrganization(#Param("userName") String userName,
#Param("password") String password,
#Param("organizationName") String organizationName);
[...]
but the result is case insensitive, so if userName="USER" or username="user" is the same for this query.
I use Spring 4.1.6.RELEASE, Spring Data 1.8.0.RELEASE, Hibernate 4.0.1.Final and MySql 5.6
EDIT:
I've resolved with native query:
#Query(value = "SELECT * FROM USER user, ORGANIZATION organization "
+ "where user.USER_NAME= ?1 "
+ "and BINARY user.PASSWORD= ?2 "
+ "and organization.name= ?3 "
+ "and user.ORGANIZATION_ID=organization.ORGANIZATION_ID", nativeQuery = true)
User findByNamePasswordOrganizationSql(String userName, String password, String organizationName);
MySQL is case insensitive unless you do a binary comparison http://dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html
By the way using Spring data you dont need the #Query just use the interface method definition
User findByUserNameAndPasswordAndOrganizationName(#Param("userName") String userName,
#Param("password") String password,
#Param("organizationName") String organizationName)
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Section 4.3.2. Query creation

Resources