Spring JPA QuerydslPredicate Snake Case - spring-boot

I am using spring boot with Query DSL. I have configured my spring boot to use snake case i.e. spring.jackson.property-naming-strategy=SNAKE_CASE. So my json payload input and output is in snake case like below
{
"first_name": "First",
"last_name": "Last"
}
I am using #QuerydslPredicate for my search functionality. If I enter query params in snake case like below
(http://localhost:8080/context/resource/?first_name=First)
it is not parsed by Query DSL Predicate. However, If I provide the params in camel case like below
(http://localhost:8080/context/resource/?firsName=First)
it works fine. This is not synonymous with json structure.
How can I achieve snake case in query params so that Query DSL parses it?
This is how my REST end point looks like
#GetMapping
public ResponseEntity<Output> listUsers(#QuerydslPredicate(root = User.class) Predicate predicate, Pageable pageable) {
...
return new ResponseEntity<>(output, HttpStatus.OK);
}
PN: My Model Object is in camel case like below
Model Object
public class User {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
I came across a solution on HandlerMethodArgumentResolver, but that is lot of work. There should be a way to automate it than doing all model fields.

Related

Return an empty object Java Spring Boot REST

I'm writing a dto class containing the information of a user
public class User {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
I'd receive this JSON object if I do GET api/v1/user/1
{
firstName: John,
lastName: Kennedy
}
But when the firstName and the lastName are null, I want to receive an empty object like:
{}
How would you do technically on Spring Boot in order to receive such empty object in Json format?
Thank you in advance
You could try to annotate your class with:
#JsonInclude(Include.NON_NULL)
If you want an empty object { } and empty request is not valid for you, you can create an auxiliar class called EmptyObject or something similar:
#JsonSerialize
public class EmptyObject {
}
And into your controller:
return user.getFirstName() == null && user.getLastName() == null ?
ResponseEntity.ok(new EmptyObject()) : ResponseEntity.ok(user);

Springdoc cannot detect POJO's fields to map as individual parameters on UI

My springboot application has a #RestController which takes a POJO class as parameter.
#GetMapping(path="/")
public void sayHello(Person person) {
System.out.println(person);
}
and here's the definition of Person class which is just a POJO.
public class Person {
private String firstName;
private String lastName;
private int age;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
#Override
public String toString() {
return "Person [firstName=" + firstName + ", lastName=" + lastName + ", age=" + age + "]";
}
}
This is how springdoc-ui interprets it for showing parameters in UI.
I did not use #RequestBody in the controller yet springdoc assumes the input to be in the form of JSON body. I intend it to be as query parameters as below
Interestingly if I change the swagger implementation to springfox, each fields of the POJO is interpreted as individual parameters in the UI by default. The last screenshot was taken using springfox implemntation. How do I get the same behavior with springdoc?
Using #ParameterObject fixes this.
#GetMapping(path="/")
public void sayHello(#ParameterObject Person person) {
System.out.println(person);
}
Found the solution here:
https://github.com/springdoc/springdoc-openapi/issues/162
https://github.com/springdoc/springdoc-openapi/pull/505

Order results based on count using spring boot specification API

Consider the entity below.
PS: The model has more fields but for the question to be short I have posted only the relevant fields
Class Employee {
private String name;
private String country;
private String region;
private String department
#OneToMany
private Set<Skill> skills;
}
Class Skill {
private name;
}
I am using spring boot Specification API to filter employees on different fields like region, country, and so on.
public class EmployeeSpec implements Specification<Employee> {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
String fielName = //some field name
String fieldValue = //some field value
switch (fielName ) {
case "country":
return cb.equal(root.get("country"), fieldValue);
case "department":
return cb.equal(root.get("department"), fieldValue);
case "region":
return cb.equal(root.get("region"), fieldValue);
}
}
I want to order the results such that employee with maximum skills comes first. I am not sure how to implement this using Specification.
You can use CriteriaBuilder.size(..). For your case, the code will look like:
cq.orderBy(cb.desc(cb.size(root.get("skills"))));

How can I get multiple properties from a Java POJO using the Java 8 Stream API?

Given this class written in the Java 8 style, I wanted to see if I dont need to call the stream api twice :
import java.util.*;
public class Foo {
public static void main(String... args) {
List<Person> persons = new ArrayList<>();
init(persons, Person::new, "John", "Doe");
persons.stream()
.map(Person::getFirstName)
.forEach(System.out::println);
persons.stream()
.map(Person::getLastName)
.forEach(System.out::println);
}
#FunctionalInterface
interface PersonFactory {
Person create(String firstName, String lastName);
}
private static void init(List<Person> persons, PersonFactory factory, String fn, String ln) {
persons.add(factory.create(fn, ln));
}
}
class Person {
private final String firstName;
private final String lastName;
public Person(String fName, String lName) {
this.firstName = fName;
this.lastName = lName;
}
public String getFirstName() {return this.firstName;}
public String getLastName() {return this.lastName;}
}
I wanted to see if I could instead stream the "persons" List in one go.
Any suggestions ?
If you don't need to transform object to another, you can try this
persons.forEach(i -> System.out.println(i.getFirstName() + " " + i.getLastName()));
i think it could be helpfull for you using Map
Map<String, String> mapp = persons.stream().collect(HashMap::new,
(m, c) ->{
m.put(c.getFirstname(), "");
m.put(c.getLastname(), "");
},HashMap::putAll);
System.out.println(mapp.keySet().toString());

Upsert Mongo Document using spring data mongo

I have a Class
#Document
public class MyDocument {
#Id
private String id;
private String title;
private String description;
private String tagLine;
#CreatedDate
private Date createdDate;
#LastModifiedDate
private Date updatedDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTagLine() {
return tagLine;
}
public void setTagLine(String tagLine) {
this.tagLine = tagLine;
}
}
i have added annotated application with #EnableMongoAuditing
i have created interface which implements mongorepository
public interface MyDocumentRepository extends MongoRepository<MyDocument, String> {
}
when i have created RestController with GET,POST,PATCH methods
in POST I'm sending
{'title':'first'}
Controller Class POST method is
#RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<?> saveMyDocument(#RequestBody MyDocument myDocument) {
MyDocument doc = myDocumentRepo.save(myDocument);
return new ResponseEntity<MyDocument>(doc, HttpStatus.CREATED);
}
Its saving the data in mongo.
{
"_id" : ObjectId("56b3451f0364b03f3098f101"),
"_class" : "com.wiziq.service.course.model.MyDocument",
"title" : "test"
}
and PATCH request is like
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
public ResponseEntity<MyDocument> updateCourse(#PathVariable(value = "id") String id,
#RequestBody MyDocument myDocument) {
myDocument.setId(id);
MyDocument doc = courseService.save(myDocument);
return ResponseEntity.ok(course);
}
when in make PATCH request with data {"description":"This is test"}
it update the docuent BUT it removes title field and createdDate form the document, its doing update which is ok. But i wanted to do an upsert, i can do its using mongoTemplate,
but there i have to set each property which i want to set.
Is there any generic way to that if i get a PATCH request i can update only not null properties.. properties which are coming in request
spring-data-rest seems to do it using #RepositoryRestResource. How can i achieve the same.
I don't want to code like this
Update update = new Update().set("title", myDocument.getTitle()).set("description", myDocument.getdescription());
Unfortunately its the behavior in MongoDB, you can verify the same using shell.
So to update create an Update Object and using
Query query = new Query(Criteria.where("id").is(ID));
Here ID is the document which you want to update.Based on your requirement set upsert after that using findAndModify update document.
mongoTemplate.findAndModify(query, update,
new FindAndModifyOptions().returnNew(true).upsert(false),
someclass.class);
If you have a model like MyModel.class and you need a smooth way to create an Update object from it there is no real clear way how to do this but you can use MongoConverter bean that is created in Spring Data Mongo auto configuration and then just use replaceOne method of MongoCollection.
#Autowired
private MongoTemplate template;
#Autowired
private MongoConverter mongoConverter;
...
#Override
public void upsertMyModel(MyModel model) {
Document documentToUpsert = new Document();
mongoConverter.write(model, documentToUpsert);
template.getCollection(collectionName).replaceOne(
Filters.eq("_id", model.getId()),
documentToUpsert,
new ReplaceOptions().upsert(true));
}
Upsert can be done in Spring data mongodb using BulkOperations.
Suppose there are two entities Entity1 and Entity2. Entity1 has foreginId which is primary id of Entity2. Both have a field title. Now, to upsert from entity2 to entity1, we can do it as follows:
Query query = new Query(Criteria.where("foreignId").is(entity2.getId()));
Update update = new Update();
update.set("title",entity2.getTitle());
List<Pair<Query, Update>> updates = new ArrayList<Pair<Query, Update>>();
updates.add(Pair.of(query, update););
BulkOperations bulkOps = this.mongoTemplate.bulkOps(BulkMode.UNORDERED, Entity1.class);
bulkOps.upsert(updates);
bulkOps.execute();

Resources