specification-arg-resolver - URL operators - spring-boot

Looking at the sample code from specification-argument-resolver,
#Controller
public class SampleController {
#Autowired
CustomerRepository customerRepository;
#RequestMapping("/find")
public List<Customer> findByRegistrationDate(
#And({
#Spec(path = "name", params = "name", spec = Equal.class),
#Spec(path = "registrationDate", params = "registrationDate", spec = GreaterThanOrEqual.class)
}) Specification<Customer> spec) {
return customerRepository.findAll(spec);
}
}
#Entity
public class Customer {
String name;
Date registrationDate;
}
Looking at the sample code from specification-argument-resolver, the sample request looks like below:
GET /find?name=John&registrationDate=2020-06-19
Is there a way for the query params to reflect the actual operator.
Example: registrationDate>=2020-06-19 instead of just =
I am looking for a hybrid between RSQL and specification-argument-resolver.

You may just use this library: https://github.com/turkraft/spring-filter
It will let you run search queries such as:
/search?filter= average(ratings) > 4.5 and brand.name in ('audi', 'land rover') and (year > 2018 or km < 50000) and color : 'white' and accidents is empty
It supports dates too, and manages very well the n+1 query problem.

Related

How to change the formatting of the output of Hibernate HQL query

I'm developing a Spring Boot application with Spring Data JPA. I'm using a custom JPQL query to group by some field and get the count. Following is my repository method.
#Query("SELECT v.status.name, count(v) as cnt FROM Pet v GROUP BY v.status.name")
List<Object[]> countByStatus();
It's working and result is obtained as follows:
[
[
"pending",
1
],
[
"available",
4
]
]
However, I would like my Rest endpoint to respond with an output which is formatted like this
{
"pending": 1,
"available": 4
}
How can I achieve this?
Basically you want to produce a JSON where its properties ("pending", "available") are dynamic and come from the SELECT v.status.name part of the query.
Create a DTO to hold the row values:
package com.example.demo;
public class ResultDTO {
private final String key;
private final Long value;
public ResultDTO(String key, Long value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public Long getValue() {
return value;
}
}
Change your query to create a new ResultDTO per row:
#Query("SELECT new com.example.demo.ResultDTO(v.status.name, count(v)) as cnt FROM Pet v GROUP BY v.status.name")
List<ResultDTO> countByStatus();
"com.example.demo" is my package, you should change it to yours.
Then from your service class or from your controller you have to convert the List<ResultDTO> to a Map<String, Long> holding all rows' keys and values.
final List<ResultDTO> repositoryResults = yourRepository.countByStatus();
final Map<String, Long> results = repositoryResults.stream().collect(Collectors.toMap(ResultDTO::getKey, ResultDTO::getValue));
Your controller should be able to transform final Map<String, Long> results to the desired JSON

Multiple dynamic parameters to search with Spring Boot, sometimes someparam can be empty

I am working on Angular 2 with a Spring Boot application, and I am passing some search parameters through my UI to SpringBoot RestController where I am using the CrudRepository<User, id> interface for CRUD operations and am using some methods like findByValue(), but in my app I have four search boxes, like
name, age, city, and country.
If I fill only two boxes then I would like to search for that given two parameters like Age=22, City=New York then only those people who lives in New York with age 22 should be as result if I add name also like name=James then it should search with name and age 22 and city New York.
How do I achieve this kind of functionality in my SpringBoot with a Angular 2 app?
Repository Class:
public interface UserRepository extends CrudRepository<User, Integer> {
public List<User> findAll();
public List<User> findByName(String name);
}
My Controller Code:
#CrossOrigin(origins = "http://localhost:4200", maxAge = 3600)
#RequestMapping(value = "/user/list", method=RequestMethod.POST)
public List<User> getRequestedUsers(#RequestBody User userObject) {
return userRepository.findByAllParam();
// I want to write some method here to get data with all or may be 3, 2, or 1 parameters
}
If your controller is simply returning the repository response, you're better off using Spring Data REST:
https://spring.io/guides/gs/accessing-data-rest/
Once setup, you can expose your repository endpoints directly over REST:
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
List<User> findByName(#Param("name") String name);
List<User> findByNameAndAge(#Param("name") String name, #Param("age") Integer age)
List<User> findByNameAndAgeAndCity(#Param("name") String name, #Param("age") Integer age, #Param("city") String city);
}
This then takes care of paging, sorting etc also.
You will end up with an API similar to:
curl -i -X POST -H "Content-Type:application/json" -d "{ \"name\" : \"...\", \"age\" : 0, \"city\" : \"...\" }" http://localhost: 4200/users
In terms of searching with optional parameters, you could manage this on the client side in JavaScript. The following solution doesn't scale but should help you get something working:
function hasValue(id) {
return document.getElementById("age").value !== ''
}
function doSearch() {
const nameHasValue = hasValue("name");
const ageHasValue = hasValue("age");
const cityHasValue = hasValue("city");
if (nameHasValue && ageHasValue && cityHasValue) {
// search by name, age and city
} else if (nameHasValue && ageHasValue) {
// search by name and age
} else if (nameHasValue && cityHasValue) {
// search by name and city
} else if (ageHasValue && cityHasValue) {
// search by age and city
} else if (nameHasValue) {
// search by name
} else if (ageHasValue) {
// search by age
} else if (cityHasValue) {
// search by city
}
}
public interface OrdersRepository extends CrudRepository<OrdersModel, Integer> {
List<OrdersModel> findAllByCityLikeAndPartLike(String city, String part);
}
And if you want searching by city = New York and part = any String:
findAllByCityLikeAndPartLike("New York", "%");
Not exactly problem like yours but I think that will be helpful.

Dynamic Queries in Spring Data JPA

I am looking for a solution to dynamically build queries using Spring Data JPA. I have a GameController which has a RESTful service endpoint /games which takes 4 optional parameters: genre, platform, year, title. The API may be passed none of those, all 4, and every combination in between. If any parameter is not passed it defaults to null. I need a method in the Repository that will build the appropriate query and ideally also still allow Spring Data JPA Paging, although I'm not sure if that is possible.
I found this article but this doesn't seem to be what I need unless I am misunderstanding. http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
I know JPA has a Query Criteria API but really have no idea how to implement this.
I realize I could create a method for each possible scenario but that seems like really bad practice and a lot of unnecessary code.
GameRepository:
package net.jkratz.igdb.repository;
import net.jkratz.igdb.model.Game;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface GameRepository extends JpaRepository<Game, Long> {
#Query("select g from Game g, GamePlatformMap gpm, Platform p where g = gpm.game and gpm.platform = p and p.id = :platform")
Page<Game> getGamesByPlatform(#Param("platform") Long platformId, Pageable pageable);
#Query("select g from Game g where g.title like :title")
Page<Game> getGamesByTitle(#Param("title") String title, Pageable pageable);
#Query("select g from Game g, GameGenreMap ggm, Genre ge where g = ggm.game and ggm.genre = ge and ge.id = :genreId")
Page<Game> getGamesByGenre(#Param("genre") Long genreId, Pageable pageable);
}
I would say that using QueryDSL is one way of doing what you want.
For example I have a repository defined as below:
public interface UserRepository extends PagingAndSortingRepository<User, Long>, QueryDslPredicateExecutor<User> {
public Page<User> findAll(Predicate predicate, Pageable p);
}
I can call this method with any combination of parameters, like below:
public class UserRepositoryTest{
#Autowired
private UserRepository userRepository;
#Test
public void testFindByGender() {
List<User> users = userRepository.findAll(QUser.user.gender.eq(Gender.M));
Assert.assertEquals(4, users.size());
users = userRepository.findAll(QUser.user.gender.eq(Gender.F));
Assert.assertEquals(2, users.size());
}
#Test
public void testFindByCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Edinburgh"));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Stirling"));
Assert.assertEquals(1, users.size());
}
#Test
public void testFindByGenderAndCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.M)));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.F)));
Assert.assertEquals(1, users.size());
}
}
For those using Kotlin (and Spring Data JPA), we've just open-sourced a Kotlin JPA Specification DSL library which lets you create type-safe dynamic queries for a JPA Repository.
It uses Spring Data's JpaSpecificationExecutor (i.e. JPA criteria queries), but without the need for any boilerplate or generated metamodel.
The readme has more details on how it works internally, but here's the relevant code examples for a quick intro.
import au.com.console.jpaspecificationsdsl.* // 1. Import Kotlin magic
////
// 2. Declare JPA Entities
#Entity
data class TvShow(
#Id
#GeneratedValue
val id: Int = 0,
val name: String = "",
val synopsis: String = "",
val availableOnNetflix: Boolean = false,
val releaseDate: String? = null,
#OneToMany(cascade = arrayOf(javax.persistence.CascadeType.ALL))
val starRatings: Set<StarRating> = emptySet())
#Entity
data class StarRating(
#Id
#GeneratedValue
val id: Int = 0,
val stars: Int = 0)
////
// 3. Declare JPA Repository with JpaSpecificationExecutor
#Repository
interface TvShowRepository : CrudRepository<TvShow, Int>, JpaSpecificationExecutor<TvShow>
////
// 4. Kotlin Properties are now usable to create fluent specifications
#Service
class MyService #Inject constructor(val tvShowRepo: TvShowRepository) {
fun findShowsReleasedIn2010NotOnNetflix(): List<TvShow> {
return tvShowRepo.findAll(TvShow::availableOnNetflix.isFalse() and TvShow::releaseDate.equal("2010"))
}
/* Fall back to spring API with some extra helpers for more complex join queries */
fun findShowsWithComplexQuery(): List<TvShow> {
return tvShowRepo.findAll(where { equal(it.join(TvShow::starRatings).get(StarRating::stars), 2) })
}
}
For more complex and dynamic queries it's good practice to create functions that use the DSL to make queries more readable (as you would for QueryDSL), and to allow for their composition in complex dynamic queries.
fun hasName(name: String?): Specifications<TvShow>? = name?.let {
TvShow::name.equal(it)
}
fun availableOnNetflix(available: Boolean?): Specifications<TvShow>? = available?.let {
TvShow::availableOnNetflix.equal(it)
}
fun hasKeywordIn(keywords: List<String>?): Specifications<TvShow>? = keywords?.let {
or(keywords.map { hasKeyword(it) })
}
fun hasKeyword(keyword: String?): Specifications<TvShow>? = keyword?.let {
TvShow::synopsis.like("%$keyword%")
}
These functions can be combined with and() and or() for complex nested queries:
val shows = tvShowRepo.findAll(
or(
and(
availableOnNetflix(false),
hasKeywordIn(listOf("Jimmy"))
),
and(
availableOnNetflix(true),
or(
hasKeyword("killer"),
hasKeyword("monster")
)
)
)
)
Or they can be combined with a service-layer query DTO and mapping extension function
/**
* A TV show query DTO - typically used at the service layer.
*/
data class TvShowQuery(
val name: String? = null,
val availableOnNetflix: Boolean? = null,
val keywords: List<String> = listOf()
)
/**
* A single TvShowQuery is equivalent to an AND of all supplied criteria.
* Note: any criteria that is null will be ignored (not included in the query).
*/
fun TvShowQuery.toSpecification(): Specifications<TvShow> = and(
hasName(name),
availableOnNetflix(availableOnNetflix),
hasKeywordIn(keywords)
)
for powerful dynamic queries:
val query = TvShowQuery(availableOnNetflix = false, keywords = listOf("Rick", "Jimmy"))
val shows = tvShowRepo.findAll(query.toSpecification())
JpaSpecificationExecutor supports paging, so you can achieve pageable, type-safe, dynamic queries!
I have got a solution for this. I wrote some code to extend the spring-data-jpa .
I call it spring-data-jpa-extra
spring-data-jpa-extra comes to solve three problem:
dynamic native query support like mybatis
return type can be anything
no code, just sql
You can try it : )

Create custom finder with spring repository

I am using Spring Roo with repository layers. I have two classes: Document (with the title of the document) and FilledDocument (that contains the related Document). Like this:
public class Document {
/**
*/
#NotNull
private String titleDocument;
...
}
public class FilledDocument {
/**
*/
#OneToOne
private Document relatedDocument;
...
}
I would want to create a finder for the class FilledDocument that match for title inside of the atribute relatedDocument.
I am using a solution that isnt elegant and efficient. In my controller I have:
#RequestMapping(params = "find", produces = "text/html")
public String findFilledDocumentsByTitleDocumentContaining(#RequestParam(value = "title", required = true) String title, Model uiModel) {
LinkedList<FilledDocument> allFilledDocuments = new LinkedList<FilledDocument>();
allFilledDocuments.addAll(filledDocumentService.findAllFilledDocuments());
ArrayList<FilledDocument> filledDocuments=new ArrayList<FilledDocument>();
for( FilledDocument filledDocument : allFilledDocuments ) {
if( filledDocument.getRelatedDocument().getTitleDocument().toLowerCase().contains(title.toLowerCase()) == true ) {
filledDocuments.add(filledDocument);
}
}
uiModel.addAttribute("filleddocuments", filledDocuments);
return "filleddocuments/list";
}
I am reading this Spring Data JPA - Reference Documentation and I am trying with #Query in the repository class, like this:
#Query("select f from FilledDocument f where f.relatedDocument.titleDocument containing = ?1")
public ArrayList<FilledDocument> findFilledDocumentByTitleDocument(#Param("titleDocument") String titleDocument);
but it doesnt work. Any ideas?
If you are using Spring Data JPA 1.3.1 or newer that you can just write :
#Query("select f from FilledDocument f where f.relatedDocument.titleDocument like %:title%")
List<FilledDocument> findFilledDocumentByTitleDocument(#Param("title") String titleDocument);
If you are stuck on an older version then your code would be:
#Query("select f from FilledDocument f where f.relatedDocument.titleDocument like :title")
List<FilledDocument> findFilledDocumentByTitleDocument(#Param("title") String titleDocument);
And when you call the findFilledDocumentByTitleDocument method you need to pass
"%"+title+"%"
as an argument.
Check out this blog post for the full story and section 1.3.4 of the documentation.

Spring Data MongoDB: Accessing and updating sub documents

First experiments with Spring Data and MongoDB were great. Now I've got the following structure (simplified):
public class Letter {
#Id
private String id;
private List<Section> sections;
}
public class Section {
private String id;
private String content;
}
Loading and saving entire Letter objects/documents works like a charm. (I use ObjectId to generate unique IDs for the Section.id field.)
Letter letter1 = mongoTemplate.findById(id, Letter.class)
mongoTemplate.insert(letter2);
mongoTemplate.save(letter3);
As documents are big (200K) and sometimes only sub-parts are needed by the application: Is there a possibility to query for a sub-document (section), modify and save it?
I'd like to implement a method like
Section s = findLetterSection(letterId, sectionId);
s.setText("blubb");
replaceLetterSection(letterId, sectionId, s);
And of course methods like:
addLetterSection(letterId, s); // add after last section
insertLetterSection(letterId, sectionId, s); // insert before given section
deleteLetterSection(letterId, sectionId); // delete given section
I see that the last three methods are somewhat "strange", i.e. loading the entire document, modifying the collection and saving it again may be the better approach from an object-oriented point of view; but the first use case ("navigating" to a sub-document/sub-object and working in the scope of this object) seems natural.
I think MongoDB can update sub-documents, but can SpringData be used for object mapping? Thanks for any pointers.
I figured out the following approach for slicing and loading only one subobject. Does it seem ok? I am aware of problems with concurrent modifications.
Query query1 = Query.query(Criteria.where("_id").is(instance));
query1.fields().include("sections._id");
LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class);
LetterSection emptySection = letter1.findSectionById(sectionId);
int index = letter1.getSections().indexOf(emptySection);
Query query2 = Query.query(Criteria.where("_id").is(instance));
query2.fields().include("sections").slice("sections", index, 1);
LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
LetterSection section = letter2.getSections().get(0);
This is an alternative solution loading all sections, but omitting the other (large) fields.
Query query = Query.query(Criteria.where("_id").is(instance));
query.fields().include("sections");
LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class);
LetterSection section = letter.findSectionById(sectionId);
This is the code I use for storing only a single collection element:
MongoConverter converter = mongoTemplate.getConverter();
DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);
Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
Update update = new Update().set("sections.$", newSectionRec);
mongoTemplate.updateFirst(query, update, LetterInstance.class);
It is nice to see how Spring Data can be used with "partial results" from MongoDB.
Any comments highly appreciated!
I think Matthias Wuttke's answer is great, for anyone looking for a generic version of his answer see code below:
#Service
public class MongoUtils {
#Autowired
private MongoTemplate mongo;
public <D, N extends Domain> N findNestedDocument(Class<D> docClass, String collectionName, UUID outerId, UUID innerId,
Function<D, List<N>> collectionGetter) {
// get index of subdocument in array
Query query = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query.fields().include(collectionName + "._id");
D obj = mongo.findOne(query, docClass);
if (obj == null) {
return null;
}
List<UUID> itemIds = collectionGetter.apply(obj).stream().map(N::getId).collect(Collectors.toList());
int index = itemIds.indexOf(innerId);
if (index == -1) {
return null;
}
// retrieve subdocument at index using slice operator
Query query2 = new Query(Criteria.where("_id").is(outerId).and(collectionName + "._id").is(innerId));
query2.fields().include(collectionName).slice(collectionName, index, 1);
D obj2 = mongo.findOne(query2, docClass);
if (obj2 == null) {
return null;
}
return collectionGetter.apply(obj2).get(0);
}
public void removeNestedDocument(UUID outerId, UUID innerId, String collectionName, Class<?> outerClass) {
Update update = new Update();
update.pull(collectionName, new Query(Criteria.where("_id").is(innerId)));
mongo.updateFirst(new Query(Criteria.where("_id").is(outerId)), update, outerClass);
}
}
This could for example be called using
mongoUtils.findNestedDocument(Shop.class, "items", shopId, itemId, Shop::getItems);
mongoUtils.removeNestedDocument(shopId, itemId, "items", Shop.class);
The Domain interface looks like this:
public interface Domain {
UUID getId();
}
Notice: If the nested document's constructor contains elements with primitive datatype, it is important for the nested document to have a default (empty) constructor, which may be protected, in order for the class to be instantiatable with null arguments.
Solution
Thats my solution for this problem:
The object should be updated
#Getter
#Setter
#Document(collection = "projectchild")
public class ProjectChild {
#Id
private String _id;
private String name;
private String code;
#Field("desc")
private String description;
private String startDate;
private String endDate;
#Field("cost")
private long estimatedCost;
private List<String> countryList;
private List<Task> tasks;
#Version
private Long version;
}
Coding the Solution
public Mono<ProjectChild> UpdateCritTemplChild(
String id, String idch, String ownername) {
Query query = new Query();
query.addCriteria(Criteria.where("_id")
.is(id)); // find the parent
query.addCriteria(Criteria.where("tasks._id")
.is(idch)); // find the child which will be changed
Update update = new Update();
update.set("tasks.$.ownername", ownername); // change the field inside the child that must be updated
return template
// findAndModify:
// Find/modify/get the "new object" from a single operation.
.findAndModify(
query, update,
new FindAndModifyOptions().returnNew(true), ProjectChild.class
)
;
}

Resources