Creating a CRUD (Create, Read, Update, Delete) example in Spring Boot that adheres to SonarQube rules involves writing clean, efficient, and secure code. Below is a simplified example of a CRUD application that should meet SonarQube's quality standards. This example assumes you have basic Spring Boot setup done, including the necessary dependencies for Spring Web and Spring Data JPA in your pom.xml
or build.gradle
file.
Entity Class
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// Constructors, getters, and setters
public Product() {
}
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
Explanation
Sure, let's break down the Product
entity class line by line:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
These lines import annotations from the javax.persistence
package which are used for defining the class as an entity model for JPA (Java Persistence API).
@Entity
The @Entity
annotation specifies that the class is an entity. This means it's a JPA entity and should be mapped to a database table.
public class Product {
Defines the class named Product
. This class will be used to model the data that the application will be dealing with.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Id
marks theid
field as the primary key of the entity.@GeneratedValue
specifies that the primary key will be generated automatically by the database. The strategyGenerationType.IDENTITY
indicates that the database will automatically increment the value.
private String name;
private double price;
These lines declare two private fields, name
and price
, representing the product's name and price, respectively.
// Constructors, getters, and setters
public Product() {
}
Defines a no-argument constructor. JPA requires an entity to have a no-argument constructor.
public Product(String name, double price) {
this.name = name;
this.price = price;
}
This is a parameterized constructor used to create instances of Product
with a name and price.
The // Getters and Setters
comment indicates the following methods are getter and setter methods for the class fields. These methods are used to access and modify the private fields of the class.
public Long getId() {
return id;
}
The getId
method returns the value of the id
field.
public void setId(Long id) {
this.id = id;
}
The setId
method sets the value of the id
field.
public String getName() {
return name;
}
The getName
method returns the value of the name
field.
public void setName(String name) {
this.name = name;
}
The setName
method sets the value of the name
field.
public double getPrice() {
return price;
}
The getPrice
method returns the value of the price
field.
public void setPrice(double price) {
this.price = price;
}
The setPrice
method sets the value of the price
field.
The class ends with a closing curly brace }
.
Repository Interface
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
Explanation
The ProductRepository
interface is defined as follows:
import org.springframework.data.jpa.repository.JpaRepository;
This line imports the JpaRepository
interface from Spring Data JPA. The JpaRepository
is a JPA-specific extension of the Repository interface that provides a rich set of methods to manipulate entities.
public interface ProductRepository extends JpaRepository<Product, Long> {
}
public interface ProductRepository
: This declares a public interface namedProductRepository
. An interface in Java is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Interfaces cannot contain instance fields. The interface is intended to be implemented by classes that then provide implementations for the abstract methods declared by the interface.extends JpaRepository<Product, Long>
: This means thatProductRepository
extends theJpaRepository
interface. By extendingJpaRepository
,ProductRepository
inherits a large number of methods for performing operations such as saving, deleting, and finding entities.JpaRepository
takes two parameters: the entity type and the type of the entity's primary key. In this case,Product
is the entity type, andLong
is the type of its primary key. This setup enablesProductRepository
to work withProduct
entities and their IDs of typeLong
.
The ProductRepository
interface does not explicitly declare any methods because it inherits them from JpaRepository
. However, it can be used to declare custom query methods that are specific to the Product
entity
Service Class
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
private final ProductRepository productRepository;
@Autowired
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public List<Product> findAllProducts() {
return productRepository.findAll();
}
public Optional<Product> findProductById(Long id) {
return productRepository.findById(id);
}
public Product saveProduct(Product product) {
return productRepository.save(product);
}
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
public Product updateProduct(Long id, Product productDetails) {
Product product = productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found"));
product.setName(productDetails.getName());
product.setPrice(productDetails.getPrice());
return productRepository.save(product);
}
}
Explanation
Certainly, let's break down the ProductService
class line by line:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
- These lines import the necessary classes and annotations.
@Autowired
is used for dependency injection,@Service
marks the class as a service component in Spring,List
is used for collections, andOptional
is a container object which may or may not contain a non-null value.
@Service
- The
@Service
annotation marks this class as a service layer component, making it eligible for Spring's component scanning to detect and configure it as a Spring bean.
public class ProductService {
- Declares the
ProductService
class.
private final ProductRepository productRepository;
- Declares a private final field of type
ProductRepository
. Thefinal
keyword indicates that the reference toproductRepository
cannot be changed once it has been assigned.
@Autowired
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
- This is a constructor with
@Autowired
annotation, which means Spring will automatically inject an instance ofProductRepository
whenProductService
is created. This is an example of constructor-based dependency injection.
public List<Product> findAllProducts() {
return productRepository.findAll();
}
- Defines a method
findAllProducts
that callsfindAll
on theproductRepository
to retrieve all products from the database. It returns a list ofProduct
objects.
public Optional<Product> findProductById(Long id) {
return productRepository.findById(id);
}
- Defines a method
findProductById
which takes aLong id
as a parameter and returns anOptional<Product>
. It retrieves a product by its ID using thefindById
method of the repository.
public Product saveProduct(Product product) {
return productRepository.save(product);
}
- Defines a method
saveProduct
that takes aProduct
object as a parameter and returns aProduct
object. It saves the product to the database using thesave
method of the repository.
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
- Defines a method
deleteProduct
that takes aLong id
as a parameter. It deletes the product with the specified ID from the database using thedeleteById
method of the repository.
public Product updateProduct(Long id, Product productDetails) {
Product product = productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found"));
product.setName(productDetails.getName());
product.setPrice(productDetails.getPrice());
return productRepository.save(product);
}
- Defines a method
updateProduct
that takes aLong id
and aProduct productDetails
as parameters and returns aProduct
object. It first retrieves the product with the given ID from the database. If the product is not found, it throws aRuntimeException
. Then, it updates the product's name and price with the values fromproductDetails
and saves the updated product to the database using thesave
method of the repository.
Controller Class
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public List<Product> getAllProducts() {
return productService.findAllProducts();
}
@GetMapping("/{id}")
public Product getProductById(@PathVariable Long id) {
return productService.findProductById(id).orElseThrow(() -> new RuntimeException("Product not found"));
}
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.saveProduct(product);
}
@PutMapping("/{id}")
public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
return productService.updateProduct(id, product);
}
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
}
}
Explanation
The ProductController
class manages HTTP requests for CRUD operations on products. Here's a detailed explanation:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
These lines import the necessary classes and annotations from Spring Framework to build the REST controller.
@RestController
This annotation marks the class as a controller where every method returns a domain object instead of a view. It's a shorthand for including both @Controller
and @ResponseBody
.
@RequestMapping("/products")
Specifies that all request mappings in this controller will be prefixed with /products
.
public class ProductController {
Defines the class as a public controller class named ProductController
.
@Autowired
private ProductService productService;
Automatically injects the ProductService
instance, allowing the controller to use its services.
@GetMapping
public List<Product> getAllProducts() {
return productService.findAllProducts();
}
Defines a method to handle GET requests without any specific ID. It retrieves all products by calling findAllProducts
on the productService
.
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
return productService.findProductById(id)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
Handles GET requests for a specific product by its ID. Uses @PathVariable
to extract the ID from the URL. If the product is found, it returns the product wrapped in a 200 OK response; otherwise, it returns a 404 Not Found response.
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.saveProduct(product);
}
Handles POST requests to create a new product. The product details are passed in the request body, saved by the productService
, and then returned.
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product productDetails) {
return productService.updateProduct(id, productDetails)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
Manages PUT requests to update an existing product by its ID. The new product details are in the request body. If the product is successfully updated, it returns a 200 OK response; otherwise, it returns a 404 Not Found response.
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.ok().build();
}
Handles DELETE requests to remove a product by its ID. After deletion, it returns a 200 OK response to signify successful deletion.
This breakdown explains how the ProductController
class processes HTTP requests for CRUD operations on products, leveraging Spring Boot's annotations and patterns.
Controller using Response Entity
(same code)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
List<Product> products = productService.findAllProducts();
return ResponseEntity.ok(products);
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
return productService.findProductById(id)
.map(ResponseEntity::ok)
.orElseThrow(() -> new RuntimeException("Product not found"));
}
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product savedProduct = productService.saveProduct(product);
return ResponseEntity.ok(savedProduct);
}
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product product) {
Product updatedProduct = productService.updateProduct(id, product);
return ResponseEntity.ok(updatedProduct);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.ok().build();
}
}
Explanation in detail :
This Spring Boot ProductController
class is structured to handle RESTful web services for CRUD operations on a Product
entity. Here's a detailed explanation of each line:
import org.springframework.beans.factory.annotation.Autowired;
This imports theAutowired
annotation, enabling Spring's dependency injection mechanism.import org.springframework.http.ResponseEntity;
This imports theResponseEntity
class, which allows the controller to specify HTTP status codes and response bodies.import org.springframework.web.bind.annotation.*;
This imports Spring MVC annotations used for web controllers, including@RestController
,@RequestMapping
,@GetMapping
,@PostMapping
,@PutMapping
, and@DeleteMapping
.import java.util.List;
Imports theList
interface from the Java Collections framework for handling collections of objects.@RestController
Marks the class as a RESTful controller, indicating that each method's return value is automatically written directly to the HTTP response body.@RequestMapping("/api/products")
Defines the base URI for all resource URLs provided by this controller, setting the path to/api/products
.public class ProductController {
Declares theProductController
class, defining the structure for handling product-related requests.private final ProductService productService;
Declares aProductService
dependency that will be injected by Spring, marked asfinal
to indicate it won't change after construction.@Autowired public ProductController(ProductService productService) {
Defines a constructor forProductController
withProductService
as a parameter, demonstrating constructor-based dependency injection.this.productService = productService;
Assigns the injectedProductService
instance to theproductService
field within the controller.@GetMapping
Indicates that the method following this annotation handles HTTP GET requests, fetching all products.public ResponseEntity> getAllProducts() {
Defines a method to retrieve all products, returning them wrapped in aResponseEntity
to include HTTP status information.List products = productService.findAllProducts();
InvokesfindAllProducts()
method ofProductService
to fetch all products, storing them in a list.return ResponseEntity.ok(products);
Wraps the list of products in aResponseEntity
with an HTTP status of OK (200), preparing it for the response.@GetMapping("/{id}")
Specifies that the following method handles GET requests for a specific product identified by its{id}
path variable.public ResponseEntity getProductById(@PathVariable Long id) {
Defines a method to fetch a single product by its ID, with the possibility of throwing a runtime exception if not found.@PostMapping
Marks the method to handle POST requests, used for creating a new product.public ResponseEntity createProduct(@RequestBody Product product) {
Defines a method to save a new product passed in the request body, returning the saved product.Product savedProduct = productService.saveProduct(product);
CallssaveProduct()
onProductService
to persist the new product, capturing the saved instance.return ResponseEntity.ok(savedProduct);
Returns the saved product within aResponseEntity
with an OK status, indicating success.@PutMapping("/{id}")
Indicates that the following method handles PUT requests for updating a product by its ID.public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody Product product) {
Defines a method to update an existing product, identified by ID, with new data from the request body.Product updatedProduct = productService.updateProduct(id, product);
Updates the product usingProductService
, storing the updated version.return ResponseEntity.ok(updatedProduct);
Wraps the updated product in aResponseEntity
with an OK status, signifying a successful update.@DeleteMapping("/{id}")
Marks a method to handle DELETE requests, used for removing a product by its ID.public ResponseEntity deleteProduct(@PathVariable Long id) {
Defines a method to delete a product, returning an emptyResponseEntity
to indicate the operation's success.productService.deleteProduct(id);
CallsdeleteProduct()
onProductService
to remove the specified product.return ResponseEntity.ok().build();
Builds and returns an emptyResponseEntity
with an OK status, confirming the product's deletion.