How can I return map collections as jpa query - spring-boot-jpa

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.

Related

JPA Custom query Delete from multiple tables

I have an custom query for delete records from 2 tables as follows
#Repository
public interface RoamingStatusHistoryRepository extends JpaRepository<RoamingStatusHistory, String> {
#Query("DELETE rsh,rs from RoamingStatusHistory rsh inner join RoamingStatus rs on rsh.msisdn = rs.msisdn where TIMEDIFF(NOW(),rsh.createdDate)>'00:00:30'")
public List<Date> deleteByDate();
}
But after DELETE IntelliJ saying from expected got rsh and after rsh there is a error saying alias definition or WHERE expected, got ','
How to fix this issue. Have researched in the internet but couldn't find a solution
I assume that this query is a native SQL query so you have to add nativeQuery = true
#Repository
public interface RoamingStatusHistoryRepository extends JpaRepository<RoamingStatusHistory, String> {
#Query("DELETE rsh,rs from RoamingStatusHistory rsh inner join RoamingStatus rs on rsh.msisdn = rs.msisdn where TIMEDIFF(NOW(),rsh.createdDate)>'00:00:30'",
nativeQuery = true)
public List<Date> deleteByDate();
}

Spring mongodb repository returns 0 entries for query on field which is of type list

I am trying to get list of users whose comments are matching specific input keyword from mongodb document collection.
My User document defintion looks like
public class User {
#Id
private String id;
private String name;
List<String> comments;
}
And my Spring Repository code looks like
#RepositoryRestResource(collectionResourceRel = "user", path = "user")
public interface UserRepository extends
MongoRepository<User,String>,CustomUserRepository {
#Query(value = "{'comments': ?0} ")
List<User> findByComments(String username);
List<User> findByCommentsIn(List<String> comments);
List<User> findBycomments(String username);
When i query it from mongo shell it works fine,
db.user.find({"comments": /test/}) returns the expected result .
But same is not working with Spring Data mongodb.
And i also tried using Custom Repository , to use mongo template.
The code snippet is as follows
Query query = new Query()
query.addCriteria(
Criteria.where("comments").in("/"+user+"/")
);
List<User> result = mongoTemplate.find(query, User.class);
After little more research , it works if i use $regex in my query method.
#Query(value = "{'comments': {$regex:?0}}")
List findByComment(String comment);
Additional, would be interested in knowing how to debug such issues.

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

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

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

Resources