how to properly handle POST in a custom Spring Data Rest controller? - java

in my Spring Data Rest application I have a standard repository:
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(#Param("name") String name);
}
I also have a custom controller, which will implement some additional logic upon HTTP POST:
#RestController
#RequestMapping("/people")
public class PersonController {
#RequestMapping(value = "/**", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> savePerson(#RequestBody Person person, UriComponentsBuilder b, #RequestParam Map<String, ?> id) {
UriComponents uriComponents =
b.path("/people/").buildAndExpand();
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(uriComponents.toUri());
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World\n\n", responseHeaders, HttpStatus.CREATED);
}
}
What is the proper way to save my "Person" entity within this controller, since I'm not using Hibernate Entity Manager explicitly?
The "person" parameter is just a POJO, so it does not have any persistance CRUD methods.

If the Person class used in the PersonRepository is same as whatever you are using in the controller to map RequestBody to, then in the controller method you can just do personRepository.save(person) -- Assuming personRepository is an Autowired instance of PersonRepository class.
I am guessing that, you are experimenting with spring data rest https://spring.io/guides/gs/accessing-data-rest/ . If that's the case, you might have in-memory database com.h2database:h2 in your class path. Thats why, in the given example, everything is just working without you even configuring the database or adding any JPA annotations to your person class. So, you can still do personRepository.save(person) from your custom controller without having any of the JPA annotations in your Person class.

Related

For multipart request, how to use using #ModelAttribute and Hashmap together?

First of all, thank you in advance for your support.
My problem;
First I am successfully getting my specific parameters in Employer. However, I also have a constantly changing parameter list in request. I want to get them with Map too.
My dto:
public class Employee {
private String name;
private MultipartFile document;
}
#RequestMapping(path = "/employee", method = POST, consumes = { MediaType.MULTIPART_FORM_DATA_VALUE })
public Mono<Employee> saveEmployee(#ModelAttribute Employee employee, Map<String,Object> otherValues) {
System.out.println(otherValues.get("key1").toString());
return employeeService.save(employee);
}
I attached a request example aslo.
NOTE:
I used #RequestParam, #RequestPart before Map<String,Object> otherValues like this;
#RequestParam Map<String,Object> otherValues
#RequestPart Map<String,Object> otherValues
But I still couldn't get the rest of the data.
When you want to get values from form data, you can create a model like a DTO, with field of both entity (employee) and additional data (otherValues). Because #ModelAttribute can bind data to only one named model attribute
This is working for Spring MVC. But When you work with Spring WebFlux for reactive applications, it doesn't work.
#RequestMapping(path = "/employee", method = POST, consumes ={MediaType.MULTIPART_FORM_DATA_VALUE })
public Employee saveEmployee(
#ModelAttribute Employee employee,
#RequestParam Map<Object,Object> otherValues,
#RequestParam("file") MultipartFile file) {
System.out.println(otherValues.get("key1").toString());
return employeeService.save(employee);
}
Main trick is to use Object for key and value;
#RequestParam Map<Object,Object> otherValues

How send a Map<String, String> using #PathVariable from Spring MVC

I want to receive a Map<Timestamp, Integer> as PathVariable using Spring MVC. Is this possible?
This is my controller:
#ApiOperation(value = "Some Api", produces = MediaType.APPLICATION_JSON_VALUE)
#PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<RestResult> createApi(#RequestPart (value="image", required = false) MultipartFile banner, SomeRequest request){
RestResult result = new RestResult();
return new ResponseEntity<>(result, HttpStatus.CREATED);
}
This is my request:
#Getter
#Setter
public class SomeRequest implements Serializable {
#NonNull
private MultiValueMap<Timestamp, Integer> someValue;
}
As one of the way you could use Spring's HandlerMethodArgumentResolver and implement its supportsParameter() and resolveArgument() methods. The last one accepts the method parameter and web request, which you can use to get any required data and compose the object whatever you want.
The argument resolver is invoked before the controller method and makes the required argument transformation.
Update:
You have to register the resolver to make it active. If you use spring-boot, you just need to define it as a bean.
Hope it will help.

Chain of REST path in spring boot controller

I develope RESTful back-end app with spring boot. I find out how to use annotation in the class:
#RestController
#RequestMapping(path = "/users")
public class User{
// rest of code!
}
But every user has orders and any orders has items! So I design rest API like this:
/users /users/{user_id}
/users/{user_id}/orders
/users/{user_id}/orders/{order_id}
/users/{user_id}/orders/{order_id}/items
/users/{user_id}/orders/{order_id}/items/{item_id}
/users/{user_id}/cart
Now, what is best practice or normal implementation for this design in spring boot? How can I handle APIs with Spring Boot?
Continue and use the annotated method inside the class:
#RestController
#RequestMapping(path = "/users")
public class UserController {
#GetMapping("/{user_id}")
public User getUserById(#PathVariable("user_id") String userId) { }
#GetMapping("/{user_id}/orders")
public List<Order> getOrdersByUserId(#PathVariable("user_id") String userId) { }
#GetMapping("/{user_id}/orders/{order_id}")
public List<Order> getOrdersByIdAndUserId(#PathVariable("user_id") String userId, #PathVariable("order_id") String orderId) { }
// ... and so on
}
Don't forget the implementation inside the {} brackets.
The example method getOrdersByIdAndUserId is mapped to the GET method of path /users/{user_id}/orders/{order_id} where /users is a common part defined as the class mapping and the rest with the method.
I suggest you rename the class User to UserController, because the User is a suitable name for the returned entity.

Can't use ReposiotryResource endpoint in spring service

I am using #RepositoryResource annotation on my Reposioptory interface with this code:
#RepositoryRestResource(collectionResourceRel = "rest", path = "rest")
public interface HoliDayRepository extends CrudRepository<HoliDayEntity, Integer> {
HoliDayEntity findOne(Integer id);
}
and i have alsoe added RequestMapping("rest) in controller class
#RestController
#RequestMapping("/rest")
public class DayController {}
but when i start spring boot application and try this link :http://localhost:8080/rest i got 404 error also while building application i have ResourceNotFoumd exceptions how should i manage these errors?
with spring boot you don't need to create your own controller; also make sure your web application mapping is different to the one you use for spring data, for example you can set in application.properties spring.data.rest.base-path: /api
Have a look at this example:
public interface PersonRepository extends JpaRepository<Person, UUID> {
List<Person> findByAddress(String address);
}
with just this code you should able to access spring data repositories here: http://localhost:8080/api and the person endpoint here http://localhost:8080/api/person
Have a look at this tutorial: https://spring.io/guides/tutorials/react-and-spring-data-rest/ or this example: https://github.com/Paizo/SpringBootCamelStreamsExample
You need a method which should be called when you hit your endpoint.
try below and also check spring example:
https://spring.io/guides/tutorials/bookmarks/
#Autowired
private HoliDayRepository holiDayRepository; //your repository to execute the query
#GetMapping(value = "/{id}")//you can use #RequestMapping(method = RequestMethod.GET, value = "/{holida}")
public ResponseEntity<HoliDayEntity > getHolidayById(#PathVariable("id") Integer id) {
HoliDayEntity holiDayEntityresponse = productOperations.getProductById(id);
return new ResponseEntity<>(holiDayEntityresponse , HttpStatus.OK);
}
EDIT:
As pointed by Gimby, this is not applicable when #RepositoryRestResource is used. Both the code and the tutorial attached are refering to creating new REST service by creating the controller

Exception using Spring Data JPA and QueryDsl via REST Controller

I'm trying to implement a controller method similar to how is documented in the latest Gosling release train of Spring Data that supports QueryDsl. I've implemented the controller as shown in the example in the docs at http://docs.spring.io/spring-data/jpa/docs/1.9.0.RELEASE/reference/html/#core.web.type-safe. Everything compiles and when I start the application (using Spring Boot 1.2.5.RELEASE), everything starts fine.
However, when I try to call my rest endpoint, I always get the following exception:
org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mysema.query.types.Predicate]: Specified class is an interface
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:101)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:80)
My guess is that the QuerydslPredicateArgumentResolver is not being applied to the request, and thus the exception. But I see that the QuerydslPredicateArgumentResolver is registered as a bean when I query the Spring Boot manage endpoint /manage/beans. I have also ensured that #EnableSpringDataWebSupport is on my #Configuration class to no effect.
I have the controller annotated with #BasePathAwareController, since I'm using this with Spring Data REST and I want the methods to be under a similar path as the ones that Spring Data REST exposes. I also tried using #RepositoryRestController, but that didn't seem to matter. However, when using #RestController and putting it under a path that was different then the base path that Spring Data REST is using, things worked. So the question is, should it work?
The entire controller right now is:
#RestController
#RequestMapping(value = "/query")
public class AvailController
{
private final AvailRepository repo;
#Autowired
public AvailController(AvailRepository repository)
{
this.repo = repository;
}
#RequestMapping(value = "/avails", method = GET)
public #ResponseBody Page<Avail> getAvails(Model model,
#QuerydslPredicate(root = Avail.class) Predicate predicate,
Pageable pageable,
#RequestParam MultiValueMap<String, String> parameters)
{
return repo.findAll(predicate, pageable);
}
}
I had the same problem with instantiation of Predicate. In the example:
#Controller
#RequiredArgsConstructor(onConstructor = #__(#Autowired) )
class UserController {
private final UserRepository repository;
#RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, //
#QuerydslPredicate(root = User.class) Predicate predicate, //
#PageableDefault(sort = { "lastname", "firstname" }) Pageable pageable, //
#RequestParam MultiValueMap<String, String> parameters) {
(...)
(https://github.com/spring-projects/spring-data-examples/blob/master/web/querydsl/src/main/java/example/users/web/UserController.java#L42 ) is using just #Controller and I was using #RepositoryRestController, that seems to be the reason. #RestController also works for me.
I created https://jira.spring.io/browse/DATAREST-838
I also had this issue when trying to implement a custom controller that mimics the returned value as Spring Data REST. I wanted to inject QuerydslPredicate to the controller method and got the annoying 'BeanInstantiationException'.
I found a work around for this by adding the following configuration file to my application:
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE )
public class MvcConfig extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("repositoryExporterHandlerAdapter")
RequestMappingHandlerAdapter repositoryExporterHandlerAdapter;
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
List<HandlerMethodArgumentResolver> customArgumentResolvers = repositoryExporterHandlerAdapter.getCustomArgumentResolvers();
argumentResolvers.addAll(customArgumentResolvers);
}
}
See here for reference: https://jira.spring.io/browse/DATAREST-657

Categories