Java Spring: Efficient design of basic CRUD controller for RESTful API - java

So let's say I have a simple entity defined as such:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue
private Long id;
private String fieldOne;
private String fieldTwo;
//...
private String fieldN;
}
Let's consider a simple controller for an endpoint which handles updating Person's, but only updating fields passed in that aren't empty/blank:
#Controller
#RequestMapping(value = "/api/person")
public class PersonController {
#Autowired
PersonRepository personRepository;
#RequestMapping(value = "/update", method = RequestMethod.PUT)
public void updatePerson(#RequestParam("personId") Long personId,
#RequestParam("fieldOne") String fieldOne,
#RequestParam("fieldTwo") String fieldTwo,
//...
#RequestParam("fieldN") String fieldN) {
Person toUpdate = personRepository.findOne(personId);
if(fieldOne != null && !fieldOne.isEmpty())
toUpdate.setFieldOne(fieldOne);
if(fieldTwo != null && !fieldTwo.isEmpty())
toUpdate.setFieldTwo(fieldTwo);
//...
if(fieldN != null && !fieldN.isEmpty())
toUpdate.setFieldN(fieldN);
personRepository.save(toUpdate);
}
}
Is there a more efficient approach for such updating of an instance of an entity? I thought about using the DTO and #RequestBody approach (i.e. updatePerson(#Valid #RequestBody PersonDTO personDTO) but this does more or less the same thing.
My main concern is that regardless of what approach I use, I obviously do not want to receive a null/empty string and set some field of an entity to be blank, but then I want to do this as efficiently as possible (efficiency in terms of code readability/portability and of course, runtime) and I'm not entirely sure if having hundreds of if-statements similar to what I have above is the most efficient option.

The #RequestParam actually has another argument which is defaultValue, you can use this so the argument won't never be empty & so you can reduce the checking againts empty & null values
#RequestParam(value="field" , defaultValue="****")
by using that you have already eliminated if(fieldOne != null && !fieldOne.isEmpty()) and calling toUpdate.setFieldOne(...)that wrapping the fields directly. Using defaultValue automatically sets required to false, and inserts a default value into your input argument when the request parameter is missing from your URL.

Related

Spring boot - partial update best practise?

I am using Spring boot v2 with mongo database. I was wondering what is the best way to do partial updates on the data model. Say I have a model with x attributes, depending on the request I may only want to update 1, 2 , or x of them attributes. Should I be exposing an end point for each type of update operation, or is it possible to expose one end pint and do it in a generic way? Note I will need to be able to validate the contents of the request attributes (e.g tel no must be numbers only)
Thanks,
HTTP PATCH is a nice way to update a resource by specifying only the properties that have changed.
The following blog explain it very well
You can actually expose just one endpoint. This is the situation I had a few months ago:
I wanted people to modify any (or even all)fields of a Projects document (who am I to force the users to manually supply all fields lol). So I have my Model,
Project.java:
package com.foxxmg.jarvisbackend.models;
//imports
#Document(collection = "Projects")
public class Project {
#Id
public String id;
public String projectTitle;
public String projectOverview;
public Date startDate;
public Date endDate;
public List<String> assignedTo;
public String progress;
//constructors
//getters & setters
}
I have my repository:
ProjectRepository.java
package com.foxxmg.jarvisbackend.repositories;
//imports
#Repository
public interface ProjectRepository extends MongoRepository<Project, String>, QuerydslPredicateExecutor<Project> {
//please note, we are going to use findById(string) method for updating
Project findByid(String id);
//other abstract methods
}
Now to my Controller, ProjectController.java:
package com.foxxmg.jarvisbackend.controllers;
//import
#RestController
#RequestMapping("/projects")
#CrossOrigin("*")
public class ProjectController {
#Autowired
private ProjectRepository projectRepository;
#PutMapping("update/{id}")
public ResponseEntity<Project> update(#PathVariable("id") String id, #RequestBody Project project) {
Optional<Project> optionalProject = projectRepository.findById(id);
if (optionalProject.isPresent()) {
Project p = optionalProject.get();
if (project.getProjectTitle() != null)
p.setProjectTitle(project.getProjectTitle());
if (project.getProjectOverview() != null)
p.setProjectOverview(project.getProjectOverview());
if (project.getStartDate() != null)
p.setStartDate(project.getStartDate());
if (project.getEndDate() != null)
p.setEndDate(project.getEndDate());
if (project.getAssignedTo() != null)
p.setAssignedTo(project.getAssignedTo());
return new ResponseEntity<>(projectRepository.save(p), HttpStatus.OK);
} else
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
That will allow partial update in MongoDB with Spring Boot.
If you are using Spring Data MongoDB, you have two options either use the MongoDB Repository or using the MongoTemplate.

Java Spring REST API Handling Many Optional Parameters

I'm currently messing around with a Spring Boot REST API project for instructional purposes. I have a rather large table with 22 columns loaded into a MySQL database and am trying to give the user the ability to filter the results by multiple columns (let's say 6 for the purposes of this example).
I am currently extending a Repository and have initialized methods such as findByParam1 and findByParam2 and findByParam1OrderByParam2Desc and etc. and have verified that they are working as intended. My question to you guys is the best way to approach allowing the user the ability to leverage all 6 optional RequestParams without writing a ridiculous amount of conditionals/repository method variants. For example, I want to give the user the ability to hit url home/get-data/ to get all results, home/get-data?param1=xx to filter based on param1, and potentially, home/get-data?param1=xx&param2=yy...&param6=zz to filter on all the optional parameters.
For reference, here is what the relevant chunk of my controller looks like (roughly).
#RequestMapping(value = "/get-data", method = RequestMethod.GET)
public List<SomeEntity> getData(#RequestParam Map<String, String> params) {
String p1 = params.get("param1");
if(p1 != null) {
return this.someRepository.findByParam1(p1);
}
return this.someRepository.findAll();
}
My issue so far is that the way I am proceeding about this means that I will basically need n! amount of methods in my repository to support this functionality with n equalling the amount of fields/columns I want to filter on. Is there a better way to approach handling this, perhaps where I am filtering the repository 'in-place' so I can simply filter 'in-place' as I check the Map to see what filters the user did indeed populate?
EDIT: So I'm currently implementing a 'hacky' solution that might be related to J. West's comment below. I assume that the user will be specifying all n parameters in the request URL and if they do not (for example, they specify p1-p4 but not p5 and p6) I generate SQL that just matches the statement to LIKE '%' for the non-included params. It would look something like...
#Query("select u from User u where u.p1 = :p1 and u.p2 = :p2 ... and u.p6 = :p6")
List<User> findWithComplicatedQueryAndSuch;
and in the Controller, I would detect if p5 and p6 were null in the Map and if so, simply change them to the String '%'. I'm sure there is a more precise and intuitive way to do this, although I haven't been able to find anything of the sort yet.
You can do this easily with a JpaSpecificationExecutor and a custom Specification: https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
I would replace the HashMap with a DTO containing all optional get params, then build the specifications based on that DTO, obviously you can also keep the HashMap and build the specification based on it.
Basically:
public class VehicleFilter implements Specification<Vehicle>
{
private String art;
private String userId;
private String vehicle;
private String identifier;
#Override
public Predicate toPredicate(Root<Vehicle> root, CriteriaQuery<?> query, CriteriaBuilder cb)
{
ArrayList<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(art))
{
predicates.add(cb.equal(root.get("art"), art));
}
if (StringUtils.isNotBlank(userId))
{
predicates.add(cb.equal(root.get("userId"), userId));
}
if (StringUtils.isNotBlank(vehicle))
{
predicates.add(cb.equal(root.get("vehicle"), vehicle));
}
if (StringUtils.isNotBlank(identifier))
{
predicates.add(cb.equal(root.get("identifier"), fab));
}
return predicates.size() <= 0 ? null : cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
// getter & setter
}
And the controller:
#RequestMapping(value = "/{ticket}/count", method = RequestMethod.GET)
public long getItemsCount(
#PathVariable String ticket,
VehicleFilter filter,
HttpServletRequest request
) throws Exception
{
return vehicleService.getCount(filter);
}
Service:
#Override
public long getCount(VehicleFilter filter)
{
return vehicleRepository.count(filter);
}
Repository:
#Repository
public interface VehicleRepository extends JpaRepository<Vehicle, Integer>, JpaSpecificationExecutor<Vehicle>
{
}
Just a quick example adapted from company code, you get the idea!
Another solution with less coding would be to use QueryDsl integration with Spring MVC.
By using this approach all your request parameters will be automatically resolved to one of your domain properties and appended to your query.
For reference check the documentation https://spring.io/blog/2015/09/04/what-s-new-in-spring-data-release-gosling#querydsl-web-support and the example project https://github.com/spring-projects/spring-data-examples/tree/master/web/querydsl
You can do it even more easily using Query By Example (QBE) technique if your repository class implements JpaRepository interface as that interface implements QueryByExampleExecutor interface which provides findAll method that takes object of Example<T> as an argument.
Using this approach is really applicable for your scenario as your entity has a lot of fields and you want user to be able to get those which are matching filter represented as subset of entity's fields with their corresponding values that have to be matched.
Let's say the entity is User (like in your example) and you want to create endpoint for fetching users whose attribute values are equal to the ones which are specified. That could be accomplished with the following code:
Entity class:
#Entity
public class User implements Serializable {
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String city;
private String state;
private String zipCode;
}
Controller class:
#Controller
public class UserController {
private UserRepository repository;
private UserController(UserRepository repository) {
this.repository = repository;
}
#GetMapping
public List<User> getMatchingUsers(#RequestBody User userFilter) {
return repository.findAll(Example.of(userFilter));
}
}
Repository class:
#Repository
public class UserRepository implements JpaRepository<User, Integer> {
}

How does SpringBoot deserialize JSON, and when does it do it?

I'm currently working on a SpringBoot API to interface with a MongoRepository, but I'm having trouble understanding how the JSON being passed becomes a Document for storage within Mongo. I currently have a simple API that stores a group of users:
#Document
#JsonInclude
public class Group {
#Id
#JsonView(Views.Public.class)
private String id;
#JsonView(Views.Public.class)
private String name;
#JsonView(Views.Public.class)
private Set<GroupMember> groupMembers = new HashSet<>();
}
There are also setter and getter methods for each of the fields, although I don't know how necessary those are either (see questions at the end).
Here is the straightforward component I'm using:
#Component
#Path("/groups")
#Api(value = "/groups", description = "Group REST")
public class Groups {
#Autowired
private GroupService groupService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Get all Groups", response = Group.class, responseContainer = "List")
#JsonView(Views.Public.class)
public List<Group> getAllGroups() {
return groupService.getAllGroups();
}
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Create a Group", response = Group.class)
#JsonView(Views.Detailed.class)
public Group submitGroup(Group group) {
return groupService.addGroup(group);
}
}
Finally, I have a Service class:
#Service
public class GroupServiceImpl implements GroupService {
#Autowired
private GroupRepository groupRepository;
#Override
public Group addGroup(Group group) {
group.setId(null);
return groupRepository.save(group);
}
#Override
public List<Group> getAllGroups() {
return groupRepository.findAll();
}
}
The GroupRespository is simply an interface which extends MongoRepository<Group,String>
Now, when I actually make a call to the POST method, with a body containing:
{
"name": "group001",
"groupMembers": []
}
I see that it properly inserts this group with a random Mongo UUID. However, if I try to insert GroupMember objects inside the list, I receive a null pointer exception. From this, I have two questions:
How does SpringBoot (Jackson?) know which fields to deserialize from the JSON being passed? I tested this after deleting the getter and setter methods, and it still works.
How does SpringBoot handle nested objects, such as the Set inside the class? I tested with List instead of Set, and it worked, but I have no idea why. My guess is that for each object that is both declared in my class and listed in my JSON object, SpringBoot is calling a constructor that it magically created behind the scenes, and one doesn't exist for the Set interface.
Suppose I'm adamant on using Set (the same user shouldn't show up twice anyway). What tools can I use to get SpringBoot to work as expected?
It seems to me that a lot of the things that happen in Spring are very behind-the-scenes, which makes it difficult for me to understand why things work when they do. Not knowing why things work makes it difficult to construct things from scratch, which makes it feel as though I'm hacking together a project rather than actually engineering one. So my last question is something like, is there a guide that explains the wiring behind the scenes?
Finally, this is my first time working with Spring... so please excuse me if my questions are entirely off the mark, but I would appreciate any answers nonetheless.

Spring MVC - #ModelAttribute with Long type

I'm trying to inject an model attribute using the annotation #ModelAttribute on method argument.
#RequestMapping({"/", "/index"})
public String home(Principal principal, Model model, #ModelAttribute("commerceId") Long commerceId) {
if (commerceId == null) {
LOGGER.info("Initializing commerce code...");
String name = principal.getName();
commerceId = Long.parseLong(name);
model.addAttribute("commerceId", commerceId);
}
return "index";
}
But always I get the next exception:
java.lang.NoSuchMethodException: java.lang.Long.<init>()
I can see that spring is trying to create the Long value using a no-arg constructor, but obviously It will fail because Long type doesn't have no-arg constructor.
Why Spring is unable to create correctly a Long type with #ModelAttribute ?
Can any Wrapper type (Integer, Long, etc...) be injected with #ModelAttribute ?
I'm using Spring 3.1.1
eg:
#ModelAttribute("peson") Person person
First it tries to crate a Person object with no-arg constructor. Then it calls the setter methods to set/populate the values for the respective properties.
In case of
#ModelAttribute("commerceId") Long commerceId
There is no no-arg constructor, and no setter method to set the value. Primitive Wrapper class provides arg-base constructor, also no setter method. Because once you initialize it, it does not allow you to change the value.
Try with #RequestParam("commerceId") Long commerceId
As suggested by #ParagFlume, I could change the argument type to String, but that would force me to do a casting to Long in all method using the #ModelAttribute("commerceId").
My solution was create a Wrapper object containing all concerning commerce.
public class CommerceData {
private Long commerceId;
public Long getCommerceId() {
return commerceId;
}
public void setCommerceId(Long commerceId) {
this.commerceId = commerceId;
}
}
When user has logged, I create my POJO CommerceData and then I set the Long value as attribute. Since I need the model attribute lives in session, I had create manually ant not being injected with #ModelAttribute annotation, because Spring MVC claimed the value doesn't exist.
#Controller
#SessionAttributes("data")
public class IndexController {
private static final Logger LOGGER = LoggerFactory.getLogger(IndexController.class);
#RequestMapping({"/", "/index"})
public String home(Principal principal, ModelMap model) {
if (model.get("data") == null) {
LOGGER.info("Inicializando código de comercio");
CommerceData data = new CommerceData();
String name = principal.getName();
data.setCommerceId(Long.parseLong(name));
model.addAttribute("data", data);
}
return "index";
}
}
But I believe that Spring MVC should support inject Wrappers values (Long, Integer, Double) using #ModelAttribute and not only POJO classes. Maybe I wrong but I could not do work with #ModelAttribute("commerceId") Long commerceId.
primitives work better with #modelattribute in spring .
i also faced issue with Long type but when used long then issue is resolved.

How can I programmatically call the same validator that runs on a #RequestMethod method with a #Valid parameter in Spring?

I have a class with hibernate's validation annotation on some fields (such as #NotNull and #Size(min = 4, max = 50), etc...)
public class MyClass {
Long id;
#NotEmpty
#Size(min = 4, max = 50)
String machineName;
#NotEmpty
#Size(min = 4, max = 50)
String humanName;
// Getters, setters, etc…
}
I also have a custom controller that acts as a JSON API, and a JSON deserializer that creates MyClass objects when API methods are called. In my custom controller I have a method to create a new object of that type:
#RequestMapping(method = RequestMethod.POST)
public long createMyObject(#RequestBody #Valid MyClass newObj) {
// Create the object in the database
return newObj.getId();
}
and another method that updates an existing object
#RequestMapping(method = RequestMethod.PUT)
public void updateMyObject(#RequestBody MyClass updatedObj) {
MyClass existingObj = // Get existing obj from DB by updatedObj.getId();
// Do some secondary validation, such as making sure that a specific
// field remains unchanged compared to the existing instance
if (existingObj.getMachineName() != null &&
!existingObj.getMachineName().equals(updatedObj.getMachineName())) {
throw new CannotChangeMachineNameException();
}
else {
updatedObj.setMachineName(existingObj.getMachineName());
}
// [HERE IS WHERE I WANT THE MAGIC TO HAPPEN]
// Save updatedObj to the database
}
While I can use #Valid in createMyObject, I cannot use it in updateMyObject because our API implementation requires that machineName remains unchanged - users can call the API with a JSON object that either excludes machineName entirely or populate it with the same value that exists in the database.*
Before saving the updated object to the database I want to call the same validator that having the #Valid annotation would cause to be called. How can I find this validator and use it?
Nothing says you need to use #Valid in your controller methods only. Why not make a validation method that accepts a parameter you annotate as #Valid, then just return that same parameter.
Like this:
public Book validateBook(#Valid Book book) {
return book;
}
Looks like an alternative would be to use Hibernate's validation package. Here's it's documentation.
Basically, you get a Validator from a ValidationFactory, and then use the validator like this:
#Test
public void manufacturerIsNull() {
Car car = new Car(null, "DD-AB-123", 4);
Set<ConstraintViolation<Car>> constraintViolations =
validator.validate(car);
assertEquals(1, constraintViolations.size());
assertEquals("may not be null", constraintViolations.iterator().next().getMessage());
}

Categories