DEV Community

Cover image for Create a Service and Controller using Spring Boot 3
William
William

Posted on

Create a Service and Controller using Spring Boot 3

Continue the implementation, let's use the repository class in the news service and controller classes. In this post you will see the API working

Create the Service class

Create a class called ProductService inside the new folder services.

package com.spring.services;

import com.spring.models.Product;
import com.spring.repositories.ProductRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class ProductService {

    @Autowired
    private ProductRepository repository;

    @Transactional
    public Product create(Product product) {
        return repository.save(product);
    }

    public Product findById(Long id) {
        return repository.findById(id).orElse(new Product());
    }

    public List<Product> findAll() {
        return repository.findAll();
    }

    @Transactional
    public Product update(Product product, Long id) {
        var productPersisted = findById(id);
        if (!Objects.equals(productPersisted.getId(), id)) {
            return productPersisted;
        }
        BeanUtils.copyProperties(product, productPersisted, "id");

        return repository.save(productPersisted);
    }

    @Transactional
    public void delete(Long id) {
        repository.deleteById(id);
    }
}

Enter fullscreen mode Exit fullscreen mode

This service class will take care about the methods create a product, update a product, find one specific or all products.
Reading the name of each method you can identify what it does.

This class is using the ProductRepository class to access the database information from product entity. The @Autowired annotation tells to the Spring takes care about the instancia. So we don't need to worry about that.

The @Transactional annotation is very important to guarantee that if happened one error, the commit will dont make in the database.

If you remenber, we dont create the methods findAll, findById, save or delete to repository, but thanks to Spring this was created automatically when we used the JpaRepository inside the ProductRepository

Create the Product DTO class

This class is to guarantee that the client can't informe value to id or any other information that inst insite the DTO classe.
Create a record class called ProductCreateUpdate inside the new folder models/dto.

package com.spring.models.dto;

public record ProductCreateUpdate(String name, Double price, String description) {
}
Enter fullscreen mode Exit fullscreen mode

This DTO don't have the id information because we don't want the user change the value ou informe one when created.

Create the Controller class

Create a class called ProductController inside the new folder controllers.

package com.spring.controllers;

import com.spring.models.Product;
import com.spring.models.dto.ProductCreateUpdate;
import com.spring.services.ProductService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("api/product")
public class ProductController {

    @Autowired
    private ProductService service;

    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE,
                produces = MediaType.APPLICATION_JSON_VALUE)
    public Product create(@RequestBody ProductCreateUpdate productCreate) {
        Product product = new Product();
        BeanUtils.copyProperties(productCreate, product);
        return service.create(product);
    }

    @GetMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public Product findById(@PathVariable(value = "id") Long id){
        return service.findById(id);
    }

    @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
    public List<Product> findAll(){
        return service.findAll();
    }

    @PutMapping(path = "/{id}",
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public Product update(@PathVariable(value = "id") Long id, @RequestBody ProductCreateUpdate productUpdate) {
        Product product = new Product();
        BeanUtils.copyProperties(productUpdate, product);
        return service.update(product, id);
    }

    @DeleteMapping(path = "/{id}")
    public void delete(@PathVariable(value = "id") Long id){
        service.delete(id);
    }

}
Enter fullscreen mode Exit fullscreen mode

This class will be responsible for connect the client with the application by HTTP connections.
In the start the class is using @RestController to inform that is a Rest API and @RequestMapping annotation to inform that the base path is api/product.

How we need to access the database information, the class has a var of type ProductService.

Before each explation of methods, we need understand what the next annotations do.

  • @RequestBody: Inform that the information will come from the body of the request and of the type that variable.
  • @PathVariable: Inform that the information will come from the specific part of the path an of the type that variable.
  • @PostMapping: The request need be by HTTP Post
  • @GetMapping: The request need be by HTTP Post
  • @PutMapping: The request need be by HTTP PUT
  • @DeleteMapping: The request need be by HTTP DELETE

In the first method create is about create a new product that data recived from the body in json format, the type of information is ProductCreateUpdate, will called the method create from the service and return the new product in json format.

The method findById is about find one specific product by the id informad in the path api/product/{id}, will called the method findById from the service and return the product a empty Product if not exist, both in json format.

The method findAll will find all products, will called the method findAll from the service and return a list of products in json format.

The method update is about update one existing product by the id informad in the path api/product/{id} with the updated data recived from the body in json format, the type of information is ProductCreateUpdate, will called the method update from the service and return the updated product in json format. If doesn't exist a product for the id informad, will return an empty product.

To finish with the last one, the delete is about delete a product by the id informad in the path api/product/{id}, will called the method delete from the service.

I know that the code can be improve, it will be done in the future.

Testing the endpoints

For the test I'll use the Postman, it's easy do install.

post create
Here is creating a product by POST in localhost:8080/api/product with below body

{
    "name": "Mouse",
    "price": 50.00,
    "description": "Mouse Wireless 10000Dpi"
}
Enter fullscreen mode Exit fullscreen mode

get all
Here is find all products by GET in localhost:8080/api/product


get id
Here is find a product with ID 1 by GET in localhost:8080/api/product/1

If you select all products in the database, you'll see the same information.


put
Here is updating a product with ID 1 by PUT in localhost:8080/api/product with below body

{
    "name": "Mouse Professional",
    "price": 45.00,
    "description": "Mouse Wireless 8000Dpi"
}
Enter fullscreen mode Exit fullscreen mode

Now in the database the information is updated.


delete
Here is delete a product with ID 1 by DELETE in localhost:8080/api/product/1

Now the product doesn't exist in the database anymore.

Conclusion

In this post, we create the Service and Controller classes about Product, also we saw important annotations about the Rest

Next Step

In the next step we will improve the API using ResponseEntity with the better response code and exception control.

Top comments (2)

Collapse
 
ichwansh profile image
Ichwan Sholihin • Edited

Overall, this article is great, and I appreciate your effort in creating it. However, I would like to suggest a small addition to the section. I usually prefer utilizing the interface at the service layer. Why? The primary objective is to advocate for loose coupling and enhance flexibility in the codebase. This approach makes it easier to swap implementations or introduce new ones in the future. I believe this addition can serve as a valuable lesson for others. For more information, you can refer to this article. I hope this suggestion proves helpful.

Collapse
 
wkreuch profile image
William

Hi Ichwan
Thanks for help to improve this post and about your good article.
You are right, it's better create a interface and be implement by a class.
I'm only did that because the extension the article, but I'm doing the new smaller posts.