spring boot fundamentals
spring boot fundamentals
___________________________
Key topics:
==================
=> Understand Depdendency Injection
=> bean wiring :annoation, java configuraton
=> REST basics
=> Spring boot REST application
=> Spring data
=> Spring boot web application, Spring MVC arch
=> spring boot profile
=> spring boot actuator
=>Spring boot Monotring and Logging
=> Spring boot testing
=> Spring boot security
=> Spring Boot microservice architecture
Session 1:
Understand Depdendency Injection
Rest
Controller <------------ Service layer <---------- persistance layer <------ db
as we have design our application as per interface and we have use DI therefore
kapil team can change implemenation
of service layer ( let earlier they are using Jdbc now wnat to use hibernate )
without effectiving even a single line
of code in Service layer (sumit team) do you not think it is great...
beside that spring DI help to manage dependency of our project and make our project
flexiable
---------- ProductDaoImplHib
|
ProductService <---------------- ProductDao-------ProductDaoImplJdbc
|
---------- ProductDaoImplUtil
public ProductServiceImpl(){
productDao=new ProductDaoImplUtil(); // or ProductDaoImplJdbc()
}
public List<Product>getProducts(){
// business logic
}
}
* BeanFactory:
- light weight container , dont support many featues
- dont use it
BeanFactory applicationContext=new XmlBeanFactory(new
ClassPathResource("demo.xml"));
* ApplicationContext
- more powerful
ApplicationContext applicationContext=
new ClassPathXmlApplicationContext("demo.xml");
ApplicationContext
-ClassPathXmlApplicationContext
-FileSystemXmlApplicationContext
-AnnotationConfigApplicationContext
-XMLWebApplicationContext
session 2:
___________
Spring vs Spring boot
=> Auto-Configuration
=> Microservice
@SpringBootApplication
public class Application {
spring-boot:run
@RestController
public class HelloRestController {
@RequestMapping("/hello")
public String hello(){
return "spring boot";
}
}
@GetMapping("products/{id}")
public Book getProductById(@PathVariable(name = "id")int id) {
return new Product(id, "java basics book", 300);
}
application.properties
---------------------------
server.servlet.context-path=/productapp
server.port=8080
bannner:
________________
spring.banner.location=classpath:banner.txt
https://devops.datenkollektiv.de/banner.txt/index.html
How to run spring boot app with command line:
java -jar jpa_demo2-0.0.1-SNAPSHOT.jar --server.port=8050
Rest
Controller <------------ Service layer <---------- persistance layer <------
SessionFactory
step 1: application.properties
_______________________
in case of h2 database :
---------------------
server.port=8090
server.servlet.context-path=/productapp
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# Custom H2 Console URL
spring.h2.console.path=/h2
spring.jpa.hibernate.ddl-auto=update
logging.level.org.springframework.web: DEBUG
logging.level.org.hibernate: ERROR
spring.jpa.show-sql=true
spring.datasource.url=jdbc:mysql://localhost:3306/demoms?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
logging.level.org.springframework.web: DEBUG
logging.level.org.hibernate: ERROR
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
@Repository
public interface ProductDao extends JpaRepository<Product, Integer>{
public Product findByName(String name);
}
@Autowired
public ProductServiceImpl(ProductDao productDao) {
this.productDao = productDao;
}
@Override
public List<Product> findAll() {
return productDao.findAll();
}
@Override
public Product getById(int id) {
return productDao.findById(id)
.orElseThrow(() -> new ProductNotFoundException("product
with id" + id + " is not found"));
}
@Override
public Product addProduct(Product product) {
productDao.save(product);
return product;
}
@Override
public Product updateProduct(int id, Product product) {
Product productToUpdate= getById(id);
productToUpdate.setPrice(product.getPrice());
productDao.save(productToUpdate);
return productToUpdate;
}
@Override
public Product deleteProduct(int id) {
Product productToDelete= getById(id);
productDao.delete(productToDelete);
return productToDelete;
}
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping(path = "products")
public List<Product>findAll(){
return productService.findAll();
}
@GetMapping(path = "products/{id}")
public Product findById(@PathVariable(name = "id") int id){
return productService.getById(id);
}
@PostMapping(path = "products")
public Product addProduct( @RequestBody Product product){
return productService.addProduct(product);
}
@DeleteMapping(path = "products/{id}")
public Product deleteProduct(@PathVariable(name = "id") int id){
return productService.deleteProduct(id);
}
@PutMapping(path = "products/{id}")
public Product updateProduct(@PathVariable(name = "id") int id, @RequestBody
Product product){
return productService.updateProduct(id, product);
}
}
@ResponseStatus(HttpStatus.CREATED)
@RestController
public class ProductController {
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping(path = "products")
public ResponseEntity<List<Product>> findAll(){
return
ResponseEntity.status(HttpStatus.OK).body(productService.findAll());
}
@GetMapping(path = "products/{id}")
public ResponseEntity<Product> findById(@PathVariable(name = "id") int id){
return ResponseEntity.ok(productService.getById(id));
}
@PostMapping(path = "products")
public ResponseEntity<Product> addProduct( @RequestBody Product product){
return
ResponseEntity.status(HttpStatus.CREATED).body(productService.addProduct(product));
}
@DeleteMapping(path = "products/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable(name = "id") int id){
productService.deleteProduct(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
@PutMapping(path = "products/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable(name = "id") int
id, @RequestBody Product product){
return
ResponseEntity.status(HttpStatus.CREATED).body(productService.updateProduct(id,
product));
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorDetails {
private String message;
private String details;
@RestControllerAdvice
public class ExHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<ErrorDetails> handle404(Exception ex, WebRequest req){
ErrorDetails details=new ErrorDetails();
details.setDate(new Date());
details.setDetails(req.getDescription(true));
details.setName("[email protected]");
details.setDetails(ex.toString());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(details);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDetails> handle500(Exception ex, WebRequest req){
ErrorDetails details=new ErrorDetails();
details.setDate(new Date());
details.setDetails(req.getDescription(true));
details.setName("[email protected]");
details.setDetails(ex.toString());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(details);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDetails> handle500(Exception ex, WebRequest req){
ErrorDetails details=new ErrorDetails();
details.setDate(new Date());
details.setDetails(req.getDescription(true));
details.setName("[email protected]");
details.setDetails(ex.toString());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(details);
}
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Step 2:
@GetMapping(path = "products", produces = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE})
1. @Value annotation
2. Enviornment
3. @ConfigrationProperties
@EnableConfigurationProperties(Config.class)
@ConfigrationProperties(prefix="db")
Disable logging :
---------------
logging.level.root=OFF
logging.level.org.springframework.boot=OFF
spring.main.banner-mode=OFF
Customizing logging :
---------------
logging.level.org.springframework.web: DEBUG
logging.level.org.hibernate: ERROR
logging.level.com.productapp=info
logging.level.org.springframework.web: DEBUG
logging.level.org.hibernate: ERROR
logging.file.name=/home/raj/Desktop/logs/server.log
Step 1:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
Step 2:
http://localhost:8090/bookapp/v3/api-docs
http://localhost:8090/bookapp/swagger-ui/index.html
http://localhost:8090/bookapp/v3/api-docs.yaml
Step 3:
Customization location
springdoc.swagger-ui.path=/swagger-ui-bookapp.html
server.port=8080
spring.devtools.restart.enabled=true
#management.endpoints.web.exposure.exclude=*
management.endpoints.web.exposure.include=health, custom-endpoint
management.endpoint.health.show-details=always
management.health.disk.enabled=true
management.health.livenessstate.enabled=true
management.health.readinessstate.enabled=true
management.server.port=9090
@Configuration
@Endpoint(id = "custom-endpoint")
public class CustomEndpoints {
@ReadOperation
public String getCustomData(){
return "This is custom Data";
}
}
Step 11: deployment spring boot as war file to tomcat
-------------------------------------------------------
server.port=8090
server.servlet.context-path=/bankapp
spring.datasource.driver-class-name= com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update
spring.datasource.url=jdbc:mysql://localhost:3306/edu123?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=update
logging.level.org.springframework.web: DEBUG
logging.level.org.hibernate: ERROR
logging.level.com.productapp: INFO
logging.level.com.productapp.service: INFO
spring.jpa.show-sql=true
spring.banner.location=
spring.jmx.enabled=true
management.endpoints.web.exposure.include=*
management.endpoints.jmx.exposure.include=*
management.info.env.enabled=true
info.app.encoding=UTF-8
info.app.java.source=21
info.app.java.target=21
info.app.name=productapp
info.app.dev=amit ku
management.endpoint.health.show-details=always
management.endpoint.health.probes.enabled=true
# livenessstate readinessstate
#management.health.livenessstate.enabled=true
#management.health.readinessstate.enabled=true
info.key=default
spring.profiles.active=test
server.port=8090
server.servlet.context-path=/productapp
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=foo
spring.datasource.password=foo
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
====================
repo layer testing
====================
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.annotation.Rollback;
import java.util.List;
import java.util.Optional;
@Autowired
private ProductRepo productRepo;
private Product product;
@BeforeEach
void setUp() {
product=new Product("laptop",120000);
}
@DisplayName("givenProductListWhenFindAllThenRetunProductList")
@Test
public void givenProductListWhenFindAllThenRetunProductList(){
//given
Product product2=new Product("laptop cover",1200);
productRepo.save(product);
productRepo.save(product2);
//when
List<Product> productList=productRepo.findAll();
//then verify
assertThat(productList).isNotNull();
assertThat(productList.size()).isEqualTo(2);
}
@DisplayName("JUnit test for get product by id operation")
@Test
public void givenProductObject_whenFindById_thenReturnProductObject(){
// given - precondition or setup
Product p1=new Product("laptop",120000);
productRepo.save(p1);
@AfterEach
void tearDown() {
}
}
====================
service layer testing
====================
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.BDDMockito;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Collections;
import java.util.List;
@Mock
private ProductRepo productRepo;
@InjectMocks
private ProductServiceImpl productService;
@BeforeEach
void setUp() {
product=new Product(1,"laptop",120000);
}
given(productRepo.findAll()).willReturn(List.of(product,product2));
@AfterEach
void tearDown() {
}
==========================
controller layer testing
===========================
import com.fasterxml.jackson.databind.ObjectMapper;
import com.productapp.repo.Product;
import com.productapp.service.ProductService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.ArrayList;
import java.util.List;
@WebMvcTest(controllers = ProductRestController.class)
class ProductRestControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ProductService productService;
@Autowired
private ObjectMapper objectMapper;
@Test
public void givenProductObject_whenCreateProduct_thenReturnSavedProduct()
throws Exception{
// @Test
public void givenInvalidProductId_whenGetProductById_thenReturnEmpty() throws
Exception{
// given - precondition or setup
int productId = 1;
Product product=new Product(1,"laptop", 120000);
given(productService.getById(productId)).willReturn(null);
==========================
integration layer testing
===========================
import com.fasterxml.jackson.databind.ObjectMapper;
import com.productapp.repo.Product;
import com.productapp.repo.ProductRepo;
import com.productapp.service.ProductService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class ProductAppIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ProductService productService;
@Autowired
private ProductRepo productRepo;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
void setup(){
productRepo.deleteAll();
}
@Test
public void givenProductObject_whenCreateProduct_thenReturnSavedProduct()
throws Exception{
@Test
public void givenListOfProducts_whenGetAllProducts_thenReturnProductList()
throws Exception{
// given - precondition or setup
List<Product> listOfProducts = new ArrayList<>();
listOfProducts.add(Product.builder().name("foo").price(7000).build());
listOfProducts.add(Product.builder().name("bar").price(7000).build());
productRepo.saveAll(listOfProducts);
// when - action or the behaviour that we are going test
ResultActions response = mockMvc.perform(get("/products"));
jacoco
========
https://medium.com/@truongbui95/jacoco-code-coverage-with-spring-boot-835af8debc68
jacoco plugin:
----------------
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- com.productapp.Productapp01Application
com.productapp.exceptions.ProductNotFoundException
com.productapp.dto.ErrorInfo
com.productapp.repo.Product
-->
<exclude>com/productapp/Productapp01Application.class</exclude>
<exclude>com/productapp/exceptions/ProductNotFoundException.class</exclude>
<exclude>com/productapp/dto/ErrorInfo.class</exclude>
<exclude>com/productapp/repo/Product.class</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>00%</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
https://blog.stackademic.com/integratation-of-sonarqube-with-springboot-
6d2cebd4ef95
https://blog.stackademic.com/integratation-of-sonarqube-with-springboot-
6d2cebd4ef95