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

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.

Related

Spring Data JDBC - Pageable on custom Query

On my Project I have a Repository that extends CrudRepository. Inside there I have a custom query:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
#Query("select * from person where firstname = :firstname")
List<Customer> findByFirstname(#Param("firstname") String firstname, Pageable pageable);
}
in my Service-Class I try to put the List in a Pageable - Object like:
... getPageableCustomer(String firstname, Pageable pageable){
// => By using "Sol" I got 90 matching entries
List<Customer> custList = customerRepository.findByFirstname(firstname, pageable);
Page<Customer> custPage = new PageImpl<Customer>(custList, pageable, custList.size());
return custPage;
}
the return value includes the complete List "custList". What would be the best way to get a pageable object with specified offset and size?
One option could be to use
customer.subList(fromIndex, toIndex)
but that feels wrong. Also because of Loading all Data inside the list instead of just getting data by size and offset as parameterized with pageable.
Remark: In case of using Page inside the Repository I ´ll get org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 88
There is also a open Improvement on Jira that could be found here:
https://jira.spring.io/browse/DATAJDBC-554?filter=-3
hope for some help...
I got a response on the JIRA-Issue from Dirk Luijk (Thx Dirk :))
https://jira.spring.io/browse/DATAJDBC-554?filter=-3
interface FooRepository extends PagingAndSortingRepository<FooEntity, Long> {
List<FooEntity> findAllByBar(String bar, Pageable pageable);
Long countAllByBar(String bar);
}
And then combining those 2 queries like this:
List<FooEntity> fooList = repository.findAllByBar("...", pageable);
Long fooTotalCount = repository.countAllByBar("...");
Page<FooEntity> fooPage = PageableExecutionUtils.getPage(fooList, pageable, () -> fooTotalCount);
"the mistake in your workaround is your custom query. In Spring Data JDBC 2.0 you don't need to use that, except for special queries but they won't support pageables."
Possible Parameters could be found:
https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.query-methods
Thx Dirk,
I also find a workaround to get it running with a custom Query. Just use limit, offset and orderBy as additional Parameter like so:
#Query("select * from person where firstname = :name order by :order limit :size offset :offset")
List<Customer> findByFirstNameCustomQuery(#Param("name") String name, Pageable page, #Param("offset") long offset,
#Param("size") long size, #Param("order") String order);
And than change the call inside the Service like:
List<Customer> custList = customerRepository.findByFirstNameCustomQuery(firstname, pageable, ....

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.

JPA #Query annotation does not seem to be using paramter

I have a Spring Boot application using JPA/Hibernate as entity management/modeling. I have the following user class:
#Entity
public class User {
#Id
private Long Id;
private String name;
//more fields, getters and setters below
}
I want users of my application to be able to search for users by name. So in my repository interface, I have:
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT u from User u WHERE lower(u.name) LIKE lower(:name)")
List<User> findByNameLike(#Param(value="name") String nmae);
}
Then in my UserController, I have:
#GetMapping(value = "/users/")
public #ResponseBody List<User> search(#RequestParam String name) {
return this.userRepository.findByNameLike(name);
}
This always returns an empty list. I know the name parameter is coming in correctly.
Moreover, I do know that it is recognizing the #Query annotation, because if I change the query to something like
SELECT u FROM User u
it will return me all users. Or if I change it to
SELECT u from User u WHERE u.name = '%Bob%'
and remove the parameter from the method, it will return all users whose name is Bob. I've tried other variations of the query, such as
SELECT u FROM User u WHERE lower(u.name) LIKE CONCAT('%', lower(:name), '%')
SELECT u FROM User u WHERE u.name = :name (even a simple equals doesn't work)
and so on and so forth. Every source I look at whose syntax I copy seems to say I'm writing the query right, so I think something else must be going on.
I've also tried letting Spring generate the query for me, using
public List<User> findByNameLike(String name);
public List<User> findByNameContaining(String name);
which also don't seem to work. Any help would be appreciated here!

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

Externalize mongo json query using spring boot

I have just started using spring data MongoDb with Spring-Boot.
I have some mongo based json queries added in the interface using #query annotation when using spring data repository.
I want to know if it is possible to externalize or separate out the JSON query outside the codebase so that it can be optimized separately and
also not having it mixed with code.
Thanks for your suggestions.
This is the code which i have added in my interface and annotated with #query annotation.
#Query("{ 'firstname' : ?0 ,'lastname': ?1}")
List findByCriteria(String firstname,String lastname);
The above is a simple example. I have complex conditions involving $and and $or operators too .
What i basically want to achieve is externalize the above native mongo json query to a config file and refer that in the above annotation.
Spring data supports something similar when using jpa with hibernate. But not sure if we can do the same using spring data mongodb with spring boot.
Do like this (I am explaining only for the API)
Suppose you have an Entity user
At the Top there will be User domain
public class User extends CoreDomain {
private static final long serialVersionUID = -4292195532570879677L;
#Length(min = 2)
private String name;
#Length(min = 2)
#UniqueUserName(message = "User name already registered,Please choose something Different")
private String userName;
#Length(min = 6)
private String password;
}
User Controller
User Service (Interface)
User ServiceImpl(Service Implementation)
Mongo Repository(Since, I have MongoDb)
Now in userController you will take all the queries , Param(Parameters) , Pagerequest like this
public class UserController extends CoreController {
#Autowired
private UserService userService;
/*
* This controller is for getting the UserDetails on passing the UserId in
* the #param Annotation
*/
#GET
#Path("{id}")
public User getUser(#PathParam("id") String UserId) {
User user = new User();
user = userService.findUserId(UserId);
if (user == null)
throw new NotFoundException();
log.info("The userId you searched is having the details as :" + user);
return user;
}}
For serviceInterface you will have :
public interface UserService {
// Boolean authenticateUser(User user);
User findUserId(String UserId);
}
For serviceImpl :
public class UserServiceImpl implements UserService {
#Setter
#Autowired
private UserRepository userRepository;
/*
* This method will find user on the basis of their userIds passed in the
* parameter.
*/
#Override
public User findUserId(String UserId) {
User userIdResult = userRepository.findOne(UserId);
log.info("The userDetail is" + userIdResult);
return userIdResult;
}
In mongoRepository for user we will have:
A default query findById(String userId);
Hopefully this will help you.

Resources