Chain of REST path in spring boot controller - java

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.

Related

Switch Schema based on call from rest controller

We have two products: Product A and Product B and both have different rest controllers. Both of these controllers are calling the same common service and the common service calling the common dao methods. I want to select the schema in the common daos methods based on which products the call has been made. So for example, if the call is coming from the Product A controller then select schema as A or else B.
I wanted to use the same database connection and change the schema based on which controller the call is made. One possible solution would be to pass the schema name from the controller layer to the service layer which in turn passes it to the dao layer. I want to avoid having two different data sources and then switching between them dynamically.
So is there any other better way that we could do this?
Note: We are using Mybatis as our persistence layer.
Please find the sample code :
Controller for ProductA
#Component
public class ProductA{
private final CommonService commonService;
public ProductA(CommonService commonService){
this.commonService = commonService;
}
#GET
#Path("/test/dbSchema")
#Produces(MediaType.APPLICATION_JSON)
public Response getTotalProducts(){
// Calling common service method here to get total products
}
}
Controller for ProductB
#Component
public class ProductB{
private final CommonService commonService;
public ProductA(CommonService commonService){
this.commonService = commonService;
}
#GET
#Path("/test/dbSchema")
#Produces(MediaType.APPLICATION_JSON)
public Response getTotalProducts(){
// Calling common service method here to get total products
}
}
FYI: We are using Jersey
Thank you #ave for the response. I am able to solve it by using ThreadLocal as you mentioned. By setting the tenant id in the controller, I am able to get the particular tenant in the common service.
Please find the updated code below:
Controller for Product A
#Component
public class ProductA{
private final CommonService commonService;
public ProductA(CommonService commonService){
this.commonService = commonService;
}
public void setSchema(){
TenantContext.setCurrentTenant("ProductA");}
#GET
#Path("/test/dbSchema")
#Produces(MediaType.APPLICATION_JSON)
public Response getTotalProducts(){
setSchema();
// Calling common service method here to get total products
}}
Controller for Product B
#Component
public class ProductB{
private final CommonService commonService;
public ProductA(CommonService commonService){
this.commonService = commonService;
}
public void setSchema(){
TenantContext.setCurrentTenant("ProductA");}
#GET
#Path("/test/dbSchema")
#Produces(MediaType.APPLICATION_JSON)
public Response getTotalProducts(){
setSchema();
// Calling common service method here to get total products
}
}

Is it possible to use two controller classes with one URI in Java Spring?

I m working on this project where am I trying to access two different controllers with the same URI. After trying to run it I m getting a BeanCreationException.
So it happens that I m getting an Error while creating a bean.
I hope there is a way to deal with this.
The error message that I m getting:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'requestMappingHandlerMapping' defined in
class path resource
[org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Invocation of init method failed; nested exception is
java.lang.IllegalStateException: Ambiguous mapping. Cannot map
'userController' method public java.lang.String
com.javalanguagezone.interviewtwitter.controller.UserController.overview(java.security.Principal,org.springframework.ui.Model)
to {[/overview],methods=[GET]}: There is already 'tweetController'
bean method
I m using as well Thymleaf for this project. The URI that I m for those two controllers: http://localhost:8080/api/overview.The two controllers are providing my Thymleaf page with information that i have to present at the same time with the URI just mentioned. With this, I m calling both controllers but I m getting a previously mentioned error.
The first controller class(TweetController):
#Controller
#Slf4j
public class TweetController {
private TweetService tweetService;
public TweetController(TweetService tweetService) {
this.tweetService = tweetService;
}
#GetMapping( "/overview")
public String tweetsFromUser(Principal principal, Model model) {
model.addAttribute("tweets",tweetService.tweetsFromUser(principal).size());
return "api/index";
}
}
The second controller class is:
#Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("/followers")
public String followers(Principal principal) {
userService.getUsersFollowers(principal);
return "api/index";
}
#GetMapping("/following")
public int following(Principal principal) {
return userService.getUsersFollowing(principal);
}
#GetMapping("/overview")
public String overview(Principal principal, Model model){
model.addAttribute("followers",userService.getUsersFollowers(principal));
model.addAttribute("following",userService.getUsersFollowing(principal));
return "api/index";
} }
My question: is there a way a fix it or I to look for another way around? I m relatively a newbie with Spring. Thank you for your help in advanced.
according to REST conventions, you should not have /overview, but /user/overview. You can set it by supplying #RequestMapping("/user") in your userController.
In the same way you would have "/tweet/overview" endpoint.
#Controller
#Slf4j
#RequestMapping("/tweet")
public class TweetController {
doing it any other way is against conventions, against Spring Rules and probably means you're doing something wrong. Spring does not allow two methods with same uri because it does not know which method exactly you would want to call.
upd: if you need logic, you can send parameters to GET: /overview?customParam=user
#GetMapping( "/overview")
public String tweetsFromUser(#RequestParam(value="customParam") String
param, Principal principal, Model model) {
// logic checking customParam...
But that CANNOT be in two different controllers. The only way to specify the controller, is through base-uri and parameters are not part of it.
Spring determines the method by 2 parameters: Mapping and HTTP method. There is no way, unless you modify Spring manually, to allow 3rd parameter in this case. Also, there is no 3rd parameter.
Alternatively, you can have 3rd controller with Mapping, that calls other 2 controllers when "/overview" endpoint is triggered. In that case, you need to remove the mapping from tweet and user - controllers.

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

Call method of Controller1 into another method of Controller2 in Spring

I did Google a lot to find my problem but I couldn't and sorry If this question already on the stack overflow because I have not find it.
First let take a look into the code
#Controller
public class Controller1 {
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView methodHandler(Parameters) {
}
public int calculation(int i){
//Some Calcucation
return i;
}
}
and second controller is
#Controller
public class Controller2 {
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView methodHandler(Parameters) {
//In this I want to call the calculation(1) method of controller1.
}
}
My question is that is there any way to call the method of calculation() of controler1 in to controller2. But remember I don't want to make method static in controller1.Is there anyway to call it without make it static?
Thanks
Yasir
You should create service bean for example in configuration file (or use # one of the annotaions) and inject it into controller. For example ()
#Configuration
public class MyConfig {
#Bean
public MyService myService(){
return new MyService();
}
}
#Controller
public class Controller1 {
#Autowire
private MyService myService;
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView First(Parameters) {
myService.calculation();
}
}
#Controller
public class Controller2 {
#Autowire
private MyBean myBean;
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView First(Parameters) {
myService.calculation();
}
}
Your controllers should not call each other. If there is a logic which needs to be used by both controllers, it is much better to put that into separate bean, which will be used by both controllers. Then you can simply inject that bean to whicheveer controller neccessary. Try not to put any business logic to controllers, try tu put it to specialized class instead which will be web independent if possible and will accept web agnostic business data as user email, account number etc. No http request or response. This way your class with actual logic is reusable and can be unit tested much more easily. Also, if there is state, it should be contained in your classes outside controllers. Controllers should be stateless and not contail any state at all.
When using MVC pattern and you are deciding where to put your logic, you should separate business logic into model and into controllers you should put only logic regarding user interaction, as explained in this stack overflow post.

Categories