Spring MVC convert Spring Rest Api - java

I have a spring mvc project, i want to convert spring rest api project.
Here my sample codes;
My Controller
#Controller
#RequestMapping("/student")
public class StudentController {
#Autowired
private StudentService studentService;
#Autowired
private SchoolService schoolService;
#GetMapping("/list")
public String ListStudents(Model model) {
List<Student> theStudent = studentService.getStudents();
model.addAttribute("students", theStudent);
return "List-student";
}
#GetMapping("/addNewStudent")
public String addNewStudent(Model model) {
Student theStudent = new Student();
model.addAttribute("student", theStudent);
List<School> theSchool = schoolService.getSchools();
model.addAttribute("schools", theSchool);
return "student-form";
}
#PostMapping("/saveStudent")
public String saveStudent(#ModelAttribute("student") Student theStudent) {
studentService.saveStudent(theStudent);
return "redirect:/student/list";
}
#GetMapping("/showFormforUpdate")
public String showFormForUpdate(#RequestParam("studentID") int theId, Model model) {
Student theStudent = studentService.getStudent(theId);
model.addAttribute(theStudent);
List<School> theSchool = schoolService.getSchools();
model.addAttribute("schools", theSchool);
return "student-form";
}
#GetMapping("/deleteStudent")
public String deleteStudent(#RequestParam("studentID") int theId, Model model) {
studentService.deleteStudent(theId);
return "redirect:/student/list";
}
}
My DAOImpl class
#Repository
public class StudenDAOImpl implements StudentDAO {
#Autowired
private SessionFactory sessionFactory;
#Override
#Transactional
public List<Student> getStudents() {
Session currentSession=sessionFactory.getCurrentSession();
Query<Student> theQuery = currentSession.createQuery("from Student", Student.class);
List<Student> students=theQuery.getResultList();
return students;
}
#Override
public void saveStudent(Student theStudent) {
Session currentSession=sessionFactory.getCurrentSession();
currentSession.saveOrUpdate(theStudent);
}
#Override
public Student getStudent(int theId) {
Session currentSession=sessionFactory.getCurrentSession();
Student theStudent=currentSession.get(Student.class, theId);
return theStudent;
}
#Override
public void deleteStudent(int theId) {
Session currentSession=sessionFactory.getCurrentSession();
Query theQuery=currentSession.createQuery("delete from Student where id=:studentID");
theQuery.setParameter("studentID", theId);
theQuery.executeUpdate();
}
}
Actually i know the places i need to change. But i don't know very well spring Rest Api. In my code i send many attributes in my view(jsp file) and i catch this attributes here . But RestApi does not have views. I need to delete attribute functions. But what can i add instead of attribute functions? I am new the RestApi please help me what should i do?

welcome to stack overflow.
Rest APIs work like this:
You expose an endpoint and a verb (for example GET at /students)
Some client calls your endpoint (makes a call to the server that holds your app)
Your server delegates the call to a controller function ( a function with a #GetMapping("/students") for example )
The controller function sends a response to the client (With spring, your method returns an object or a ResponseEntity)
REST APIs receive requests , process said request (and request data if present) and tipically return some data with a status code that indicates if the operation was successful.
With spring you do something like this:
#GetMapping("/students")
ResponseEntity<List<Student>> listStudents() {
List<Student> stds = getStudents(); // call some service or DB
return new ResponseEntity<>(stds, HttpStatus.OK);
}
When you make a GET request to /students, spring will delegate the handling of the request to the listStudents method which will get the students and return data.
REST APIs tipically work with JSON, so the list of students you'll be returning will be serialized into a json list.
If you want to customize the student json structure you can use Jackson:
public class Student {
#JsonProperty("cool_name") private String name;
// getters and setter
}
A rest api does not work with views or JSP. They tipically work with http requests and responses.
If you're working with spring mvc instead of spring boot, check this article:
https://viralpatel.net/blogs/spring-4-mvc-rest-example-json/
If you can use spring boot (which I highly recommend) , check this:
https://spring.io/guides/gs/rest-service/
You should also annotate your REST controllers with #RestController for them to automatically handle rest calls.
Hope this helps and welcome to stack overflow

Related

How to use spring-cloud-gateway for authentication and authorization?

I am very confused about this architecture. I am not even sure is it possible.
I have more than 10 microservises and a API Gateway. I want to add authentication and authorization to this system. One of this services is authentication-server and it has
an endpoint which is /signin
#PostMapping(value = "/signin")
public UserLoginResponse login(#Valid #RequestBody UserLoginRequest userLoginRequest) {
return authService.login(userLoginRequest);
}
public class UserLoginResponse {
private String accessToken; //accessToken is jwt token and it has ROLE field.
}
public class UserLoginRequest {
private String username;
private String password;
}
Here is the confusing part for me: Right now gateway creates code duplication. When I add to a new endpoint, I need to add almost same controller/service/models to API Gateway.
For example:
Lets say microservice A has /product endpoint, these are (veeery roughly) the classes I should have
// Controller
class ProductController {
#GetMapping("/product/{id}")
public ProductResponse getProduct(#PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
public getProduct(String id){
return productRepository.get(id);
}
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
Our team also has implemented classes in the gateway.
//Controller has authorization with #PreAuthorize annotation.
#RestController
#PreAuthorize("hasAnyRole('USER', 'ADMIN')")
class ProductController {
#GetMapping("/product/{id}")
public ProductResponse getProduct(PathVariable String id) {
return productService.getProduct(id)
}
}
// Service
class ProductService {
private final ClientApi productClientApi;
public ProductService(ClientApi productClientApi) {
this.productClientApi = productClientApi;
}
public getProduct(String id){
return productClientApi.getProduct(id);
}
}
//This is feign client. It makes http requests to product-api
#FeignClient(
"product-api",
url = "\${product-api.base-url}",
configuration = [FeignClientConfiguration::class]
)
interface ClientApi(){
#GetMapping( value = {"product/{id}"}, consumes = {"application/json"} )
ProductResponse getProduct(#PathVariable String id);
}
// Response DTO
class ProductResponse(){
private String id;
private String name;
}
When a request comes to /product its jwt token controlled here and if it has proper permission, it goes to the service layer,
Service layer makes request to product-api(which is microservice A)
returns the response
Question: There should be easier way. Every new endpoint in the services costs us code duplication in Gateway. I think I want just routing. Whatever comes to gateway directly should be routed to services and it should still has the authentication/authorization responsibility. I know that I can do that as below with spring-cloud-gateway but I couldn't figure out how can i do that with authentication and authorization. Can anyone explain me that am i thinking wrong ?
spring:
application:
name: "API-GATEWAY"
cloud:
gateway:
routes:
- id: product-service
uri: 'http://product-url:8083'
predicates:
- Path=/product/**

HttpMediaTypeNotSupportedException in a basic Spring boot CRUD

I am starting to learn Spring Boot and I am developing a basic CRUD connected to a MySQL Database and I am testing this app with Postman. Although some functions as "getByID" or "getList" is already working I am founding some problemns with "Save" function because I got a HttpMediaTypeNotSupportedException that I don't know how to resolve.
This is my main codes.
Application:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Controller:
#RestController
#CrossOrigin(origins="http://localhost:4200")
#RequestMapping(value="/api")
public class Controller {
#Autowired
private PruebaService pruebaservice;
#PostMapping("save-prueba")
public boolean savePrueba(#RequestBody Prueba prueba) {
return pruebaservice.savePrueba(prueba);
}
//Other methods for the CRUD
}
Service:
#Service
#Transactional
public class PruebaService {
#Autowired
private PruebaRepository pruebadao;
public boolean savePrueba(Prueba prueba) {
return pruebadao.savePrueba(prueba);
}
//Other methods for the CRUD
}
Repository:
#Repository
public class PruebaRepository {
#Autowired
private SessionFactory sessionFactory;
public boolean savePrueba(Prueba prueba) {
boolean status = false;
try {
sessionFactory.openSession().save(prueba);
status = true;
} catch (Exception e) {
e.printStackTrace();
}
return status;
}
//Other methods for the CRUD
}
Model:
#Entity
#Table(name="PRUEBA")
public class Prueba {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int idprueba;
private String columna;
//Getters and setters
}
And finally this is the postman that I am using for the tests:
It would be very appreciated if someone can help me. Thanks in advance.
Your Controller Class should be change given as below.
#RestController
#CrossOrigin(origins="http://localhost:4200")
#RequestMapping(value="/api")
public class Controller {
#Autowired
private PruebaService pruebaservice;
#PostMapping(value = "/save-prueba", produces = MediaType.APPLICATION_JSON_VALUE)
public boolean savePrueba(#RequestBody Prueba prueba) {
return pruebaservice.savePrueba(prueba);
}
//Other methods for the CRUD
}
In Postman select raw and select type JSON and test like this
In Postman. under Body, select raw and choose JSON from the drop-down menu that appears. Then write the JSON that is the request body. You can't use form-data or x-www-form-urlencoded with #RequestBody, they are used when the binding is #ModelAttribute.
Like the former users claimed to use the raw JSON format in Postman is a good way to ensure the integrity of your Postman requests, you should also handle this error case in your application in a graceful manner.
Add an ErrorController to catch and handle Exceptions thrown at Runtime by your other controller endpoints:
#ControllerAdvice
public class ErrorController {
private static final Logger LOGGER = LoggerFactory.getLogger(ErrorController.class);
#ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResponseEntity<String> handleUnsupportedMediaTypeException (HttpMediaTypeNotSupportedException exception) {
LOGGER.error(exception.getMessage());
return new ResponseEntity<>(String.format("Unsupported media type: %s", exception.getRootCause().getMessage()), HttpStatus.UNSUPPORTED_MEDIA_TYPE);
}
}
This way you can make sure that other requests with a wrong formatted body do not crash your application or do something else harmful but you even tell the requestor what has gone wrong.

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.

Spring #Transactional TransactionRequiredException or RollbackException

I have read a lot of #Transactional annotation, I saw stackoverflow answers but it does not help me. So I am creating my question.
My case is to save user with unique email. In DB I have user with email xxx#xxx.com, and I am saving user with the same email address. For saving I have to use entityManager.merge() because of this post thymeleaf binding collections it is not important.
First example:
#Controller
public class EmployeeController extends AbstractCrudController {
// rest of code (...)
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
public String processNewEmployee(Model model, #ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
prepareUserForm(model);
if (!result.hasErrors()) {
try {
saveEmployee(employee);
model.addAttribute("success", true);
} catch (Exception e) {
model.addAttribute("error", true);
}
}
return "crud/employee/create";
}
#Transactional
public void saveEmployee(User employee) {
entityManager.merge(employee);
}
private void prepareUserForm(Model model) {
HashSet<Position> positions = new HashSet<Position>(positionRepository.findByEnabledTrueOrderByNameAsc());
HashSet<Role> roles = new HashSet<Role>(roleRepository.findAll());
User employee = new User();
model.addAttribute("employee", employee);
model.addAttribute("allPositions", positions);
model.addAttribute("allRoles", roles);
}
}
This code is throwing TransactionRequiredException, I do not know why? It looks like #Transactional annotation did not work, so I moved annotation to processNewEmployee()
Second example:
#Controller
public class EmployeeController extends AbstractCrudController {
// rest of code (...)
#Transactional
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
public String processNewEmployee(Model model, #ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
prepareUserForm(model);
if (!result.hasErrors()) {
try {
entityManager.merge(employee);
model.addAttribute("success", true);
} catch (Exception e) {
model.addAttribute("error", true);
}
}
return "crud/employee/create";
}
private void prepareUserForm(Model model) { /*(.....)*/ }
}
And this code is throwing PersistenceException (because of ConstraintViolationException) and of course I got "Transaction marked as rollbackOnly" exeption.
When I try to save email which not exists this code works fine, so I thing that #Transactional annotation is configured well.
If this is important I am putting my TransationManagersConfig:
#Configuration
#EnableTransactionManagement
public class TransactionManagersConfig implements TransactionManagementConfigurer {
#Autowired
private EntityManagerFactory emf;
#Autowired
private DataSource dataSource;
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm =
new JpaTransactionManager();
tm.setEntityManagerFactory(emf);
tm.setDataSource(dataSource);
return tm;
}
public PlatformTransactionManager annotationDrivenTransactionManager() {
return transactionManager();
}
}
Could you explain my what I am doing wrong and suggest possible solution of this problem?
Solution:
Thanks to R4J I have created UserService and in my EmployeeController I am using it instead of entityManager.merge() now it works fine
#Service
public class UserService {
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void merge(User user) {
entityManager.merge(user);
}
}
And EmployeeController:
#Controller
public class EmployeeController extends AbstractCrudController {
#Autowired
private UserService userService;
#RequestMapping(value = urlFragment + "/create", method = RequestMethod.POST)
public String processNewEmployee(Model model, #ModelAttribute("employee") User employee, BindingResult result, HttpServletRequest request) {
// (.....)
userService.merge(employee);
// (.....)
}
}
Your transactions don't work because you call directly 'this.saveEmployee(...)' from your 'public String processNewEmployee' method.
How come?
When you add #Transactional, Spring creates a Proxy for your Component and proxies all public methods. So when Spring itself calls your method as a HTTP Rest Request it is considered an external call that goes properly through a Proxy and new Transaction is started as required and code works.
But when you have a Proxied Component and you call 'this.saveEmployee' (that has #Transactional annotation) inside your class code you are actually bypassing the Proxy Spring has created and new Transaction is not started.
Solution:
Extract entire database logic to some sort of a Service or DAO and just Autowire it to your Rest Controller. Then everything should work like a charm.
You should avoid direct database access from Controllers anyway as it is not a very good practice. Controller should be as thin as possible and contain no business logic because it is just a 'way to access' your system. If your entire logic is in the 'domain' then you can add other ways to run business functionalities (like new user creation) in a matter of just few lines of code.

Spring restful webservice returning JSON

I just took the tutorial over at Spring.io http://spring.io/guides/gs/rest-service/ and created a simple rest service. But, does anybody know how I can return multiple objects in JSON format? If I for instance have a person class with a name and an id, how can I add three persons to /persons?
You can use the #ResponseBody annotation and just return whatever you want, providing that those objects can be jsonized.
For example, you can have a bean like this:
#Data
public class SomePojo {
private String someProp;
private List<String> someListOfProps;
}
and then in your controller you can have:
#ResponseBody
#RequestMapping("/someRequestMapping")
public List<SomePojo> getSomePojos(){
return Arrays.<String>asList(new SomePojo("someProp", Arrays.<String>asList("prop1", "prop2"));
}
and Spring by default would use its Jackson mapper to do it, so you'd get a response like:
[{"someProp":"someProp", "someListOfProps": ["prop1", "prop2"]}]
The same way, you can bind to some objects, but this time, using the #RequestBody annotation, where jackson would be used this time to pre-convert the json for you.
what you can do is
#RequestMapping("/someOtherRequestMapping")
public void doStuff(#RequestBody List<SomePojo> somePojos) {
//do stuff with the pojos
}
Try returning a list from the method:
#RequestMapping("/greetings")
public #ResponseBody List<Greeting> greetings(
#RequestParam(value="name", required=false, defaultValue="World") String name) {
return Arrays.asList(new Greeting(counter.incrementAndGet(),String.format(template, name)));
}

Categories