Spring Core

Application Development

Fullstack Development

Monolith vs Microservices

Monolithic vs Microservices Architecture

Programming Language vs Framework

Hibernate : It is an ORM framework. It is used to develop only persistence layer of our application.

Struts : It is a web framework. It is used to develop only web layer of our application.

Note: Frameworks like Hibernate and Struts can't be used to develop entire application.

Spring Framework

Note: To create a servlet we need to implement Servlet interface or we need to extend HttpServlet or GenericServlet class. That means servlet is forcing us to use servlets specific interface or classes. Hence the project will become tightly coupled with Servlets.

Note: Reactive Programming support added in Spring Framework 5.x version

Note: In Spring framework we have to do the configurations manually.

Spring Boot

Note: Spring Boot is an extension for Spring Framework. It is not an independent framework, so it can't be used without spring.

Spring Modules

Spring Core : Spring Core is base module in Spring Framework. It is providing IOC container & Dependency Injection.

Note: IOC & DI are fundamental concepts of Spring Framework.

Spring Context : Spring Context module will deal with configuration related stuff

Spring AOP : AOP stands for Aspect Oriented Programming. Spring AOP is used to deal with Cross Cutting logics in application.

	Application = Business Logic + Cross Cutting Logic

Note: We can separate business logic and cross cutting logic using AOP module.

Spring DAO : Spring DAO / Spring JDBC module used to develop Persistence Layer

Spring ORM : Spring ORM module is used to develop Persistence Layer with ORM features

Spring Web MVC : Spring Web MVC module is used to develop Web Applications

Spring Security : Spring Security module is used to implement Security Features in our application

	1) Authentication
	2) Authorization

Spring REST : Spring REST is used to develop RESTFul services (REST API)

Spring Data : Spring Data is used to develop persistence layer. It provided pre-defined repositories to simplify CRUD operations

Spring Cloud : Spring Cloud providing cloud concepts like Service Registry, Cloud Gateway, Filters, Routing, FeignClient etc...

Spring Batch : Spring Batch is used to implement Batch Processing in our application. Batch Processing means bulk operation.

Spring Core

Tightly Coupling Problem

Tightly Coupling Problem

Note: In both approaches classes will become tightly coupled.

How to avoid Tightly Coupling ?

Strategy Design Pattern

Rules

Strategy Design Pattern

	// Strategy Design Pattern
	
	// IPayment interface
	package in.ashokit;
	
	public interface IPayment {
		public String pay(double amount);
	}
	
	// BillCollector class
	package in.ashokit;
	
	public class BillCollector {
		private IPayment payment;
		
		public void setPayment(IPayment payment) {
			this.payment = payment;
		}
		
		public BillCollector() { }
		
		public BillCollector(IPayment payment) {
			this.payment = payment;
		}
		
		public void payBill(double amount) {
			String status = payment.pay(amount);
			System.out.println(status);
		}
	}

Dependency Injection

Setter Injection

	// injecting dependent obj into target obj using setter method
	BillCollector bc = new BillCollector();
	bc.setPayment(new CreditCardPayment()); // setter injection
	bc.payBill(1400.0);

Constructor Injection

	// injecting dependent obj into target obj using constructor
	BillCollector bc = new BillCollector(new DebitCardPayment()); // constructor injection
	bc.payBill(1500.0);

Field Injection

	// injecting dependent obj into target obj using field
	Class<?> clz = Class.forName("in.ashokit.BillCollector");
			
	Field field = clz.getDeclaredField("payment");
	field.setAccessible(true);
			
	Object obj = clz.newInstance();
	field.set(obj, new UpiPayment()); // field injection
			
	BillCollector bc = (BillCollector) obj;
	bc.payBill(2000.0);

Note: If variable is declared as private then we can't access that variable outside of the class directly. To access private variables outside of the class we have to use Reflection API.

Note: Field injection is strictly not recommended to use because it is violating oops principle.

IOC Container

IOC Container

Note: IOC stands for Inversion of Control. DI stands for Dependency Injection.

Note: XML approach is not supported in spring boot. Spring boot supports only Java config and annotations.

What is Spring Bean ?

First Spring Application

Step-1

Step-2

	<!-- Spring-Context Dependency -->
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.3.20</version>
		</dependency>
	</dependencies>

Step-3

Step-4

	<!-- Spring-Beans.xml -->
	<bean id="creditCard" class="in.ashokit.CreditcardPayment" />
	<bean id="debitCard" class="in.ashokit.DebitcardPayment" />
	<bean id="amexCard" class="in.ashokit.AmexCard" />
	<bean id="upi" class="in.ashokit.UpiPayment" />
	
	<bean id="billCollector" class="in.ashokit.BillCollector">
		<property name="payment" ref="creditCard" />
	</bean>

Step-5

	// Test class
	public class Test {
		public static void main(String[] args) throws Exception {	
			ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Beans.xml");
	
			BillCollector bc = context.getBean("billCollector", BillCollector.class);
			bc.collectPayment(1400.00);
		}
	}

Dependency Injection using IOC

	<!-- Setter injection -->
	<bean id="billCollector" class="in.ashokit.BillCollector">
		<property name="payment" ref="upi" />
	</bean>
	<!-- Constructor injection -->
	<bean id="billCollector" class="in.ashokit.BillCollector">
		<constructor-arg name="payment" ref="upi" />
	</bean>

Note: If a spring bean does not have a non-parameterized constructor in that case performing constructor injection is mandatory because IOC container needs the required parameter(s) to create the object for the spring bean.

	<!-- Setter injection overriding constructor injection -->
	<bean id="billCollector" class="in.ashokit.BillCollector">
		<property name="payment" ref="creditCard" />
		<constructor-arg name="payment" ref="upi" />
	</bean>

Note: Using XML approach we can't perform field injection.

Bean Scopes

	<!-- Bean Scopes -->
	<bean id="car" class="in.ashokit.Car" />
	
	<bean id="engine" class="in.ashokit.Engine" scope="prototype" />

Q) Why default scope for Spring Beans is Singleton ?
Ans: To save memory spring framework made the default scope as singleton. Another reason is most of the beans are stateless (they doesn't hold any per-user or per-request state)

Singleton vs Prototype Scope

Autowiring

	<!-- Manual wiring -->
	<bean id="billCollector" class="in.ashokit.BillCollector">
		<constructor-arg name="payment" ref="upi" />
	</bean>

byName Mode

Autowiring byName

	<!-- byName mode -->
	<bean id="dieselEng" class="in.ashokit.beans.DieselEngine" />
	
	<bean id="car" class="in.ashokit.beans.Car" autowire="byName" />

Note: Every spring bean should have a unique id. We can't configure more than one bean with same id.

byType Mode

Autowiring byType

	<!-- byType mode-->
	<bean id="xyz" class="in.ashokit.beans.DieselEngine" />
	
	<bean id="car" class="in.ashokit.beans.Car" autowire="byType" />
	<!-- Ambiguity problem in byType mode -->
	<bean id="abc" class="in.ashokit.beans.DieselEngine" />
	<bean id="xyz" class="in.ashokit.beans.DieselEngine" />
	
	<bean id="car" class="in.ashokit.beans.Car" autowire="byType" />
	<!-- How to resolve ambiguity problem -->
	<bean id="abc" class="in.ashokit.beans.DieselEngine" />
	<bean id="xyz" class="in.ashokit.beans.DieselEngine" autowire-candidate="false" />
	
	<bean id="car" class="in.ashokit.beans.Car" autowire="byType" />

Note: When we configure autowiring with byName or byType mode then it is performing setter injection by default. If setter method is not available in target class then IOC will not perform dependency injection.

constructor Mode

	<!-- constructor mode -->
	<bean id="abc" class="in.ashokit.beans.DieselEngine" />
	
	<bean id="car" class="in.ashokit.beans.Car" autowire="constructor" />

no Mode

	<!-- no mode -->
	<bean id="car" class="in.ashokit.beans.Car" autowire="no" />

Note: By default autowiring is disabled in spring because deafult mode for autowire is no

Spring Boot

Spring

Note: When we develop application by using spring, programmer is responsible to take care of configurations required for the application.

Spring Boot

Spring Boot

Advantages of Spring Boot

Auto Configuration : Autoconfiguration means boot will identify the configurations required for our application and it will provide that configurations

	- Starting IOC container
	- Creating Connection Pool
	- Creating SMTP Connections
	- Web Application deployment to server
	- Depedency Injections etc....

POM Starters : POM starters are nothing but dependencies that we use to develop our application. POM starters will simplify maven configuration.

	- web-starter
	- jdbc-starter
	- security-starter
	- mail-starter

Embedded Servers : Boot will provide web server to run our web applications. Those servers are called as Embedded Server

	- Tomcat (default)
	- Jetty
	- Netty

Rapid Development : Rapid Development means fast development. We can focus only on business logic because Boot will take care of configurations.

Actuators : Actuators are used to monitor and manage our application. Actuators providing production-ready features for our application.

	- How many classes loaded
	- How many objects created
	- How many threads are running
	- URL Mappings Available
	- Health of the project etc...

Building First Spring Boot Application

Note: To create spring boot application internet connection is mandatory.

Creating Project using Spring Initializr Website

Creating Project using STS IDE

Note: STS IDE will use start.spring.io internally to create the project

Spring Boot Application Folder Structure (Maven App)

Spring Boot App Folder Structure Maven

Start Class in Spring Boot

	// Spring Boot start class
	@SpringBootApplication
	public class Application {
		public static void main(String[] args) {
			SpringApplication.run(Application.class, args);
		}
	}

Internals of run Method

Create Bootstrap Context : It will load pre-defined classes of spring boot (initializers & listeners)

Start Initializers : Initializers will start and perform initialization.

Start Listeners : Listeners will start and perform actions based on events.

Prepare Environment : It will load configuration provided by us and prepare environment to run our application.

Print Banner : Banner will be printed.

Create Context : IOC container will be created.

Refresh Context : IOC will refresh the beans and create objects to perform dependency injection (making IOC ready to use)

Print Time Taken : Time taken to start the application will be printed.

Call Runners : Runners will be called.

Return IOC Reference : IOC reference will be returned.

Note: All Spring Boot applications must have a start class that contains a main() method and the @SpringBootApplication annotation.

Note: In start class main() we must call run(), If we don't call run() then our application will executed as a normal Java application i.e Auto Configuration will not be performed.

	# application.properties
	spring.main.banner-mode = banner-mode
	# application.yml
	spring:
		main:
			banner-mode: 'banner-mode'

How IOC Container will start in Spring Boot ?

Note: Based on the pom starters, run() will choose the appropriate class to start the IOC.

Runners in Spring Boot

Use cases

	1) send email to management once application started
	2) read data from static db tables and store into cache memory
	// Spring Boot app using ApplicationRunner
	@Component
	public class CacheManager implements ApplicationRunner {
	
		@Override
		public void run(ApplicationArguments args) throws Exception {
			System.out.println("Logic executing to load data into cache....");
		}
	}
	// Spring Boot app using CommandLineRunner
	@Component
	public class SendAppStartMail implements CommandLineRunner {
	
		@Override
		public void run(String... args) throws Exception {
			System.out.println("Logic executing to send email....");
		}
	}

Note: @Component annotation is used to represent our java class as Spring Bean.

Note: @Order annotation is used to configure the order of execution of runners.

@SpringBootApplication Annotation

@ComponentScan Annotation

Note: The package which contains start class is called as base package.

Note: The package names which are starting with base package name are called as sub packages.

	in.ashokit   (base package)

	in.ashokit.dao   (sub package)
	in.ashokit.service   (sub package)
	in.ashokit.config   (sub package)
	in.ashokit.rest   (sub package)
	in.ashokit.util   (sub package)

	com.wallmart.security ----> This is not sub package

Note: Only base package and its sub packages will participate in component scan. Any other package will not participate in component scan.

	// Configuring multiple base packages for component scan
	@SpringBootApplication
	@ComponentScan(basePackages = {"in.ashokit", "com.wallmart"})
	public class Application {
	
		public static void main(String[] args) {
			SpringApplication.run(Application.class, args);
		}
	}

Note: It is highly recommended to follow proper package naming conventions

	companyDomainName.projectName.moduleName.layerName
	com.tcs.passport   (base package)
	com.tcs.passport.user.dao   (sub package)
	com.tcs.passport.user.service   (sub package)

@Bean Annotation

	// Spring Boot app using @Bean annotation
	@Configuration
	public class AppConfig {
		@Bean
		public AppSecurity createInstance() {
			AppSecurity as = new AppSecurity();
			// custom logic to secure our app
			return as;
		}
	}

Note: @Bean method can be written inside any spring bean class but it is not recommended. Every spring bean class participates in component scan so during the component scan of the bean class @Bean method will be discovered by IOC.

Note: We can write @Bean method inside start class but it is also not recommended. It is possible because start class contains @SpringBootApplication annotation which implicitly includes @Configuration annotation.

	@SpringBootApplication   --->   @SpringBootConfiguration   --->   @Configuration

@SpringBootConfiguration Annotation

@EnableAutoConfiguration Annotation

Configuration vs AutoConfiguration

How to represent Java class as a Spring Bean ?

	@Component
	@Service
	@Repository

	@Controller
	@RestController

	@Configuration
	@Bean

Note: All the above annotations are class level annotations except @Bean annotation. @Bean is a method level annotation.

@Component, @Service & @Repository

@Controller & @RestController

@Configuration & @Bean

Note: @Controller, @Service, @Repository and @Component annotations are called stereotype annotations.

Note: Any spring bean class is either @Controller, @Service or @Repository If it doesn't belongs to any of these then its a @Component.

Autowiring

Note: Autowiring supports only referenced types (No support for primitive types)

	// ReportDao interface
	public interface ReportDao {
		public String findData();
	}
	// ReportDaoImpl class
	@Repository
	public class ReportDaoImpl implements ReportDao {
	
		public ReportDaoImpl() {
			System.out.println("ReportDaoImpl :: Constructor");
		}
		
		@Override
		public String findData() {
			System.out.println("Fetching report data from DB .. !!");
			return "Report data";
		}
	}
	// ReportService class
	@Service
	public class ReportService {
		private ReportDao reportDao;
		
		public ReportService() {
			System.out.println("ReportService :: Constructor");
		}
		
		public void generateReport() {
			reportDao.findData();
			System.out.println("Generating report .. !!");
		}
	}
	// Start class
	@SpringBootApplication
	public class Application {
	
		public static void main(String[] args) {
			ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
			ReportService reportService = context.getBean(ReportService.class);
			reportService.generateReport();
		}
	}

Note: In the above code IOC will not perform DI.

@Autowired at Setter Level

	// Setter Injection using @Autowired
	@Service
	public class ReportService {
		private ReportDao reportDao;
		
		@Autowired
		public void setReportDao(ReportDao reportDao) {
			System.out.println("setReportDao() method called");
			this.reportDao = reportDao;
		}
		
		public void generateReport() {
			reportDao.findData();
			System.out.println("Generating report .. !!");
		}
	}

Partial Injection using Setter

	// Partial Injection using Setter
	@Service
	public class ReportService {
		private OracleReportDaoImpl oracleReportDao;
		
		private MySQLReportDaoImpl mysqlReportDao;
		
		@Autowired
		public void setOracleReportDao(OracleReportDaoImpl oracleReportDao) {
			System.out.println("setOracleReportDao() method called");
			this.oracleReportDao = oracleReportDao;
		}
		
		@Autowired(required = false) // optional
		public void setMysqlReportDao(MySQLReportDaoImpl mysqlReportDao) {
			System.out.println("setMysqlReportDao() method called");
			this.mysqlReportDao = mysqlReportDao;
		}
		
		public void generateReport() {
			oracleReportDao.findData();
			if (mysqlReportDao != null) {
				mysqlReportDao.findData();
			}
			System.out.println("Generating report .. !!");
		}
	}

@Autowired at Constructor Level

	// Constructor Injection using @Autowired
	@Service
	public class ReportService {
		private ReportDao reportDao;
		
		@Autowired
		public ReportService(ReportDao reportDao) {
			System.out.println("ReportService :: Param Constructor");
			this.reportDao = reportDao;
		}
		
		public void generateReport() {
			reportDao.findData();
			System.out.println("Generating report .. !!");
		}
	}

Note: If our class is having exactly one parameterized constructor then @Autowired annotation is optional because IOC will call the constructor to create the object for the bean, when the constructor is called DI will also be performed.

	@Service
	public class ReportService {
		private ReportDao reportDao;
		
		public ReportService() {
			System.out.println("ReportService :: Non-Param Constructor");
		}
		
		@Autowired
		public ReportService(ReportDao reportDao) {
			System.out.println("ReportService :: Param Constructor");
			this.reportDao = reportDao;
		}
		
		public void generateReport() {
			reportDao.findData();
			System.out.println("Generating report .. !!");
		}
	}

Note: By default IOC will call non-parameterized constructor to create the object for the bean. To tell the IOC to use parameterized constructor we need to use @Autowired annotation.

@Autowired at Field Level

	// Field Injection using @Autowired
	@Service
	public class ReportService {
	
		@Autowired
		private ReportDao reportDao;
		
		public void generateReport() {
			reportDao.findData();
			System.out.println("Generating report .. !!");
		}
	}
	@Component
	public class Car {
		@Autowired
		private IEngine engine;
		
		@Autowired
		private IFuel fuel;
		
		@Autowired
		private IBreak break;
		
		// logic to handle multiple tasks
	}

Note: Above class is responsible for more than one tasks hence it is violating the Single Responsibility Principle.

SI vs CI vs FI

Setter Injection

Constructor Injection

Field Injection

Note: Out of all these Dependency Injections, Constructor Injection is recommended because Partial Injection not possible and target bean will be created only if dependent bean is available.

Ambiguity Problem in Autowiring

Ambiguity Problem in Autowiring

	// ReportDao interface
	public interface ReportDao {
		public String findData();
	}
	// OracleReportDaoImpl class
	@Repository
	public class OracleReportDaoImpl implements ReportDao {
	
		public OracleReportDaoImpl() {
			System.out.println("OracleReportDaoImpl :: Constructor");
		}
		
		@Override
		public String findData() {
			System.out.println("Fetching report data from Oracle DB .. !!");
			return "Report data";
		}
	}
	// MySQLReportDaoImpl class
	@Repository
	public class MySQLReportDaoImpl implements ReportDao {
	
		public MySQLReportDaoImpl() {
			System.out.println("MySQLReportDaoImpl :: Constructor");
		}
		
		@Override
		public String findData() {
			System.out.println("Fetching report data from MySQL DB .. !!");
			return "Report data";
		}
	}
	// ReportService class
	@Service
	public class ReportService {
		private ReportDao reportDao;
		
		public ReportService() {
			System.out.println("ReportService :: Constructor");
		}
		
		@Autowired
		public void setReportDao(ReportDao reportDao) {
			System.out.println("setReportDao() method called");
			this.reportDao = reportDao;
		}
		
		public void generateReport() {
			reportDao.findData();
			System.out.println("Generating report .. !!");
		}
	}

@Qualifer Annotation

	// OracleReportDaoImpl class
	@Repository("oracle")
	public class OracleReportDaoImpl implements ReportDao {
	
		public OracleReportDaoImpl() {
			System.out.println("OracleReportDaoImpl :: Constructor");
		}
		
		@Override
		public String findData() {
			System.out.println("Fetching report data from Oracle DB .. !!");
			return "Report data";
		}
	}
	// MySQLReportDaoImpl class
	@Repository("mysql")
	public class MySQLReportDaoImpl implements ReportDao {
	
		public MySQLReportDaoImpl() {
			System.out.println("MySQLReportDaoImpl :: Constructor");
		}
		
		@Override
		public String findData() {
			System.out.println("Fetching report data from MySQL DB .. !!");
			return "Report data";
		}
	}
	// ReportService class
	@Service
	public class ReportService {
		private ReportDao reportDao;
		
		public ReportService() {
			System.out.println("ReportService :: Constructor");
		}
		
		@Autowired
		@Qualifier("oracle")
		public void setReportDao(ReportDao reportDao) {
			System.out.println("setReportDao() method called");
			this.reportDao = reportDao;
		}
		
		public void generateReport() {
			reportDao.findData();
			System.out.println("Generating report .. !!");
		}
	}

Note: When we use @Qualifier annotation IOC will use byName mode to perform DI.

@Primary Annotation

	// MySQLReportDaoImpl class
	@Repository
	@Primary
	public class MySQLReportDaoImpl implements ReportDao {
	
		public MySQLReportDaoImpl() {
			System.out.println("MySQLReportDaoImpl :: Constructor");
		}
		
		@Override
		public String findData() {
			System.out.println("Fetching report data from MySQL DB .. !!");
			return "Report data";
		}
	}
	// OracleReportDaoImpl class
	@Repository
	public class OracleReportDaoImpl implements ReportDao {
	
		public OracleReportDaoImpl() {
			System.out.println("OracleReportDaoImpl :: Constructor");
		}
		
		@Override
		public String findData() {
			System.out.println("Fetching report data from Oracle DB .. !!");
			return "Report data";
		}
	}

Note: @Qualifier takes precedence over @Primary. @Primary means default implementation while @Qualifier is the specific implementation.

Bean Lifecycle

	// Interface approach
	@Component
	public class Car implements InitializingBean, DisposableBean {
	
		public Car() {
			System.out.println("Car :: Constructor");
		}
		
		@Override
		public void afterPropertiesSet() throws Exception {
			System.out.println("afterPropertiesSet() method called");
		}
		
		@Override
		public void destroy() throws Exception {
			System.out.println("destroy() method called");
		}
	}

Note: If we use interface approach then our bean class will become tightly coupled with spring framework (invasive). Hence interface approach is not recommended to use.

	// Annotation approach
	@Component
	public class Engine {
	
		public Engine() {
			System.out.println("Engine :: Constructor");
		}
		
		@PostConstruct
		public void init() {
			System.out.println("Start Engine .. !!");
		}
		
		@PreDestroy
		public void destroy() {
			System.out.println("Stop Engine .. !!");
		}
	}

Note: In annotation approach method name can be any.

Lombok

Lombok

How to use Lombok in our Project ?

Step-1

Step-2

Note: We can run Lombok jar file by double clicking also.

Step-3

Step-4

	<!-- Lombok Maven Dependency -->
	<dependency>
	    <groupId>org.projectlombok</groupId>
	    <artifactId>lombok</artifactId>
	    <version>1.18.38</version>
	</dependency>

Step-5

Step-6

Note: In Spring Boot applications, Lombok can be added to the project by selecting the Lombok dependency at the time of project creation.

Lombok Annotations

@Setter : To generate setter methods for the variables in the class

@Getter : To generate getter methods for the variables in the class

Note: @Setter and @Getter annotations can be used at field (variable) level also.

@NoArgsConstructor : It is used to generate zero-param constructor

@ToString : It is used to generate toString() method

@EqualsAndHashCode : It is used to generate equals() method and hashCode() method

Note: Instead of writing all the above annotations we can use @Data

	@Data  =  @Setter + @Getter + @NoArgsConstructor + @ToString + @EqualsAndHashCode

@AllArgsConstructor : It is used to generate constructor with all variables as constructor arguments.

@RequiredArgsConstructor : It is used to generate constructor with all the final and @NonNull variables as constructor arguments.

@SneakyThrows : It is used to throw checked exceptions. It is a method level annotation.

Note: If we use @Data and @AllArgsConstructor together, then @Data will not generate a zero-param constructor. So, in this case, to generate a zero-param constructor, we need to use @NoArgsConstructor as well.

	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Person {
		private String name;
		private Integer age;
	}

Delombok

Note: Generally, we don’t Delombok our code in realtime.

Limitation of Lombok

	public class Person {
		private String name;
		private Integer age;
		
		public Person(String name, Integer age) {
			this.name = name;
			this.age = age;
		}
	}
	// This class will not compile
	@Data
	public class Student extends Person {
		private Integer rollNo;
	}

Note: Lombok is criticized for using annotations to generate code instead of just storing metadata. If Lombok is removed, the code may not compile, which concerns some developers.

Spring Data JPA

What is Data JPA ?

ORM vs Data JPA

Note: If we use Data JPA then we don't need to write any methods to perform CRUD operations. Data JPA provided repository interfaces which contains methods to perform CRUD operations.

Hibernate Vs Data JPA

Entity Class

@Entity : It represents our class as Entity class (It is mandatory annotation)

@Table : It is used to map our class with DB table

Note: @Table is optional if our class name and table name is same. If we don't give @Table then it will consider class name as table name.

@Id : It is used to represent our class variable as primary key column (It is mandatory annotation)

@Column : It is used to map our class variables with DB table columns

Note: @Column is optional if variable name and column name is same. If we don't give @Column then it will consider variable name as column name.

Mapping Entity class with DB Table

	// Entity class
	@Entity
	@Table(name="CRICKET_PLAYERS")
	public class Player {
		@Id
		@Column(name="PLAYER_ID")
		private Integer playerId;
		
		@Column(name="PLAYER_NAME")
		private String playerName;
		
		@Column(name="PLAYER_AGE")
		private Integer playerAge;
		
		@Column(name="LOCATION")
		private String location;
	}

Note: For every DB table we should create one Entity class. One entity class will be mapped with only one DB table.

Note: If we use JDBC then entity classes and mapping between entity classes and DB tables are not required because JDBC uses SQL queries which already defines table structure and data is handled manually using ResultSet.

Repository Interfaces

	// Syntax to write repository interface
	public interface EntityRepository extends CrudRepository<Entity, ID> {
	
	}
	// Repository interface
	public interface PlayerRepository extends CrudRepository<Player, Serializable> {
	
	}

Note: If the type of primary key is not known then we can use Serializable to make our code generic. But it is not recommended.

Note: When our interface extending properties from JPA repository interface then JPA will provide implementation for our interface during runtime using Proxy Design Pattern.

Datasource Properties

	# datasource properties
	spring.datasource.url=jdbc:mysql://localhost:3306/sbms
	spring.datasource.username=ashokit
	spring.datasource.password=AshokIT@123
	spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

ORM Properties

Note: In realtime, it is not recommended to use ddl-auto because if it's not configured properly, it can accidentally drop or modify database tables.

	# ORM properties
	spring.jpa.hibernate.ddl-auto=update
	spring.jpa.show-sql=true
	spring.jpa.properties.hibernate.format_sql=true

Building First App using Data JPA

	// Player entity class
	@Entity
	@Table(name="CRICKET_PLAYERS")
	public class PlayerEntity {
		
		@Id
		@Column(name="PLAYER_ID")
		private Integer playerId;
		
		@Column(name="PLAYER_NAME")
		private String playerName;
		
		@Column(name="PLAYER_AGE")
		private Integer playerAge;
		
		private String location;
		
		// getters & setters
	}
	// PlayerRepository interface
	public interface PlayerRepository extends CrudRepository<PlayerEntity, Integer> {
	
	}
	# application.properties file
	
	# DataSource properties
	spring.datasource.url=jdbc:mysql://localhost:3306/sbms
	spring.datasource.username=ashokit
	spring.datasource.password=AshokIT@123
	spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
	
	# ORM properties
	spring.jpa.hibernate.ddl-auto=update
	spring.jpa.show-sql=true
	spring.jpa.properties.hibernate.format_sql=true
	// Start class
	@SpringBootApplication
	public class Application {
		public static void main(String[] args) {
			ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
			PlayerRepository playerRepository = context.getBean(PlayerRepository.class);
			
			PlayerEntity p = new PlayerEntity();
			p.setPlayerId(101);
			p.setPlayerName("Sachin");
			p.setPlayerAge(54);
			p.setLocation("Mumbai");
			
			playerRepository.save(p); // upsert method
		}
	}

Note: @Repository is optional on a repository interface because it extends one of the JPA repository interfaces so it will discovered when JPA scans for repository interfaces.

Note: Data JPA will scan for repository interfaces only if our project contains data-jpa starter.

CrudRepository

save() : upsert single record (insert and update)

	User u1 = new User(101, "Ramu", "Male", 25, "India");
	repository.save(u1);

saveAll() : upsert multiple records (insert and update)

	User u2 = new User(102, "Raju", "Male", 26, "India");
	User u3 = new User(103, "John", "Male", 30, "USA");
	User u4 = new User(104, "Smith", "Male", 32, "Canada");
	
	repository.saveAll(Arrays.asList(u2, u3, u4));

findById() : To retrieve single record using primary key

	Optional<User> findById = repository.findById(103);
	if(findById.isPresent()) {
		System.out.println(findById.get());
	}

findAllById() : To retrieve multiple records using primary keys

	Iterable<User> allById = repository.findAllById(Arrays.asList(101,102,103));
	allById.forEach(user -> {
		System.out.println(user);
	});

findAll() : To retrieve all records from table

	Iterable<User> findAll = repository.findAll();
	findAll.forEach(user -> {
		System.out.println(user);
	});

count() : To get total records count

	long count = repository.count();
	System.out.println("Total Records in table :: "+ count);

existsById() : To check record presence in table using primary key

	boolean existsById = repository.existsById(101);
	System.out.println("Record Presence with id - 101 :: " + existsById);

deleteById() : To delete single record using primary key

	repository.deleteById(104);

deleteAllById() : To delete multiple records using primary keys

	repository.deleteAllById(Arrays.asList(102,103));

delete() : To delete single record using entity object

	User u1 = new User(101, "Ramu", "Male", 25, "India");
	repository.delete(u1);

deleteAll(..) : To delete multiple records from table using entity objects

	User u2 = new User(102, "Raju", "Male", 26, "India");
	User u3 = new User(103, "John", "Male", 30, "USA");
	User u4 = new User(104, "Smith", "Male", 32, "Canada");
	
	repository.deleteAll(Arrays.asList(u2, u3, u4));

deleteAll() : To delete all records from table

	repository.deleteAll();

Requirement - 1 : Retrieve all user records who belongs to India

	SQL : select * from user_master where user_country = 'India';

Requirement - 2 : Retrieve all user records whose age is below 30 years

	SQL : select * from user_master where user_age <= 30;

Note: user_age and user_country are non-primary columns in the table

findBy Methods

	// findBy method syntax
	findBy + entityClassVariableName (parameters ...)

Note: findBy methods are written inside repository interfaces (abstract methods)

	// Retrieve user data based on country
	findByCountry(String cname);
	
	// Retrieve user data based on age
	findByAge(Integer uage);

Note: findBy methods are used for only retrieval (select) operations.

Note: If no keyword is specified then by default findBy methods will use Equals keyword.

	// select * from user_master where user_country = ?;
	public List<User> findByCountry(String cname);
	
	// select * from user_master where user_age = ?;
	public List<User> findByAge(Integer age);
	
	// select * from user_master where user_age >= ?;
	public List<User> findByAgeGreaterThanEqual(Integer age);
	
	// select * from user_master where user_country in (?, ?, ? ...);
	public List<User> findByCountryIn(List<String> countries);
	
	// select * from user_master where user_country = ? and user_age = ?;
	public List<User> findByCountryAndAge(String cname, Integer age);
	
	// select * from user_master where user_country = ? and user_age = ? and user_gender = ?;
	public List<User> findByCountryAndAgeAndGender(String cname, Integer age, String gender);

Note: If we are using more then one columns in findBy method then parameters order should be same as column order in the method name.

	// It will retrieve data using country and age
	public List<User> findByCountryAndAge(String cname, Integer age);
	
	// It will throw exception
	public List<User> findByCountryAndAge(Integer age, String cname);

Custom Queries

Note: Using custom queries we can perform all types of CRUD operations.

Native SQL Vs HQL

Native SQL
HQL

Note: Hibernate has dialect classes for every database

SQL vs HQL

Note: SQL queries are executed directly on the DB, whereas HQL queries need to be converted into SQL before execution which makes HQL queries are slightly slower than SQL queries.

	-- Retrieve all records from table
	SQL : select * from user_master
	HQL :  from User
	
	-- Retrieve users who belongs to country 'India'
	SQL : select * from user_master where user_country = 'India'
	HQL : from User where country = 'India'
	
	-- Retrieve users who belongs to 'India' and their age is 25
	SQL : select * from user_master where user_country = 'India' and user_age = 25
	HQL : from User where country = 'India' and age = 25
	
	-- Retrieve userid and username based on country
	SQL : select user_id, user_name from user_master where user_country = 'India';
	HQL : select userid, username from User where country = 'India'

Note: If we are retrieving specific columns data then we need to start the HQL query with select keyword. Retrieving specific columns data is called as Projection. If we are not using projection then we can start the HQL query with from keyword also.

	@Query(value = "from User")
	public List<User> getAllUsers();
		
	@Query(value = "select * from user_master", nativeQuery = true)
	public List<User> getAllUsersNative();
		
	@Query(value = "from User where country = :cname")
	public List<User> getUsersByCountry(String cname);
		
	@Query(value = "select * from user_master where user_country = :cname", nativeQuery = true)
	public List<User> getUsersByCountryNative(String cname);
		
	@Query(value = "from User where country = :cname and age = :uage")
	public List<User> getUsersByCountryAndAge(String cname, Integer uage);
		
	@Query(value = "select * from user_master where user_country = :cname and user_age = :uage", nativeQuery = true)
	public List<User> getUsersByCountryAndAgeNative(String cname, Integer uage);
		
	@Query(value = "select username from User where userid = :uid")
	public String getUsernamebyUserid(Integer uid);

JpaRepository

Note: CrudRepository interface doesn't support Sorting + Pagination + QBE

CrudRepositoy vs JPARepository

Sorting

	// Sorting
	List<User> users = repository.findAll(Sort.by("username"));
	users.forEach(user -> System.out.println(user));
		
	List<User> users = repository.findAll(Sort.by("age").descending());
	users.forEach(user -> System.out.println(user));
		
	List<User> users = repository.findAll(Sort.by("gender", "age"));	
	users.forEach(user -> System.out.println(user));

Pagination

Pagination

	// Pagination
	int pageSize = 5;
	int pageNo = 0;
		
	PageRequest pageRequest = PageRequest.of(pageNo, pageSize);
	
	Page<User> page = repository.findAll(pageRequest);
	
	List<User> users = page.getContent();
	
	System.out.println("Total pages :: "+page.getTotalPages());
	users.forEach(user -> System.out.println(user));
	// Pagination with sorting
	int pageSize = 5;
	int pageNo = 1;
	
	PageRequest pageRequest = PageRequest.of(pageNo, pageSize, Sort.by("age").descending());
	
	Page<User> page = repository.findAll(pageRequest);
	
	List<User> users = page.getContent();
	
	System.out.println("Total pages :: " + page.getTotalPages());
	users.forEach(user -> System.out.println(user));

Query By Example (QBE)

	// Query By Example
	User entity = new User();
	
	entity.setGender("Male");
	entity.setCountry("India");
	
	Example<User> example = Example.of(entity);
	
	List<User> users = repository.findAll(example);
	
	users.forEach(user -> System.out.println(user));
	// QBE with sorting
	User entity = new User();
	entity.setGender("Male");
	
	List<User> users = repository.findAll(Example.of(entity), Sort.by("age").descending());
	
	users.forEach(user -> System.out.println(user));
	// QBE with pagination
	User entity = new User();
	entity.setGender("Male");
	
	Page<User> page = repository.findAll(Example.of(entity), PageRequest.of(1, 3));
	
	List<User> users = page.getContent();
	
	System.out.println("Total pages :: " + page.getTotalPages());
	users.forEach(user -> System.out.println(user));

Timestamping

Note: In realtime we will maintain these 4 columns for every table.

	// Timestamping
	@Data
	@Entity
	@Table(name="PRODUCT_MASTER")
	public class Product {
		@Id
		@Column(name="PRODUCT_ID")
		private Integer pid;
		
		@Column(name="PRODUCT_NAME")
		private String pname;
		
		@Column(name="PRODUCT_PRICE")
		private Double price;
		
		@CreationTimestamp
		@Column(name="CREATED_ON", updatable = false)
		private LocalDateTime createdOn;
		
		@UpdateTimestamp
		@Column(name="UPDATED_ON", insertable = false)
		private LocalDateTime updatedOn;
	}

Note: updatable = false means that column value should not updated when we perform update operation.

Note: insertable = false means that column value should not inserted when we perform insert operation.

Q) Why to use Wrapper classes instead of Primitive data types in Entity class ?
Ans: We use wrapper classes instead of primitive types for entity attributes in Java because wrapper classes have a default value of null. This ensures that if no value is provided, null will be stored in the database.

Primary Key Generation

	@Id
	@Column(name="PRODUCT_ID")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer pid;

Note: For MySQL DB, GenerationType.IDENTITY is the recommended generation strategy.

	@Id
	@Column(name="PRODUCT_ID")
	@GeneratedValue(strategy = GenerationType.SEQUENCE)
	private Integer pid;

Note: For Oracle DB, GenerationType.SEQUENCE is the recommended generation strategy.

PK Generation using Custom Sequence

	// Primary key generation using custom sequence
	@Data
	@Entity
	@Table(name="PRODUCT_MASTER")
	public class Product {
		@Id
		@Column(name="PRODUCT_ID")
		@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_gen")
		@SequenceGenerator(name = "id_gen", sequenceName = "product_id_seq", allocationSize = 1)
		private Integer productid;
		
		@Column(name="PRODUCT_NAME")
		private String productName;
		
		@Column(name="PRODUCT_PRICE")
		private Double productPrice;
	}

Note: Here id_gen is alias name for our custom sequence. It should be same in @GeneratedValue and @SequenceGenerator annotations.

Note: allocationSize must be equal to Increment by. Default value for allocation size is 50.

Custom PK Generation

Use case
	Product Ids    --->    PID1, PID2, PID3 ........
	Emp Ids    --->    TCS1, TCS2, TCS3 ........
	Order Ids    --->    OD1, OD2, OD3 ........

@GenericGenerator

Note: @GenericGenerator annotation is deprecated. Instead of this we can use @IdGeneratorType annotation.

	// Entity class
	@Data
	@Entity
	@Table(name="PRODUCT_MASTER")
	public class Product {
		@Id
		@Column(name="PRODUCT_ID")
		@GeneratedValue(generator = "id_gen")
		@GenericGenerator(name = "id_gen", strategy = "in.ashokit.generator.ProductIdGenerator")
		private String productid;
		
		@Column(name="PRODUCT_NAME")
		private String productName;
		
		@Column(name="PRODUCT_PRICE")
		private Double productPrice;
	}

Note: Here id_gen is alias name for our custom generator. It should be same in @GeneratedValue and @GenericGenerator annotations.

	// Custom Generator
	public class ProductIdGenerator implements IdentifierGenerator {
		@Override
		public Object generate(SharedSessionContractImplementor session, Object object) {
			String prefix = "PID";
			String suffix = "";
			
			try {
				JdbcConnectionAccess ca = session.getJdbcConnectionAccess();
				Connection conn = ca.obtainConnection();
				Statement stmt = conn.createStatement();
				ResultSet rs = stmt.executeQuery("select product_id_seq.nextval from dual");
				if(rs.next()) {
					int seqval = rs.getInt(1);
					suffix = String.valueOf(seqval);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			return prefix + suffix;
		}
	}

@IdGeneratorType

	// Custom annotation
	@Retention(RUNTIME)
	@Target(FIELD)
	@IdGeneratorType(ProductIdGenerator.class)
	public @interface ProductIdGenerated {
		
	}
	// Entity class
	@Data
	@Entity
	@Table(name="PRODUCT_MASTER")
	public class Product {
		@Id
		@Column(name="PRODUCT_ID")
		@ProductIdGenerated
		private String productid;
		
		@Column(name="PRODUCT_NAME")
		private String productName;
		
		@Column(name="PRODUCT_PRICE")
		private Double productPrice;
	}

Note: In this approach no changes are required in ProductIdGenerator.

Composite Primary Keys

	// Embeddable class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	@Embeddable
	public class AccountCompoositeKey implements Serializable {
		@Column(name="ACCOUNT_NO")
		private Integer accountNo;
		
		@Column(name="IFSC_CODE")
		private String ifsc;
	}
	// Entity class
	@Data
	@Entity
	@Table(name="ACCOUNT__MASTER")
	public class Account {
		@EmbeddedId
		private AccountCompoositeKey compositeKey;
		
		@Column(name="BRAMCH_NAME")
		private String branch;
		
		@Column(name="ACCOUNT_BALANCE")
		private Double balance;
	}

Note: For Composite Primary Key's we can't use Generator. We have to set the values manually.

Tx Management in Data JPA

	// How to rollback failed tx
	@Transactional(rollbackOn = Exception.class)
	public void saveData() {
		Employee emp = new Employee();
		emp.setEmpId(101);
		emp.setEmpName("Raju");
		emp.setSalary(25000.0);
		empRepo.save(emp);
		
		int i = 10 / 0;
		
		Address add = new Address();
		add.setAddId(501);
		add.setCity("Pune");
		add.setState("MH");
		add.setCountry("India");
		add.setEmpId(101);
		addRepo.save(add);
	}

Working with Images

Requirement : Develop Data JPA Application to insert image file into DB and retrieve it from DB.

	// Entity class
	@Setter
	@Getter
	@Entity
	@Table(name="USER_TBL")
	public class User {
		@Id
		@Column(name="USER_ID")
		private Integer userId;
		
		@Column(name="USER_NAME")
		private String userName;
		
		@Column(name="USER_EMAIL")
		private String email;
		
		@Lob
		@Column(name="USER_IMG", columnDefinition = "LONGBLOB")
		private byte[] img;
	
		@Override
		public String toString() {
			return "User [userId=" + userId + ", userName=" + userName + ", email=" + email + "]";
		}
	}
	// Service class
	@Service
	@AllArgsConstructor
	public class UserService {
		private UserRepository repo;
	
		@SneakyThrows
		public void saveUser() {
			String imgPath = "input-img-path";
			
			User user = new User();
			user.setUserId(101);
			user.setUserName("Raju");
			user.setEmail("raju@gmail.com");
			user.setImg(Files.readAllBytes(Path.of(imgPath)));
			repo.save(user);
			
			System.out.println("Record Inserted .. !!");
		}
		
		@SneakyThrows
		public void findUser() {
			String imgPath = "output-img-path";
			
			User user = repo.findById(101).get();
			System.out.println(user);
			
			Files.write(Path.of(imgPath), user.getImg());
			System.out.println("Output img stored .. !!");
		}
	}

Note: Instead of using byte[], we can use Blob to store images. If we use Blob, we don’t need to add @Lob or specify columnDefinition = "LONGBLOB" because JPA automatically treats Blob as a large object.

Profiles in Spring Boot

Note: Environment means platform to run our application (Linux VM, DB Server, Log Server etc...)

Profiles

	# Activating dev profile
	spring.profiles.active=dev

Spring Web MVC

What is Spring Web MVC ?

Web Application vs Distributed Application

Note: SOAP Webservices & RESTFul Services can be developed using Spring Web MVC.

Advantages of Spring Web MVC

Spring Web MVC Architecture

DispatcherServlet

HandlerMapper

Controller

ModelAndView

ViewResolver

View

Spring Web MVC Architecture

First Web App using Spring Web MVC

Note: web-starter will provide the support to build web apps with MVC architecture and it provides Tomcat as default embedded container (we no need to setup server manually)

Note: tomcat-embed-jasper will provide the support to work with JSP files in Spring Web MVC

Note: devtools is used to re-start the server when changes happened in the code

Note: Java class will be represented as a Spring Controller using @Controller annotation

Note: Controller class methods should be binded to HTTP Protocol methods to handle HTTP Requests

Note: If we select Packaging as Jar then webapp folder will not be created. If we select Packaging as War then it will be created automatically.

	<!-- tomcat-embed-jasper dependency -->
	<dependency>
		<groupId>org.apache.tomcat.embed</groupId>
		<artifactId>tomcat-embed-jasper</artifactId>
	</dependency>
	// Controller class
	@Controller
	public class WelcomeController {
		@GetMapping("/welcome")
		public ModelAndView getWelcomeMsg() {
			ModelAndView mav = new ModelAndView();
			// setting model data
			mav.addObject("msg", "Welcome to Ashok IT !!");
			
			// setting view name
			mav.setViewName("index");
			return mav;
		}
	}

Note: Controller is returning just view name (without extension) which makes it loosely coupled with the presentation technology.

	# view resolver configuration
	spring.mvc.view.prefix=/views/
	spring.mvc.view.suffix=.jsp
	<!-- Index Page -->
	<%@ page language="java" contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>Home</title>
	</head>
	<body>
		<h2>${msg}</h2>
	</body>
	</html>

What is Context-Path ?

Note: By default Embedded Tomcat Server will run on the port number 8080.

Accessing Web Application

Note: Every controller class method should be mapped to a unique url-pattern. If the url-pattern is same then the request type must be different otherwise it will result url-pattern collision.

Why default context-path is empty in Spring Boot ?

External Server
Embedded Server

External Server vs Embedded Server

Sending Data from Controller to UI

ModelAndView

	// Using ModelAndView object
	@Controller
	public class WelcomeController {
		@GetMapping("/welcome")
		public ModelAndView getWelcomeMsg() {
			ModelAndView mav = new ModelAndView();
			// setting model data
			mav.addObject("msg", "Welcome to SBMS !!");
			
			// setting view name
			mav.setViewName("welcome");
			return mav;
		}
	}

Model

	// Using Model object
	@Controller
	public class GreetController {
		@GetMapping("/greet")
		public String getGreetMsg(Model model) {
			// setting model data
			model.addAttribute("msg", "Good Afternoon !!");
			
			// returning view name
			return "greet";
		}
	}

Note: Generally returning view name is more common instead of ModelAndView object.

@ResponseBody

Note: In realtime we will use this approach to develop REST API's (distributed apps)

	// Using @ResponseBody annotation
	@Controller
	public class WishController {
		@ResponseBody
		@GetMapping("/wish")
		public String getWishMsg() {
			// returning response directly
			return "All the Best !!";
		}
	}

Note: If we want to develop the presentation logic within the same app (JSP, Thymeleaf) then the controllers should return view name. But if we want to develop presentation logic as a separate project (Angular, React) or presentation logic is not required for our app (B2B app) then the controllers should return response directly.

Note: By default, the value returned by controller methods is considered as view name, unless we use @RestController or @ResponseBody.

	@RestController    =    @Controller   +   @ResponseBody

Sending Object from Controller to UI

	// Binding class
	@Data
	@AllArgsConstructor
	public class Book {
		private Integer bookId;
		private String bookName;
		private Double bookPrice;
	}
	// Controller class
	@Controller
	public class BookController {
		@GetMapping("/book")
		public String getBookData(Model model) {
			// creating binding object
			Book obj = new Book(101, "Spring", 650.0);
			
			// seting model data
			model.addAttribute("book", obj);
			
			// returning view name
			return "book";
		}
	}
	<!-- Book Page -->
	<%@ page language="java" contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>Book Page</title>
	</head>
	<body>
		<h2>Book Details</h2>
		<table border="1">
			<thead>
				<tr>
					<th>Book Id</th>
					<th>Book Name</th>
					<th>Book Price</th>
				</tr>
			</thead>
			<tbody>
				<tr>
					<td>${book.bookId}</td>
					<td>${book.bookName}</td>
					<td>${book.bookPrice}</td>
				</tr>
			</tbody>
		</table>
	</body>
	</html>

Assignment: Develop Spring Boot web application to display multiple books in a table format.

	<!-- JSTL dependencies -->
	<dependency>
		<groupId>jakarta.servlet.jsp.jstl</groupId>
		<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
	</dependency>
	<dependency>
		<groupId>org.glassfish.web</groupId>
		<artifactId>jakarta.servlet.jsp.jstl</artifactId>
	</dependency>

Note: We have added JSTL dependency to iterate the List in UI.

	// Binding class
	@Data
	@AllArgsConstructor
	public class Book {
		private Integer bookId;
		private String bookName;
		private Double bookPrice;
	}
	// Controller class
	@Controller
	public class BookController {
		@GetMapping("/books")
		public String getBooks(Model model) {
			Book b1 = new Book(101, "Spring", 650.0);
			Book b2 = new Book(102, "Hibernate", 450.0);
			Book b3 = new Book(103, "DevOps", 850.0);
			
			// setting data
			model.addAttribute("books", Arrays.asList(b1, b2, b3));
			
			// returning view name
			return "books";
		}
	}
	<!-- Books Page -->
	<%@ page language="java" contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	    <%@ taglib prefix="c" uri="jakarta.tags.core" %>
	    
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>Books Page</title>
	</head>
	<body>
		<h2>Book Details</h2>
		<table border="1">
			<thead>
				<tr>
					<th>Book Id</th>
					<th>Book Name</th>
					<th>Book Price</th>
				</tr>
			</thead>
			<tbody>
				<c:forEach items="${books}" var="book">
					<tr>
						<td>${book.bookId}</td>
						<td>${book.bookName}</td>
						<td>${book.bookPrice}</td>
					</tr>
				</c:forEach>		
			</tbody>
		</table>
	</body>
	</html>

Thymeleaf

JSP vs Thymeleaf

Steps to Develop First Thymeleaf App

Note: No need to configure view resolver because Spring Boot will auto detect Thymeleaf template files and will process them.

	// Controller class
	@Controller
	public class WelcomeController {
		@GetMapping("/welcome")
		public String getWelcomeMsg(Model model) {
			model.addAttribute("msg", "Welcome to Ashok IT !!");
			return "welcome";
		}
	}
	<!-- Welcome Page -->
	<!DOCTYPE html>
	<html xmlns:th="http://www.thymeleaf.org">
	<head>
	<meta charset="UTF-8">
	<title>Welcome Page</title>
	</head>
	<body>
		<p th:text=${msg}/>
	</body>
	</html>

Note: Below line is used to tell the browser that we are using Thymeleaf in this page and th: is the prefix.

	<html xmlns:th="http://www.thymeleaf.org">

Form Binding

From Binding

Steps to develop Form based App

	// Binding class
	@Data
	public class Product {
		private Integer productId;
		private String productName;
		private Double productPrice;
	}
	// Controller class
	@Controller
	public class ProductController {
		// method to display empty form
		@GetMapping("/")
		public String loadForm(Model model) {
			Product obj = new Product();
			model.addAttribute("product", obj);
			return "index";
		}
		
		// method to handle form submission
		@PostMapping("/product")
		public String submitForm(Product product) {
			System.out.println(product);
			return "success";
		}
	}
	<!-- Product Page -->
	<!DOCTYPE html>
	<html xmlns:th="http://www.thymeleaf.org">
	<head>
	<meta charset="UTF-8">
	<title>Product Page</title>
	</head>
	<body>
		<h2>Product Form</h2>
		<form th:action="@{/product}" th:object="${product}" method="POST">
			<table>
				<tr>
					<td>Product Id</td>
					<td><input type="text" th:field="*{productId}" /></td>
				</tr>
				<tr>
					<td>Product Name</td>
					<td><input type="text" th:field="*{productName}" /></td>
				</tr>
				<tr>
					<td>Product Price</td>
					<td><input type="text" th:field="*{productPrice}" /></td>
				</tr>
				<tr>
					<td></td>
					<td><input type="submit" value="Save" /></td>
				</tr>
			</table>
		</form>
	</body>
	</html>
	<!-- Success Page -->
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>Success</title>
	</head>
	<body>
		<h2>Product Saved Successfully</h2>
		<a href="/">Go Back</a>
	</body>
	</html>

Form Validations

Note: It is not recommended to use primitive types for binding class variables because they can't hold null values. Instead, they have default values like 0 or false, which makes it difficult to perform validations.

	// Binding class
	@Data
	public class Person {
		@Size(min = 4, max = 16, message = "Name should be 4 to 16 characters")
		@NotEmpty(message = "Name is required")
		private String name;
		
		@Min(value = 18, message = "Minimum age should be 18")
		@NotNull(message = "Age is required")
		private Integer age;
	}
	// Controller class
	@Controller
	public class PersonController {
		@GetMapping("/")
		public String loadForm(Model model) {
			Person obj = new Person();
			model.addAttribute("person", obj);
			return "index";
		}
		
		@PostMapping("/person")
		public String submitForm(@Valid Person person, BindingResult result, Model model) {
			// to load same page, if validation fails
			if(result.hasErrors()) {
				return "index";
			}
			
			System.out.println(person);
			model.addAttribute("msg", "Person Recod Saved !!");
			return "success";
		}
	}

Note: To validate the form data @Valid annotation is used. It will validate the data and store the result into BindingResult object. BindingResult object is used to check errors.

	<!-- Person Form -->
	<!DOCTYPE html>
	<html xmlns:th="http://www.thymeleaf.org">
	<head>
	<meta charset="UTF-8">
	<title>Person Page</title>
	<style>
	.error {
		color: red
	}
	</style>
	</head>
	<body>
		<h2>Person Form</h2>
		<form th:action="@{/person}" th:object="${person}" method="POST">
			<table>
				<tr>
					<td>Name</td>
					<td><input type="text" th:field="*{name}" /></td>
					<!-- To print validation message -->
					<td class="error" th:errors="*{name}"></td>
				</tr>
				<tr>
					<td>Age</td>
					<td><input type="text" th:field="*{age}" /></td>
					<!-- To print validation message -->
					<td class="error" th:errors="*{age}"></td>
				</tr>
				<tr>
					<td></td>
					<td><input type="submit" value="Save" /></td>
				</tr>
			</table>
		</form>
	</body>
	</html>
	<!-- Success Page -->
	<!DOCTYPE html>
	<html xmlns:th="http://www.thymeleaf.org">
	<head>
	<meta charset="UTF-8">
	<title>Success Page</title>
	</head>
	<body>
		<p th:text="${msg}" />
		<a href="/">Go Back</a>
	</body>
	</html>

Assignment : Create a User registration form with below fields and perform validations.

	1) Username
	2) Password
	3) Email
	4) Phno
	5) Age
	// Binding class
	@Data
	public class User {
		@Size(min = 4, max = 16, message = "Username must be 4 to 16 characters")
		@NotEmpty(message = "Username is required")
		private String uname;
		
		@Size(min = 8, max = 16, message = "Password must be 8 to 16 characters")
		@NotEmpty(message = "Password is required")
		private String pwd;
		
		@Email(message = "Enter valid email")
		@NotEmpty(message = "Email is required")
		private String email;
		
		@Size(min = 10, message = "Phone No atleast have 10 digits")
		@NotEmpty(message = "Phone No is required")
		private String phno;
		
		@Min(value = 12, message = "Minimum age should be 12")
		@NotNull(message = "Age is required")
		private Integer age;
	}

Note: @Email annotation is used to validate email.

	// Controller class
	@Controller
	public class UserController {
		@GetMapping("/")
		public String loadForm(Model model) {
			User obj = new User();
			model.addAttribute("user", obj);
			return "index";
		}
		
		@PostMapping("/user")
		public String submitForm(@Valid User user, BindingResult result, Model model) {
			if(result.hasErrors()) {
				return "index";
			}
			
			System.out.println(user);
			model.addAttribute("msg", "User Record Saved !!");
			return "success";
		}
	}
	<!-- User Form -->
	<!DOCTYPE html>
	<html xmlns:th="http://www.thymeleaf.org">
	<head>
	<meta charset="UTF-8">
	<title>User Page</title>
	<style type="text/css">
		.error{
			color : red
		}
	</style>
	</head>
	<body>
		<h2>User Form</h2>
		<form th:action="@{/user}" th:object="${user}" method="POST" >
			<table>
				<tr>
					<td>Username</td>
					<td><input type="text" th:field="*{uname}" /></td>
					<td class="error" th:errors="*{uname}"></td>
				</tr>
				<tr>
					<td>Password</td>
					<td><input type="password" th:field="*{pwd}" /></td>
					<td class="error" th:errors="*{pwd}"></td>
				</tr>
				<tr>
					<td>Email</td>
					<td><input type="text" th:field="*{email}" /></td>
					<td class="error" th:errors="*{email}"></td>
				</tr>
				<tr>
					<td>Phone No</td>
					<td><input type="text" th:field="*{phno}" /></td>
					<td class="error" th:errors="*{phno}"></td>
				</tr>
				<tr>
					<td>Age</td>
					<td><input type="number" th:field="*{age}" /></td>
					<td class="error" th:errors="*{age}"></td>
				</tr>
				<tr>
					<td></td>
					<td><input type="submit" value="Save" /></td>
				</tr>
			</table>
		</form>
	</body>
	</html>

Note: We have taken email field type="text" to avoid the client side validation.

	<!-- Success Page -->
	<!DOCTYPE html>
	<html xmlns:th="http://www.thymeleaf.org">
	<head>
	<meta charset="UTF-8">
	<title>Success Page</title>
	</head>
	<body>
		<p th:text="${msg}" />
		<a href="/">Go Back</a>
	</body>
	</html>

Forms Development using Web MVC

	<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

Note: To map Binding class with From we use modelAttribute attribute.

Note: To map Binding class variable with Form field we use path attribute.

	// Binding class
	@Data
	public class Product {
		private Integer productId;
		private String productName;
		private Double productPrice;
	}
	// Controller class
	@Controller
	public class ProductController {
		@GetMapping("/")
		public String loadForm(Model model) {
			Product obj = new Product();
			model.addAttribute("product", obj);
			return "index";
		}
		
		@PostMapping("/product")
		public String submitForm(Product p, Model model) {
			System.out.println(p);
			model.addAttribute("msg", "Product Saved Successfully");
			return "success";
		}
	}
	<!-- Home Page -->
	<%@ page language="java" contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	    <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
	    
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>Product Page</title>
	</head>
	<body>
		<h2>Product Form</h2>
		<form:form action="product" modelAttribute="product" method="POST">
			<table>
				<tr>
					<td>Product Id</td>
					<td><form:input path="productId"/></td>
				</tr>
				<tr>
					<td>Product Name</td>
					<td><form:input path="productName"/></td>
				</tr>
				<tr>
					<td>Product Price</td>
					<td><form:input path="productPrice"/></td>
				</tr>
				<tr>
					<td></td>
					<td><input type="submit" value="Save" /></td>
				</tr>
			</table>
		</form:form>
	</body>
	</html>
	<!-- Success Page -->
	<%@ page language="java" contentType="text/html; charset=UTF-8"
	    pageEncoding="UTF-8"%>
	<!DOCTYPE html>
	<html>
	<head>
	<meta charset="UTF-8">
	<title>Success Page</title>
	</head>
	<body>
		<h2>${msg}</h2>
		<a href="/">Go Back</a>
	</body>
	</html>

Embedded Servers

Note: We can deploy spring boot application into external servers also as a war file.

How to change default container ?

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
		<exclusions>
			<exclusion>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-starter-tomcat</artifactId>
			</exclusion>
		</exclusions>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jetty</artifactId>
	</dependency>

RESTFul Services

RESTFul Services

Distributed Application

Provider and Consumer 1

Note: One provider application can have multiple consumer applications and one consumer application can also have multiple provider applications.

Provider and Consumer 2

Note: XML and JSON are interoperable (platform independent & language independent)

Note: JSON is recommended format to exchange the data from one application to another application. XML is outdated.

Q) Why we don't use objects to exchange data between Consumer and Provider application ?
Ans: Objects are language dependent. If we use objects to exchange data then we can't achieve interoperability.

What is HTTP ?

HTTP

HTTP Methods

GET

Note: Path Params & Query Params data will be displayed in the URL hence it is not recommended to send sensitive data using Path Params & Query Params.

Q) Why GET request does not have request body ?
Ans: GET request is not meant for sending the data it is meant for retrieving the data hence it does not contain the request body.

POST

Note: Request Body is the recommended approach to send sensitive data.

PUT

DELETE

HTTP Request & Response Structure

HTTP Request and Response Structure

HTTP Status Codes

XML

	<!-- XML element -->
	<name> Ashok IT </name>
	<!-- Simple element -->
	<name> Ashok IT </name>
	<type> Educational </type>
	<!-- Compound element -->
	<person>
		<id> 101 </id>
		<name> Raju </name>
	</person>

Note: Here person is a compound element and it contains id & name as child elements.

	<!-- XML element with attribute -->
	<student  branch="CSE">
		<id> 101 </id>
		<name> Mahesh </name>
	</student>

Note: Here student is the compound element, branch is the attribute and id & name are the simple elements.

	<!-- Root element with multiple child elements -->
	<persons>
		<person>
			<id> 101 </id>
			<name> Raju </name>
		</person>
		
		<person>
			<id> 102 </id>
			<name> Rani </name>
		</person>
	</persons>

Note: Here persons is the root element.

JAX-B API

Marshalling and Un-marshalling

Note: Marshalling & Un-marshalling will be performed at both Consumer and Provider sides hence binding classes are also required at both sides.

Note: Earlier XSD is used to generate binding classes but XSD became outdated. Nowadays we are writing binding classes directly without XSD.

Note: XSD stands for XML Schema Definition which is used to represent the structure of XML document.

JAX-B Annotations

@XmlRootElement : It is used to represent the Java class as the root element of the XML document.

@XmlAccessorType : It s used to specify whether to use fields or setters & getters to perform marshalling and un-marshalling. By default setters & getters will be used to perform marshalling & un-marshalling.

	XmlAccessType.FIELD    --->    To use fields for marshall and un-marshall
	XmlAccessType.PROPERTY    --->    To use setters & getters for marshall & un-marshall

Note: If you are using fields to perform marshalling & un-marshalling then the order of elements in the XML document will be same as the order of variables in the Java class.

Note: If you are using setters & getters to perform marshalling & un-marshalling then the XML document will have elements in alphabetical order.

@XmlAccessorOrder : It is used to specify the order in which elements are accessed during marshalling and un-marshalling.

	@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)

@XmlElement : It is used to change the name of an element in the XML document.

	@XmlElement(name = "elementName")

@XmlAttribute : It is used to represents a variable as an attribute in the XML document.

@XmlTransient : It is used to skip a variable in marshalling and un-marshalling.

Note: By default every variable of Java class will be considered as element and variable name will be considered as element name in the XML document.

Note: If we are using JDK 1.9 or above then we need to add JAX-B dependency in pom.xml file because JAX-B is part of JDK only upto 1.8 version.

	<!-- JAX-B dependency -->
	<dependency>
		<groupId>javax.xml.bind</groupId>
		<artifactId>jaxb-api</artifactId>
		<version>2.3.1</version>
	</dependency>
	// Binding class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	@XmlRootElement
	@XmlAccessorType(XmlAccessType.FIELD)
	public class Person {
	
		private Integer id;
		private String name;
		
		@XmlAttribute
		private Integer age;
		
		@XmlElement(name = "phoneNum")
		private Long phno;
		
		@XmlTransient
		private Address address;
	}
	// Java to XML converter
	public class JavaToXml {
		public void convertJavaToXml(Person person) throws Exception {
		
			JAXBContext context = JAXBContext.newInstance(Person.class);
			Marshaller marshaller = context.createMarshaller();
			marshaller.marshal(person, new File("Person.xml"));
			
			System.out.println("Marshalling completed .. !!");
		}
	}
	// XML to Java converter
	public class XmlToJava {
		public void convertXmlToJava() throws Exception {
		
			JAXBContext context = JAXBContext.newInstance(Person.class);
			Unmarshaller unmarshaller = context.createUnmarshaller();
			Person person = (Person) unmarshaller.unmarshal(new File("Person.xml"));
			
			System.out.println(person);
			System.out.println("Un-marshalling completed .. !!");
		}
	}

JSON

	{
		"id": 101,
		"name": "Raju",
		"age": 24
	}

Note: JSON is preferred over XML to exchange the data between applications.

Q) Why JSON is preferred over XML for exchanging the data between applications ?
Ans: XML uses opening and closing tags for each element which increases the size of the data and consumes more memory whereas JSON will represent the data in key-value pair format which is more simple and light weight. Hence JSON is preferred over XML.

XML vs Json

Jackson API

	<!-- Jackson dependency -->
	<dependency>
		<groupId>com.fasterxml.jackson.core</groupId>
		<artifactId>jackson-databind</artifactId>	
		<version>2.19.1</version>
	</dependency>
	// Author class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Author {
		private Integer authorId;
		private String authorName;
		private String authorEmail;
	}
	// Binding class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Book {
		private Integer bookId;
		private String bookName;
		private Double bookPrice;
		private Author author;
	}
	// Converter class
	public class Converter {
		public void javaToJson(Book book) throws Exception {
			ObjectMapper mapper = new ObjectMapper();
			mapper.writeValue(new File("book.json"), book);
			System.out.println("Java object converted to Json .. !!");
		}
		
		public void jsonToJava() throws Exception {
			ObjectMapper mapper = new ObjectMapper();
			Book book = mapper.readValue(new File("book.json"), Book.class);
			System.out.println("Json converted to Java object .. !!");
			System.out.println(book);
		}
	}

Gson API

Note: fromJson() method will return Json data in String format.

	<!-- Gson dependency -->
	<dependency>
		<groupId>com.google.code.gson</groupId>
		<artifactId>gson</artifactId>
		<version>2.13.1</version>
	</dependency>
	// Category class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Category {
		private Integer categoryId;
		private String categoryName;
	}
	// Binding class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Product {
		private Integer productId;
		private String productName;
		private Double productPrice;
		private Category category;
	}
	// Converter class
	public class Converter {
		public void javaToJson(Product product) throws Exception {
			Gson gson = new Gson();
			String jsonData = gson.toJson(product);
			
			FileWriter writer = new FileWriter("product.json");
			writer.write(jsonData);
			writer.close();
			
			System.out.println("Java object converted to Json !!");
		}
		
		public void jsonToJava() throws Exception {
			Gson gson = new Gson();
			Product product = gson.fromJson(new FileReader("product.json"), Product.class);
			System.out.println("Json converted to Java object !!");
			System.out.println(product);
		}
	}

How to develop RESTFul Services ?

Note: It is recommended to develop RESTFul Services using Spring Web MVC module. Developing RESTFul Services using JAX-RS API is legacy approach.

RESTFul Services Architecture

Note: RESTFul Services are used for B2B communications hence presentation logic & view resolver are not required.

First RESTFul Service using Spring Boot

Note: To represent Java class as Rest Controller we will use @RestController annotation.

	@RestController   =   @Controller   +   @ResponseBody

Note: Every Rest Controller method should be bind to a HTTP Protocol method using one of the below annotations.

	@GetMapping    --->   HTTP GET Method    --->    To retrieve the data
	@PostMapping    --->    HTTP POST Method    --->    To send the data
	@PutMapping    --->    HTTP PUT Method    --->    To update the data
	@DeleteMapping    --->    HTTP DELETE Method    --->    To delete the data

Note: To test the RESTFul Services we will use Postman tool.

	// Approach-1 (Using ResponseEntity)
	@RestController
	public class WelcomeRestController {
		@GetMapping("/welcome")
		public ResponseEntity<String> getWelcomeMsg() {
			String respPayload = "Welcome to Ashok IT !!";
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
	}

Note: ResponseEntity uses generic type parameter to specify the type of the response body.

	// Approach-2 (Returning payload directly)
	@RestController
	public class GreetRestController {
		@GetMapping("/greet")
		public String getGreetMsg() {
			String respPayload = "Good Afternoon !!";
			return respPayload;
		}
	}

Note: First approach is the recommended approach to develop the Rest Controllers because it offers more control over the HTTP response.

HTTP GET Request

Query Params

Note: If our controller method does not define any query params and we supply query params in the URL then the controller will simply ignore the query params.

	// REST API using query param
	@RestController
	public class WelcomeRestController {
		@GetMapping("/welcome")
		public ResponseEntity<String> getWelcomeMsg(@RequestParam("name") String name) {
			String respPayload = name + ", Welcome to Ashok IT !!";
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
	}
	URL: http://localhost:8080/welcome?name=Raju
	// Working with multiple query params
	@RestController
	public class CourseRestController {
		@GetMapping("/course")
		public ResponseEntity<String> getCourseFee(@RequestParam("cname") String courseName,
				@RequestParam("tname") String trainerName) {
				
			String respPayload = courseName + " By " + trainerName + " Fee is 8000 INR";
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
	}
	URL: http://localhost:8080/course?cname=SBMS&tname=Ashok

Path Params or URI Variables

Note: If we don't specify Path Params explicitly in the URL pattern then the server will not be able to identify them.

	// REST API using path params
	@RestController
	public class BookRestController {
		@GetMapping("/book/{name}")
		public ResponseEntity<String> getBookPriceByName(@PathVariable("name") String name) {
			String respPayload = name + " Book Price is 4000 INR";
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
	
		@GetMapping("/book/name/{bname}/author/{aname}")
		public ResponseEntity<String> getBookPriceByNameAndAuthor(@PathVariable("bname") String name,
				@PathVariable("aname") String author) {
			
			String respPayload = name + " By " + author + " Price is 4000 INR";
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
	
		@GetMapping("/book/{name}/{author}/{category}")
		public ResponseEntity<String> getBookPriceByNameAndAuthorAndCategory(@PathVariable("name") String name,
				@PathVariable("author") String author, @PathVariable("category") String category) {
			
			String str1 = name + " By " + author + " Price is 4000 INR.";
			String str2 = " It is a " + category + " Book.";
			String respPayload =  str1 + str2;
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
	}
	URL-1: http://localhost:8080/book/SpringBoot
	
	URL-2: http://localhost:8080/book/name/SpringBoot/author/RodJohnson
	
	URL-3: http://localhost:8080/book/SpringBoot/RodJohnson/Educational
	@RestController
	public class BookRestController {
		@GetMapping("/book/{name}")
		public ResponseEntity<String> getBookPrice(@PathVariable("name") String name) {
			String respPayload = name + " Book Price is 4000 INR";
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
		
		@GetMapping("/book/Python")
		public ResponseEntity<String> getPythonBookPrice() {
			String respPayload = "Python Book Price is 5000 INR";
			return new ResponseEntity<>(respPayload, HttpStatus.OK);
		}
	}

Note: If the value of the path param is Python then only the getPythonBookPrice() controller will be executed. For all other values the getBookPrice() controller will be executed.

Q) When to use Path Params & Query Params ?
Ans: If we want to retrieve more than one resource then we will use Query Params and if we want to retrieve specific / unique resource then we will use Path Params.

What is Produces ?

Produces Attribute

Note: If we write the code in the Rest Controller method to manually convert the response payload then the Rest Controller becomes tightly coupled with the response payload format. To avoid this Spring provided Message Converter that automatically handle the conversions.

	<!-- JAXB support -->
	<dependency>
		<groupId>jakarta.xml.bind</groupId>
		<artifactId>jakarta.xml.bind-api</artifactId>
	</dependency>
	
	<dependency>
		<groupId>org.glassfish.jaxb</groupId>
		<artifactId>jaxb-runtime</artifactId>
	</dependency>
	// Binding class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	@XmlRootElement
	public class Product {
		private Integer pid;
		private String pname;
		private Double price;
	}
	// ProductRestController
	@RestController
	public class ProductRestController {
		@GetMapping(
			value = "/product",
			produces = { "application/json", "application/xml" }
		)
		public ResponseEntity<Product> getProduct() {
			Product product = new Product(101, "Laptop", 45000.0);
			return new ResponseEntity<>(product, HttpStatus.OK);
		}
		
		@GetMapping(value = "/products")
		public ResponseEntity<List<Product>> getProducts() {
		
			Product p1 = new Product(101, "Laptop", 45000.0);
			Product p2 = new Product(102, "Mobile", 22000.0);
			Product p3 = new Product(103, "Mouse", 240.0);
			
			return new ResponseEntity<>(Arrays.asList(p1, p2, p3), HttpStatus.OK);
		}
	}

Note: The produces attribute can have multiple values and the first value is used as the default response format when the client doesn't provide an Accept header.

	<!-- JAXB support -->
	<dependency>
		<groupId>jakarta.xml.bind</groupId>
		<artifactId>jakarta.xml.bind-api</artifactId>
	</dependency>
	
	<dependency>
		<groupId>org.glassfish.jaxb</groupId>
		<artifactId>jaxb-runtime</artifactId>
	</dependency>
	// Product class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Product {
		private Integer pid;
		private String pname;
		private Double price;
	}
	// Binding class wrapping list of products
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	@XmlRootElement
	public class ProductList {
		List<Product> products;
	}
	// ProductRestController
	@RestController
	public class ProductRestController {
		@GetMapping(
			value = "/products",
			produces = { "application/json", "application/xml" }
		)
		public ResponseEntity<ProductList> getProducts() {
		
			Product p1 = new Product(101, "Laptop", 45000.0);
			Product p2 = new Product(102, "Mobile", 22000.0);
			Product p3 = new Product(103, "Mouse", 240.0);
			
			ProductList list = new ProductList(Arrays.asList(p1, p2, p3));
			
			return new ResponseEntity<>(list, HttpStatus.OK);
		}
	}

Default Response format for REST API's

HTTP POST Request

Consumes Attribute

	<!-- JAXB support -->
	<dependency>
		<groupId>jakarta.xml.bind</groupId>
		<artifactId>jakarta.xml.bind-api</artifactId>
	</dependency>
	
	<dependency>
		<groupId>org.glassfish.jaxb</groupId>
		<artifactId>jaxb-runtime</artifactId>
	</dependency>
	// Binding class
	@Data
	@XmlRootElement
	public class Book {
		private Integer id;
		private String name;
		private Double price;
	}
	// BookRestController
	@RestController
	public class BookRestController {
		@PostMapping(
				value = "/book",
				consumes = { "application/json", "application/xml" }
		)
		public ResponseEntity<String> addBook(@RequestBody Book book) {
			String resp = "Book Added Successfully !!";
			System.out.println(book);
			return new ResponseEntity<>(resp, HttpStatus.CREATED);
		}
	}
	{
		"id": 101,
		"name": "Spring Boot",
		"price": 450.0
	}
	<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
	 <book>
	     <id>101</id>
	     <name>Spring Boot</name>
	     <price>450</price>
	 </book>

Produces vs Consumes

Note: We can use both produces & consumes attributes in a single Rest Controller method.

Accept vs Content-Type

Note: We can use both Accept & Content-Type header in a single client.

Requirement : Develop IRCTC REST API to book a train ticket
	// PassengerInfo class
	@Data
	public class PassengerInfo {
		private String name;
		private Long phno;
		private String date;
		private String from;
		private String to;
		private Integer trainNo;
	}
	// TicketInfo class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class TicketInfo {
		private Integer id;
		private String pnr;
		private String status;
	}
	// TicketRestController
	@RestController
	public class TicketRestController {
		@PostMapping(
				value = "/ticket",
				produces = "application/json",
				consumes = "application/json"
		)
		public ResponseEntity<TicketInfo> bookTicket(@RequestBody PassengerInfo passenger) {
			System.out.println(passenger);
			// logic to book ticket
			TicketInfo ticket = new TicketInfo(1234, "JLSD45231", "CONFIRMED");
			return new ResponseEntity<>(ticket, HttpStatus.CREATED);
		}
	}
	{
		"name": "Ashok",
		"phno": 9876543210,
		"date": "2025-08-15",
		"from": "Delhi",
		"to": "Pune",
		"trainNo": 90123
	}

HTTP PUT Request

	// REST API using PUT request
	@PutMapping("/ticket")
	public ResponseEntity<String> updateTicket(@RequestBody PassengerInfo passenger) {
		System.out.println(passenger);
		// logic to update ticket
		return new ResponseEntity<>("Ticket Updated !!", HttpStatus.OK);
	}

HTTP DELETE Request

	// REST API using DELETE request
	@DeleteMapping("/ticket/{ticketId}")
	public ResponseEntity<String> deleteTicket(@PathVariable("ticketId") Integer tid) {
		System.out.println("Ticket ID : " + tid);
		// logic to delete ticket
		return new ResponseEntity<>("Ticket Deleted !!", HttpStatus.OK);
	}

Q) Can we write the logic to update a record in POST request method ?
Ans: Yes, we can perform any CRUD operation(s) inside any HTTP protocol method but doing so is strictly not recommended. We should follow HTTP Protocol standards while developing REST API.

Note: More than one Rest Controller method can have the same URL pattern (REST endpoint) but the HTTP method must be different so that the server can identify the correct controller method and route the request properly.

CRUD Operations using REST API with MySQL DB

Layered Architecture Application

	// Binding class
	@Data
	@Entity
	@Table(name = "BOOK_TABLE")
	public class Book {
		@Id
		@Column(name = "BOOK_ID")
		@GeneratedValue(strategy = GenerationType.IDENTITY)
		private Integer bookId;
		
		@Column(name = "BOOK_NAME")
		private String bookName;
		
		@Column(name = "BOOK_PRICE")
		private Double bookPrice;
	}
	// BookRepository interface
	public interface BookRepository extends JpaRepository<Book, Serializable> {

	}
	// BookService interface
	public interface BookService {
		public String upsertBook(Book book);
		public List<Book> getAllBooks();
		public String deleteBook(Integer bookId);
	}
	// BookService class
	@Service
	public class BookServiceImpl implements BookService {
		private BookRepository repository;
		
		public BookServiceImpl(BookRepository repository) {
			this.repository = repository;
		}
		
		@Override
		public String upsertBook(Book book) {
			Integer bookId = book.getBookId();
			
			repository.save(book);
			
			if(bookId == null) {
				return "Book Inserted !!";
			} else {
				return "Book Updated !!";
			}
		}
		
		@Override
		public List<Book> getAllBooks() {
			List<Book> books = repository.findAll();
			return books;
		}
		
		@Override
		public String deleteBook(Integer bookId) {
			repository.deleteById(bookId);
			return "Book Deleted !!";
		}
	}
	// BookRestController class
	@RestController
	public class BookRestController {
		private BookService service;
		
		public BookRestController(BookService service) {
			this.service = service;
		}
		
		@PostMapping("/book")
		public ResponseEntity<String> addBook(@RequestBody Book book) {
			String resp = service.upsertBook(book);
			return new ResponseEntity<>(resp, HttpStatus.CREATED);
		}
		
		@GetMapping("/books")
		public ResponseEntity<List<Book>> getBooks() {
			List<Book> books = service.getAllBooks();
			return new ResponseEntity<>(books, HttpStatus.OK);
		}
		
		@PutMapping("/book")
		public ResponseEntity<String> updateBook(@RequestBody Book book) {
			String resp = service.upsertBook(book);
			return new ResponseEntity<>(resp, HttpStatus.OK);
		}
		
		@DeleteMapping("/book/{bookId}")
		public ResponseEntity<String> deleteBook(@PathVariable("bookId") Integer bid) {
			String resp = service.deleteBook(bid);
			return new ResponseEntity<>(resp, HttpStatus.OK);
		}
	}

Swagger

Swagger

Note: Swagger will generate documentation in JSON format.

	<!-- Swagger dependency -->
	<dependency>
		<groupId>org.springdoc</groupId>
		<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
		<version>2.5.0</version>
	</dependency>
	// SwaggerConfig class
	@Configuration
	public class SwaggerConfig {
		@Bean
		public GroupedOpenApi apiDoc() {
			return GroupedOpenApi.builder()
					.group("ashokit-api")
					.packagesToScan("in.ashokit.rest")
					.build();
		}
	}

Note: Configuration class is optional.

Embedded Database (H2)

Note: We can't store the data permanently in embedded database (when we stop the application then the data will be lost)

Note: Embedded databases are used for practice or testing purpose. We don't use embedded databases in realtime projects.

Note: Spring Boot provides H2 database as embedded database.

	<!-- h2 DB dependency -->
	<dependency>
		<groupId>com.h2database</groupId>
		<artifactId>h2</artifactId>
	</dependency>
	# DataSource properties
	spring.datasource.url=jdbc:h2:mem:testdb
	spring.datasource.username=sa
	spring.datasource.password=sa
	spring.datasource.driver-class-name=org.h2.Driver

REST API Cloud Deployment

REST API Deployment to Heroku Cloud

Note: We can deploy our app to Heroku cloud by using Heroku CLI or GitHub Repo. Here we are using Heroku CLI for deployment.

	$ heroku login
	$ cd <project-location>
	$ git init
	
	$ heroku git:remote -a <heroku-app-name>
	
	$ git add .
	
	$ git commit -am "make it better"
	
	$ git push heroku master

Note: Heroku cloud will provide Platform as a Service (PaaS)

REST API Deployment to AWS Cloud

Note: AWS cloud will provide Infrastructure as a Service (IaaS)

Note: In realtime we will deploy our apps to AWS cloud.

REST Client

REST Client and REST API Communication 1

Note: WebClient is introduced in Spring 5.x version.

REST Client and REST API Communication 2

Note: Rest API team will send swagger documentation to Rest Client side team. Using this swagger documentation client side team will understand the rest API and develop the Rest Client logic to access the Rest API.

Note: We will write Rest Client logic inside Service classes using @Service annotation.

	// Sending HTTP GET Request using RestTemplate
	@Service
	public class WelcomeRestClient {
		public void invokeGetWelcomeMsg() {
			String apiURL = "api-url";
			
			RestTemplate rt = new RestTemplate();
			ResponseEntity<String> responseEntity = rt.getForEntity(apiURL, String.class);
			String body = responseEntity.getBody();
			HttpStatusCode statusCode = responseEntity.getStatusCode();
			
			System.out.println("Body : "+body);
			System.out.println("Status Code : "+statusCode);
		}
	}

	// Binding class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Book {
		private Integer bookId;
		private String bookName;
		private Double bookPrice;
	}

Note: In our rest client binding class we can create only the fields we need and left the others.

	// Sending HTTP POST Request using RestTemplate
	@Service
	public class BookClient {
		public void invokeAddBook() {
			String apiURL = "api-url";
			
			Book book = new Book(101, "Spring Boot", 650.0);
			
			RestTemplate rt = new RestTemplate();
			ResponseEntity<String> responseEntity = rt.postForEntity(apiURL, book, String.class);
			String body = responseEntity.getBody();
			
			System.out.println(body);
		}
		
		public void invokeGetBooks() {
			String apiURL = "api-url";
			
			RestTemplate rt = new RestTemplate();
			ResponseEntity<String> responseEntity = rt.getForEntity(apiURL, String.class);
			String body = responseEntity.getBody();
			
			System.out.println(body);
		}
	}
	// Sending GET Request and binding Response JSON to Binding Obj
	@Service
	public class BookClient {
		public void invokeGetBooksJson() {
			String apiURL = "api-url";
			
			RestTemplate rt = new RestTemplate();
			ResponseEntity<Book[]> responseEntity = rt.getForEntity(apiURL, Book[].class);
			Book[] books = responseEntity.getBody();
			
			for(Book book : books) {
				System.out.println(book);
			}
		}
	}

Note: Binding the response data to binding class object is recommended approach.

What is Synchronous & Asynchronous ?

Sync vs Async

	// Binding class
	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class Book {
	
		private Integer bookId;
		private String bookName;
		private Double bookPrice;
	}
	// HTTP Post Request with WebClient
	@Service
	public class BookClient {
		public void invokeAddBook() {
			String apiURL = "api-url";
			
			Book book = new Book(101, "Spring Boot", 650.0);
			
			WebClient client = WebClient.create();
			String body = client.post() // post request
					.uri(apiURL) // endpoint url
					.bodyValue(book) // setting request body								.retrieve() // retrieving response data
					.bodyToMono(String.class) // binding response data
					.block(); // make it sync client
			
			System.out.println(body);
		}	
	}
	// HTTP GET Request using WebClient
	@Service
	public class BookClient {
		public void invokeGetBooks() {
			String apiURL = "api-url";
			
			WebClient client = WebClient.create();
			Book[] books = client.get() // get request
					.uri(apiURL) // endpoint url
					.retrieve() // retrieving response data
					.bodyToMono(Book[].class) // binding response data
					.block(); // make it sync client
			
			for(Book book : books) {
				System.out.println(book);
			}
		}
	}
	// Asynchronus call using Webclient
	@Service
	public class BookClient {
		public static void invokeGetBooksAsync() {
			String apiURL = "api-url";
			
			WebClient client = WebClient.create();
				client.get()
					.uri(apiURL)
					.retrieve()
					.bodyToMono(Book[].class)
					.subscribe(BookClient::respHandler); // make it async client
			
			System.out.println("Request Sent !!");
		}
		
		public static void respHandler(Book[] books) {
			for(Book book : books) {
				System.out.println(book);
			}
		}
	}

Note: For every async call we need to create one response handler.

REST API Communication

REST API Communication

Note: A REST API can act as both a provider and a consumer at the same time for different REST APIs.

application.properties file vs application.yml file

application.properties

Note: We need to create separate properties file for every profile.

	# application.properties file
	server.port = 9090
	
	spring.application.name = my-app
	
	spring.mvc.view.prefix = /views/
	spring.mvc.view.suffix = .jsp

application.yml

Note: We can configure all the profiles in single YML file.

Note: Indent spaces are crucial in YML files.

	# application.yml file
	server:
	  port: 9090
	
	spring:
	  application:
	    name: my-app
	
	  mvc:
	    view:
	      prefix: /views/
	      suffix: .jsp

Note: In realtime we will use application.yml file because it is more simple and readable.

Working with Dynamic Properties

Predefined Properties : Spring Boot will read these properties automatically to configure our app.

User-defined Properties : We will define these properties and use them in our application.

@Value : This approach is suitable when we have less no of properties to configure.

@ConfigurationProperties : This approach is used when we have a large no of properties to configure.

@Value Approach

	# application.properties file
	# predefined properties
	server.port = 9090
	
	# user-defined properties
	messages.welcome = Welcome to Ashok IT !!
	messages.greet = Good Evening !!
	# application.yml file
	# predefined properties
	server:
	  port: 9090
	
	# user-defined properties
	messages:
	  welcome: Welcome to Ashok IT !!
	  greet: Good Evening !!
	// MessageRestController class
	@RestController
	public class MessageRestController {
	
		@Value("${messages.welcome}")
		private String welcomeMsg;
		
		@Value("${messages.greet}")
		private String greetMsg;
		
		@GetMapping("/welcome")
		public String getWelcomeMsg() {
			return welcomeMsg;
		}
		
		@GetMapping("/greet")
		public String getGreetMsg() {
			return greetMsg;
		}
	}

Note: A project can have both application.properties file & application.yml file but it is not recommended because it can cause confusions.

@ConfigurationProperties Approach

	# application.properties file
	# predefined properties
	server.port = 9090
	
	# user-defined properties
	my-app.messages.welcomeMsg = Welcome to Ashok IT !!
	my-app.messages.greetMsg = Good Evening !!
	my-app.messages.wishMsg = All the Best !!
	# application.yml file
	# predefined properties
	server:
	  port: 9090
	
	# user-defined properties
	my-app:
	  messages:
	    welcomeMsg: Welcome to Ashok IT !!
	    greetMsg: Good Evening !!
	    wishMsg: All the Best !!
	// AppProperties class
	@Data
	@Configuration
	@EnableConfigurationProperties
	@ConfigurationProperties(prefix = "my-app")
	public class AppProperties {
		private Map<String, String> messages = new HashMap<>();
	}
	// MessageRestController class
	@RestController
	public class MessageRestController {
	
		private AppProperties props;
	
		public MessageRestController(AppProperties props) {
			this.props = props;
		}
	
		@GetMapping("/welcome")
		public String getWelcomeMsg() {
			Map<String, String> messages = props.getMessages();
			return messages.get("welcomeMsg");
		}
	
		@GetMapping("/greet")
		public String getGreetMsg() {
			Map<String, String> messages = props.getMessages();
			return messages.get("greetMsg");
		}
		
		@GetMapping("/wish")
		public String getWishMsg() {
			return props.getMessages().get("wishMsg");
		}
	}

Spring Boot Actuators

Actuator Endpoints

/health : To get application health status

/info : To get application information

/beans : To get which beans loaded by our application

/mappings : To get which URL patterns available in our application

/configProps : To get which configuration properties loaded by our application

/heapdump : To download heap data

/threaddump : To get threads information

/shutdown : To stop our application (it is bind to POST request)

Note: All the actuator endpoints are bind to GET request except /shutdown

	<!-- Spring Boot actuator dependency -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>

Note: /health is a default endpoint which we can access directly.

	# application.yml file
	management:
	  endpoints:
	    web:
	      exposure:
	        include:
	        - beans
	        - mappings

Note: If we expose actuator endpoints then /health endpoint will not be available by default. It also need to be exposed manually.

	# application.yml file
	management:
	  endpoints:
	    web:
	      exposure:
	        include: '*'
	# application.yml file
	management:
	  endpoints:
	    web:
	      exposure:
	        include: '*'
	        exclude:
	        - beans
	        - mappings

Working with shutdown

	# application.yml file
	management:
	  endpoints:
	    web:
	      exposure:
	        include: '*'
	  endpoint:
	    shutdown:
	      enabled: true

Microservices

Monolithic vs Microservices

Monolithic vs Microservices Architecture

Monolithic Architecture

Monolithic Architecture

Advantages
	1) Development is easy
	2) Deployment is easy
	3) Performance is good
	4) Easy Testing
	5) Easy Debugging
	6) KT is easy (Knowledge Transfer)

Challenges

	1) Single point of failure
	2) Whole application re-deployment
	3) Scalability (increasing & decreasing resources based on demand)
	4) Reliability (strong)
	5) Availability (zero downtime)

Note: It is not recommended to develop applications using monolithic architecture because it is difficult to maintain monolithic architecture based applications.

Microservices Architecture

Microservices Architecture

Advantages
	1) Loosely Coupled
	2) Fast development
	3) Quick Releases
	4) Flexibility
	5) Scalability
	6) No single point of failure
	7) Technology Independence
Challenges
	1) Bounded context (identifying no of services to develop)
	2) Lots of Configurations
	3) Visibility (you may not get the chance to work with all the services)
	4) Testing is difficult
	5) Debugging is difficult

Microservices Architecture

Microservices Architecture

Service Registry

Note: Eureka Server is provided by Spring Cloud Netflix Library.

Admin Server

Note: Admin Server and Admin Client provided by Code Centric company (we can integrate it with Spring Boot)

Zipkin Server

Note: Zipkin is third party open source server (it can be integrated with Spring Boot)

Services

FeignClient

Note: Based on requirement our backend APIs can communicate with 3rd party APIs also by using RestTemplate or WebClient.

API Gateway

Note: Earlier we use Zuul Proxy as API Gateway but it got removed from latest version of Spring Boot.

Microservices Mini Project

Step-1) Create Service Registry Application using Eureka Server
	# Configures Eureka Server port
	server:
	  port: 8761
	
	# To prevent Eureka Server self-registration
	eureka:
	  client:
	    register-with-eureka: false

Note: The default port for Eureka Server is 8761 but we can run it on any other port as well. If we use a custom port for Eureka Server then our services will not register with Eureka Server automatically. In this case we need to configure Eureka Server URL manually in each service.

Step-2) Create Spring Boot Application with Admin Server
Step-3) Download & Run Zipkin Server
	java  -jar  <zipkin-server-jar>

Note: The default port for Zipkin Server is 9411 but we can run it on any other port as well. If we use a custom port for Zipkin Server then our services will not register with Zipkin Server automatically to send traces. In this case we need to configure Zipkin Server URL manually in each service.

Step-4) Develop REST API (WELCOME-API)
	# Configures server port
	server:
	  port: 8081
	
	# Configures app name & Admin Server URL
	spring:
	  application:
	    name: WELCOME-API
	  boot:
	    admin:
	      client:
	        url: http://localhost:1111/
	
	# Configures Eureka Server URL (optional)
	eureka:
	  client:
	    service-url:
	      defaultZone: http://localhost:8761/eureka/
	
	# Configures actuator endpoints
	management:
	  endpoints:
	    web:
	      exposure:
	        include: '*'

Note: @EnableDiscoveryClient annotation is used to enable service registration and discovery in a Spring Boot application.

Note: If Eureka Server is running on the default port and at the default URL then we no need to configure Eureka Server URL in the application.yml file of client applications.

Step-5) Develop REST API (GREET-API)
	@FeignClient(name = "WELCOME-API")
	public interface WelcomeApiClient {
		@GetMapping("/welcome")
		public String invokeWelcomeApi();
	}
	# Configures server port
	server:
	  port: 9091
	
	# Configures app name & Admin Server URL
	spring:
	  application:
	    name: GREET-API
	  boot:
	    admin:
	      client:
	        url: http://localhost:1111/
	
	# Configures actuator endpoints
	management:
	  endpoints:
	    web:
	      exposure:
	        include: '*'

Note: @EnableFeignClients annotation is used enable FeignClient support which allows us to call other services using simple Java interfaces.

Note: We don't need to provide implementation for FeignClient interface Spring Cloud will provide implementation for FeignClient interface during runtime.

Note: For inter-service communication we only specify the service name in the @FeignClient annotation. FeignClient will automatically contact the service registry (Eureka Server) to get the actual service URL.

Note: FeignClient can also be used to call external services (not registered in the service registry). In this case we must provide the full URL of the external service in the @FeignClient annotation.

	@FeignClient(name = "api-name", url = "http://api.example.com")

Note: We can use above approach for inter-service communication also but it is not recommended because service URL can be change in future.

Step-6) Develop API-Gateway Application
	# Configures API Gateway port
	server:
	  port: 3333
	
	# Configures app name
	spring:
	  application:
	    name: API-GATEWAY
	
	# Configures API Routes
	  cloud:
	    gateway:
	      server:
	        webflux:
	          routes:
	          - id: api-1
	            uri: lb://WELCOME-API
	            predicates:
	            - Path= /welcome
	          - id: api-2
	            uri: lb://GREET-API
	            predicates:
	            - Path= /greet

Note: API Gateway will act as a client for Eureka Server to get the API service URLs from Eureka Server for routing requests.

Filters

	// Accessing request information using Filter
	@Component
	public class MyPreFilter implements GlobalFilter {
		@Override
		public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
			System.out.println("Filter executed..!!");
			
			// Accessing request information
			ServerHttpRequest request = exchange.getRequest();
			HttpHeaders headers = request.getHeaders();
			Set<String> keySet = headers.keySet();
			
			keySet.forEach(key -> {
				List<String> values = headers.get(key);
				System.out.println(key + " :: " + values);
			});
			
			return chain.filter(exchange);
		}
	}

Note: Since this filter implements the GlobalFilter interface hence it will be executed for every request that comes through the API Gateway.

Note: chain.filter() method passes the request to the next filter. If there is no next filter then the request goes to the routing.

Note: When we use Postman to send the request, it will add Postman-Token in request headers. We can use this token to identify whether the request came from Postman or from any other app.

	// Validating requests using Filter
	@Component
	public class MyPreFilter implements GlobalFilter {
		@Override
		public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
			System.out.println("Filter executed..!!");
	
			// Accessing request information
			ServerHttpRequest request = exchange.getRequest();
			HttpHeaders headers = request.getHeaders();
			List<String> values = headers.get("security_token");
			
			// Validating request
			if (values.contains("AshokIT@123")) {
				System.out.println("Token: " + values);
				return chain.filter(exchange);
			} else {
				System.out.println("Invalid Request..!!");
	
				// Set 401 Unauthorized response
				exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
				return exchange.getResponse().setComplete();
			}
		}
	}

Load Balancing

Load Balancing

Note: Spring Cloud will internally use Spring Cloud LoadBalancer to perform load balancing. Earlier Netflix Ribbon library is used for load balancing.

Note: If we use service URL instead of service name in API-Gateway or FeignClient then we can't achieve load balancing because in that case all the incoming requests goes to the same fixed instance.

Note: Spring Cloud LoadBalancer is used to perform client-side load balancing.

LBR implementation in Mini Project

	@RestController
	public class WelcomeRestController {
		@Autowired
		private Environment env;
		
		@GetMapping("/welcome")
		public String getWelcomeMsg() {
			String port = env.getProperty("server.port");
			String msg = "Welcome to Ashok IT..!! (Port :: " + port + ")";
			return msg;
		}
	}

Note: VM Arguments will take priority over application.yml configuration.

Scaling

Vertical Scaling: Increasing the system resources of a single server (Ex: RAM, CPU etc...)

Horizontal Scaling: Increasing the no of servers to run our application

Note: Vertical Scaling can be done upto a certain level hence we use Horizontal Scaling in realtime.

Note: To implement Auto Scaling we have to use cloud platforms like AWS, Azure and GCP.

Other Topics

Circuit Breaker

What is Circuit Breaker ?

Usecase

Note: If m1() method fails to fetch the data from Redis server then we have to execute m2() method to fetch the data from DB server. This is called as fallback logic execution.

Can we implement fault tolerance using try-catch ?

	@RestController
	public class MyRestController {
		@GetMapping("/data")
		public String m1() {
			System.out.println("********** m1() executed **********");
			String msg = "Data accessed from Redis !!";
	
			try {
				int i = 10 / 0;
			} catch (Exception e) {
				msg = m2();
			}
	
			return msg;
		}
	
		public String m2() {
			System.out.println("********** m2() executed **********");
			String msg = "Data accessed from DB !!";
			return msg;
		}
	}

Circuit Breaker States

CLOSED: System working as expected & failure rate is being monitored.

OPEN: When the failure rate exceeds the threshold then Circuit Breaker goes to the OPEN state and directly executes the fallback logic for a specific amount of time.

Note: After this specific amount of time Circuit Breaker will goes to the HALF_OPEN state.

HALF_OPEN: Circuit Breaker allows a few requests to check if the system has recovered. If these requests succeed, it moves back to the CLOSED state and if they fail again, it returns to the OPEN state.

Steps to develop Circuit Breaker App

	<!-- aop dependency -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>

Note: We need the AOP dependency because the circuit breaker works by intercepting method calls. AOP lets Spring create a proxy around our methods, so it can decide whether to run the method normally or call the fallback logic. Without AOP this interception and circuit breaker logic will not work.

	@RestController
	public class MyRestController {
		@CircuitBreaker(name = "myCircuitBreaker", fallbackMethod = "getDataFromDB")
		@GetMapping("/data")
		public String getDataFromRedis() {
			System.out.println("*********** getDataFromRedis() executed ***********");
			String msg = "Data accessed from Redis !!";
			
			if(new Random().nextInt(10) <= 10) {
				throw new RuntimeException("Redis Server Down");
			}
			
			return msg;
		}
		
		public String getDataFromDB(Throwable t) {
			System.out.println("*********** getDataFromDB() executed ***********");
			String msg = "Data accessed from DB !!";
			return msg;
		}
	}
	# actuators config
	management:
	  endpoints.web.exposure.include: '*'
	  endpoint.health.show-details: always
	  health.circuitbreakers.enabled: true
	
	# circuit breaker config
	resilience4j:
	  circuitbreaker:
	    configs:
	      default:
	        registerHealthIndicator: true
	        slidingWindowSize: 10
	        minimumNumberOfCalls: 5
	        permittedNumberOfCallsInHalfOpenState: 3
	        automaticTransitionFromOpenToHalfOpenEnabled: true
	        waitDurationInOpenState: 30s
	        failureRateThreshold: 50
	        eventConsumerBufferSize: 10

Redis Cache

What is Cache ?

Redis Cache

Note: To reduce the no of round trips between application and database we will use Cache.

Note: It is not recommended to read static data from DB table everytime. We will use Cache to store the static data.

Redis

RDBMS vs Redis

Spring Boot App with Redis DB Integration

	<!-- jedis dependency -->
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
	</dependency>
	# Redis Config
	spring.data.redis.url=redis-11429.crce179.ap-south-1-1.ec2.redns.redis-cloud.com
	spring.data.redis.port=11429
	spring.data.redis.username=default
	spring.data.redis.password=AshokIT@123
	// RedisConfig class
	@Configuration
	public class RedisConfig {
		@Value("${spring.data.redis.url}")
		private String url;
	
		@Value("${spring.data.redis.port}")
		private Integer port;
	
		@Value("${spring.data.redis.username}")
		private String username;
	
		@Value("${spring.data.redis.password}")
		private String password;
	
		@Bean
		public JedisConnectionFactory getJedisConnectionFactory() {
			RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(url, port);
			serverConfig.setUsername(username);
			serverConfig.setPassword(password);
	
			JedisClientConfiguration clientConfig = JedisClientConfiguration.builder().build();
			return new JedisConnectionFactory(serverConfig, clientConfig);
		}
	}
	// Entity class
	@Data
	@RedisHash("USER_CACHE")
	public class User {
		@Id
		private Integer userId;
		private String userName;
		private Integer userAge;
	}

Spring Boot App with Redis Cache Integration

	<!-- jedis dependency -->
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
	</dependency>
	# Redis Config
	spring.data.redis.url=redis-11429.crce179.ap-south-1-1.ec2.redns.redis-cloud.com
	spring.data.redis.port=11429
	spring.data.redis.username=default
	spring.data.redis.password=AshokIT@123
	
	# MySQL properties
	spring.datasource.url=jdbc:mysql://localhost:3306/sbms
	spring.datasource.username=ashokit
	spring.datasource.password=AshokIT@123
	spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
	
	# Hibernate properties
	spring.jpa.hibernate.ddl-auto=update
	spring.jpa.show-sql=true
	spring.jpa.properties.hibernate.format_sql=true
	@EnableCaching
	@SpringBootApplication
	public class Application {
		public static void main(String[] args) {
			SpringApplication.run(Application.class, args);
		}
	}
	// RedisConfig class
	@Configuration
	public class RedisConfig {
		@Value("${spring.data.redis.url}")
		private String url;
		
		@Value("${spring.data.redis.port}")
		private Integer port;
		
		@Value("${spring.data.redis.username}")
		private String username;
		
		@Value("${spring.data.redis.password}")
		private String password;
		
		@Bean
		public JedisConnectionFactory getJedisConnectionFactory() {
			RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(url, port);
			serverConfig.setUsername(username);
			serverConfig.setPassword(password);
			
			JedisClientConfiguration clientConfig = JedisClientConfiguration.builder().build();
			return new JedisConnectionFactory(serverConfig, clientConfig);
		}
		
		@Bean
		public RedisCacheConfiguration getRedisCacheConfiguration() {
			return RedisCacheConfiguration.defaultCacheConfig()
					.disableCachingNullValues()
					.entryTtl(Duration.ofMinutes(4))
					.serializeValuesWith(
							SerializationPair
							.fromSerializer(new Jackson2JsonRedisSerializer<>(User.class))
							);
		}
	}
	// User Service class
	@Service
	public class UserServiceImpl implements UserService {
		private UserRepository repo;
		
		public UserServiceImpl(UserRepository repo) {
			this.repo = repo;
		}
	
		@Override
		@CachePut(value = "USER_CACHE", key = "#user.userId")
		public User upsertUserData(User user) {
			repo.save(user);
			return user;
		}
	
		@Override
		@Cacheable(value = "USER_CACHE", key = "#userId")
		public User getUserData(Integer userId) {
			return repo.findById(userId).get();
		}
	
		@Override
		@CacheEvict(value = "USER_CACHE", key = "#userId")
		public void deleteUserData(Integer userId) {
			repo.deleteById(userId);
		}
	}

Note: We use Spring Expression Language (SpEL) to define the cache key.

Apache Kafka

Need of Messaging Systems

Note: API calls are used for accessing another application logic. For sharing large amounts of data to multiple systems we use Message Queues.

How Messaging Systems works ?

Apache Kafka

Note: Kafka will act as a mediator / broker between Publisher and Subscriber.

Note: Using Apache Kafka we can develop Event Driven Microservices.

Kafka Usecases
	1) Zomato orders processing
	2) Swiggy orders processing
	3) Ola rides booking process
	4) Rapido rides booking process

Kafka Architecture

	1) Zookeeper (Provides runtime environment to run kafka server)
	2) Kafka Server (Message Broker)
	3) Kafka Topic (A place to store msgs)
	4) Publisher App (The application which sends msg to topic)
	5) Subscriber App (The application which reads msg from topic)

Kafka

Message Queue

Apache Kafka Setup in Windows

Note: If kafka server is getting stopped, delete kafka logs from temp folder.

Note: When a publisher sends a message to a topic, the topic will be created automatically if it does not already exist.

Kafka Producer App

	server.port=7070
	public class KafkaConstants {
		public static final String TOPIC = "user";
		public static final String HOST = "localhost:9092";
	}
	@Data
	public class User {
		private Integer userId;
		private String userName;
		private Integer userAge;
	}
	@Configuration
	public class KafkaProducerConfig {
		@Bean
		public ProducerFactory<String, User> getProducerFactory() {
			Map<String, Object> configProps = new HashMap<>();
			configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KafkaConstants.HOST);
			configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
			configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
			return new DefaultKafkaProducerFactory<>(configProps);
		}
	
		@Bean
		public KafkaTemplate<String, User> getKafkaTemplate() {
			return new KafkaTemplate<>(getProducerFactory());
		}
	}
	@Service
	public class UserServiceImpl implements UserService {
		private KafkaTemplate<String, User> kafkaTemplate;
	
		public UserServiceImpl(KafkaTemplate<String, User> kafkaTemplate) {
			this.kafkaTemplate = kafkaTemplate;
		}
	
		@Override
		public String publishUserMsg(User user) {
			kafkaTemplate.send(KafkaConstants.TOPIC, user);
			System.out.println("****** Message Published to Kafka Topic ******");
			return "User Record Added to Kafka Queue Successfully !!";
		}
	
	}
	@RestController
	public class UserRestController {
		private UserService service;
	
		public UserRestController(UserService service) {
			this.service = service;
		}
	
		@PostMapping("/user")
		public String saveUser(@RequestBody User user) {
			return service.publishUserMsg(user);
		}
	}

Kafka Subscriber App

	server.port=9090
	public class KafkaConstants {
		public static final String TOPIC = "user";
		public static final String GROUP_ID = "group_user";
		public static final String HOST = "localhost:9092";
	}
	@Data
	public class User {
		private Integer userId;
		private String userName;
		private Integer userAge;
	}
	@Configuration
	public class KafkaConsumerConfig {
		@Bean
		public ConsumerFactory<String, User> getConsumerFactory() {
			Map<String, Object> configProps = new HashMap<>();
			configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KafkaConstants.HOST);
			configProps.put(ConsumerConfig.GROUP_ID_CONFIG, KafkaConstants.GROUP_ID);
			configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
			configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class);
			return new DefaultKafkaConsumerFactory<>(configProps, new StringDeserializer(),
					new JsonDeserializer<>(User.class));
		}
	
		@Bean
		public ConcurrentKafkaListenerContainerFactory<String, User> userContainerFactory() {
			ConcurrentKafkaListenerContainerFactory<String, User> factory = new ConcurrentKafkaListenerContainerFactory<>();
			factory.setConsumerFactory(getConsumerFactory());
			return factory;
		}
	}
	@Service
	public class UserServiceImpl implements UserService {
		@Override
		@KafkaListener(
				topics = KafkaConstants.TOPIC,
				groupId = KafkaConstants.GROUP_ID,
				containerFactory = "userContainerFactory"
		)
		public void subscribeUserMsg(User user) {
			System.out.println("Message Received from Kafka Topic : "+user);
		}
	}

Note: An application can act as Publisher for one topic and at the same time it can act as Subscriber for another topic. Below app has two API's and each API is doing both it publishes messages to one topic and subscribes messages from another topic.

	GitHub URL: https://github.com/arpitaggarwal135/kafka-mini-project

Config Server

Need of Config Server

Config Server

Config Server

Note: Configuration properties file name and application-name (spring.application.name) must be same so that the Config Server can identify the application and provide correct configuration.

	app-name : welcome    --->    config-file : welcome.yml
	app-name : greet    --->    config-file : greet.yml
	app-name : admin    --->    config-file : admin.yml
	app-name : cart    --->    config-file : cart.yml

Note: Config Server also supports profiles. If our application uses a profiles, it can detect the profile-specific configuration and provide it to that application.

	welcome.yml
	welcome-dev.yml
	welcome-prod.yml

	greet.yml
	greet-dev.yml
	greet-prod.yml

Note: Our microservices will act as client for the config-server.

Config Server App

	@EnableConfigServer
	@SpringBootApplication
	public class Application {
		public static void main(String[] args) {
			SpringApplication.run(Application.class, args);
		}
	}
	# Config Server properties
	spring:
	  cloud:
	    config:
	      server:
	        git:
	          uri: https://github.com/arpitaggarwal135/config_props
	          clone-on-start: true

Note: clone-on-start property forces the config server to clone the Git repo immediately at startup, instead of lazily when the first request comes.

Config Client App

	# Set server port
	server:
	  port: 9090
	
	# Set application name
	spring:
	  application:
	    name: welcome
	
	# Set config server url
	  config:
	    import: optional:configserver:http://localhost:8080
	
	# Enables refresh actuator endpoint
	management:
	  endpoints:
	    web:
	      exposure:
	        include: refresh 
	@RefreshScope
	@RestController
	public class WelcomeRestController {
		@Value("${msg:Config Server Not Working !!}")
		private String msg;
	
		@GetMapping("/")
		public String getWelcomeMsg() {
			return msg;
		}
	}

Note: If we make any changes in the configuration properties then we need to restart our application to pick-up the latest configuration.

How to reload config properties dynamically ?

Spring Boot Property Loading Order

Note: If a property is defined in more than one place then the one with higher priority overrides the others.

Mono & Flux Objects

What is Mono & Flux Objects ?

Spring Boot App using Mono & Flux Objects

	@Data
	@AllArgsConstructor
	@NoArgsConstructor
	public class User {
		private Integer userId;
		private String userName;
		private Integer userAge;
	}

	@RestController
	public class UserRestController {
		@GetMapping(value = "/user", produces = "application/json")
		public ResponseEntity<Mono<User>> getUser() {
			User user = new User(101, "Ashok", 38);
			Mono<User> userMono = Mono.just(user);
			return new ResponseEntity<>(userMono, HttpStatus.OK);
		}
		
		@GetMapping(value = "/users", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
		public ResponseEntity<Flux<User>> getUsers() {
			// creating binding object with data
			User user = new User(101, "Ashok", 38);
			
			// creating stream for binding object
			Stream<User> stream = Stream.generate(() -> user);
			
			// creating flux object using stream
			Flux<User> userFlux = Flux.fromStream(stream);
			
			// setting response interval
			Flux<Long> interval = Flux.interval(Duration.ofSeconds(3));
			
			// combining user flux and interval flux
			Flux<Tuple2<Long,User>> zip = Flux.zip(interval, userFlux);
			
			// creating map for response
			Flux<User> map = zip.map(Tuple2::getT2);
			
			return new ResponseEntity<>(map, HttpStatus.OK);
		}
	}

Exception Handling in REST API's

What is Exception Handling ?

Note: In REST APIs, it is not recommended to handle exceptions directly using above keywords.

Exception Handling in REST API's

	{
		code : "SBI0004",
		msg : "Exception Reason" 
	}

Note: We will assign a unique exception code and a message that describes the reason to each Exception in the application.

Local Exception Handling

	// ExceptionInfo class
	@Data
	public class ExceptionInfo {	
		private String code;
		private String msg;
	}
	// Controller specific exception handling
	@RestController
	public class WelcomeRestController {
		private Logger logger = LoggerFactory.getLogger(WelcomeRestController.class);
	
		@GetMapping("/welcome")
		public String getWelcomeMsg() {
			String msg = "Welcome to Ashok IT !!";
			
			try {
				int i = 10 / 0;
			} catch (Exception e) {
				logger.error("Exception Occurred : " + e.getMessage());
				throw new ArithmeticException(e.getMessage());
			}
	
			return msg;
		}
	
		@ExceptionHandler(value = ArithmeticException.class)
		public ResponseEntity<ExceptionInfo> handleArithmeticException(ArithmeticException ae) {
			ExceptionInfo exception = new ExceptionInfo();
			exception.setCode("AE0001024");
			exception.setMsg(ae.getMessage());
			return new ResponseEntity<>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
		}
	}

Note: We use loggers to save exception details in a log file so that programmers can review them later.

Global Exception Handling

	// ExceptionInfo class
	@Data
	public class ExceptionInfo {
		private String code;
		private String msg;
	}
	// WelcomeRestController class
	@RestController
	public class WelcomeRestController {
		@GetMapping("/welcome")
		public String getWelcomeMsg() {
			String msg = "Welcome to Ashok IT !!";
			int i = 10 / 0;
			return msg;
		}
	}
	// GreetRestController class
	@RestController
	public class GreetRestController {
		@GetMapping("/greet")
		public String getGreetMsg() {
			String msg = "Good Morning !!";
			String str = null;
			str.length();
			return msg;
		}
	}
	// Global Exception Handler class
	@RestControllerAdvice
	public class AppExceptionHandler {
		private Logger logger = LoggerFactory.getLogger(AppExceptionHandler.class);
		
		@ExceptionHandler(value = ArithmeticException.class)
		public ResponseEntity<ExceptionInfo> handleArithmeticException(ArithmeticException ae) {
			ExceptionInfo exception = new ExceptionInfo();
			exception.setCode("AE0001024");
			exception.setMsg(ae.getMessage());
			logger.error("Exception Occurred : " + ae.getMessage());
			return new ResponseEntity<>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		
		@ExceptionHandler(value = NullPointerException.class)
		public ResponseEntity<ExceptionInfo> handleNullPointerException(NullPointerException npe) {
			ExceptionInfo exception = new ExceptionInfo();
			exception.setCode("NPE0001028");
			exception.setMsg(npe.getMessage());
			logger.error("Exception Occurred : " + npe, npe);
			return new ResponseEntity<>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
		}
	}

Note: If Local Exception Handler and Global Exception Handler both exist for the same exception then Local Handler will take priority and execute.

User Defined Exceptions

	public class MyException extends RuntimeException {
		public MyException(String msg) {
			super(msg);
		}
	}

How to handle User Defined Exceptions ?

	@Data
	public class ExceptionInfo {
		private String code;
		private String msg;
	}
	public class BookNotFoundException extends RuntimeException {
		public BookNotFoundException(String msg) {
			super(msg);
		}
	}
	@RestController
	public class BookRestController {
		@GetMapping("/book/{isbn}")
		public String getBookName(@PathVariable Integer isbn) {
			if(isbn == 1001) {
				return "Spring Boot";
			} else if(isbn == 1002) {
				return "AWS";
			} else {
				throw new BookNotFoundException("Invalid ISBN");
			}
		}
	}
	@RestControllerAdvice
	public class AppExceptionHandler {
		private Logger logger = LoggerFactory.getLogger(AppExceptionHandler.class);
		
		@ExceptionHandler(value = BookNotFoundException.class)
		public ResponseEntity<ExceptionInfo> handleBookNotFoundException(BookNotFoundException be) {
			ExceptionInfo exception = new ExceptionInfo();
			exception.setCode("AIT0005062");
			exception.setMsg(be.getMessage());
			logger.error("Exception Occurred : " + be);
			return new ResponseEntity<>(exception, HttpStatus.BAD_REQUEST);
		}
	}

REST API Unit Testing

What is Unit Testing ?

Mocking

	// Welcome Service class
	@Service
	public class WelcomeService {
		public String getMsg() {
			String msg = "Good Evening !!";
			return msg;
		}
	}
	// Welcome Rest Controller class
	@RestController
	public class WelcomeRestController {
		@Autowired
		private WelcomeService service;
		
		@GetMapping("/welcome")
		public String getWelcomeMsg() {
			String msg = service.getMsg();
			return msg;
		}
	}
	// Welcome Rest Controller Test class
	@WebMvcTest(value = WelcomeRestController.class)
	public class WelcomeRestControllerTest {
		@MockitoBean
		private WelcomeService service;
	
		@Autowired
		private MockMvc mockMvc;
	
		@Test
		public void getWelcomeMsgTest() throws Exception {
			// defining mock obj behaviour
			when(service.getMsg()).thenReturn("Welcome to Ashok IT !!");
	
			// preparing request
			MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/welcome");
	
			// sending request
			MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn();
	
			// getting response
			MockHttpServletResponse response = mvcResult.getResponse();
	
			// validating response status code
			int actuaStatus = response.getStatus();
			assertEquals(200, actuaStatus);
		}
	}

Spring Security

What is Spring Security ?

Working with Spring Security

Basic Auth

Note: By adding security-starter our application will become secured with HTTP Basic Auth and when we run our application everytime it will generate one random password to access our application. This generated random password will be printed on console.

Note: When we send credentials from Postman (or any client app) using Basic Auth, it automatically generates an HTTP header in below format

	Key    --->    Authorization
	Value    --->    Basic Base64.encode(username:password)

How to override Spring Security Default Credentials

	# Security Credentials
	spring.security.user.name=ashokit
	spring.security.user.password=ashokit@123

Note: We can set a single username and password in application.properties or application.yml file hence same credentials will be used by all users which is not recommended.

How to secure specific URL Patterns

	// Securing specific url patterns
	@Configuration
	public class AppSecurityConfig {
		@Bean
		public SecurityFilterChain securityConfig(HttpSecurity httpSecurity) throws Exception {
			httpSecurity.authorizeHttpRequests(req -> req
							.requestMatchers("/welcome").permitAll()
							.anyRequest().authenticated()
					).httpBasic(Customizer.withDefaults())
					 .formLogin(Customizer.withDefaults());
			
			return httpSecurity.build();
		}
	}

Note: formLogin() is optional it is used to display login form in the browser.

Rest Template with Basic Authentication Header

	// Rest Template with Basic Authentication Header
	HttpHeaders headers = new HttpHeaders();
	headers.setBasicAuth("ashokit", "ashokit@123");
		
	HttpEntity<String> entity = new HttpEntity<>(headers);
		
	RestTemplate rt = new RestTemplate();
	ResponseEntity<String> responseEntity = rt.exchange(apiUrl, HttpMethod.GET, entity, String.class);
		
	String body = responseEntity.getBody();	
	System.out.println("Rest Template : "+body);

WebClient with Basic Authentication Header

	// WebClient with Basic Authentication Header
	WebClient webClient = WebClient.create();
		
	String body = webClient.get()
			               .uri(apiUrl)
			               .headers(header -> header.setBasicAuth("ashokit", "ashokit@123"))
			               .retrieve()
			               .bodyToMono(String.class)
			               .block();
		
	System.out.println("Web Client : "+body);

In-Memory Authentication

Note: It is not recommended in realtime because it is insecure and not scalable.

	// In-Memory Authentication
	@Configuration
	public class AppSecurityConfig {
		@Bean
		public InMemoryUserDetailsManager inMemoryUsers() {
		
			UserDetails u1 = User.withDefaultPasswordEncoder()
					             .username("ashokit")
					             .password("ashokit@123")
					             .build();
			
			UserDetails u2 = User.withDefaultPasswordEncoder()
					             .username("raju")
		                         .password("raju@123")
		            		     .build();
			
			UserDetails u3 = User.withDefaultPasswordEncoder()
								 .username("rani")
		            			 .password("rani@123")
		            		     .build();
			
			return new InMemoryUserDetailsManager(u1, u2, u3);
		}
	}

Login and Registration using Spring Security

Note-1: When customer register, we need to store customer data in database table by encrypting password.

Note-2: When customer try to login, if credentials are valid send welcome msg as response. If credentials are invalid then send "Invalid Credential" msg as response.

Spring Security Internals

	# DataSource properties
	spring.datasource.url=jdbc:mysql://localhost:3306/sbms
	spring.datasource.username=ashokit
	spring.datasource.password=AshokIT@123
	spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
	
	# ORM properties
	spring.jpa.hibernate.ddl-auto=update
	spring.jpa.show-sql=true
	spring.jpa.properties.hibernate.format_sql=true
	// Customer entity class
	@Data
	@Entity
	@Table(
			name = "CUST_TBL",
			uniqueConstraints = @UniqueConstraint(columnNames = "CUST_EMAIL")
	)
	public class Customer {
		@Id
		@Column(name = "CUST_ID")
		@GeneratedValue(strategy = GenerationType.IDENTITY)
		private Integer id;
		
		@Column(name = "CUST_NAME")
		private String name;
		
		@Column(name = "CUST_EMAIL")
		private String email;
		
		@Column(name = "CUST_PWD")
		private String pwd;
		
		@Column(name = "CUST_PHN")
		private Long phn;
	}
	// Customer Repository interface
	public interface CustomerRepository extends JpaRepository<Customer, Integer> {
		public Customer findByEmail(String email);
	}
	// Customer service class
	@Service
	public class CustomerService implements UserDetailsService {
		@Autowired
		private CustomerRepository custRepo;
		
		@Override
		public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
			Customer customer = custRepo.findByEmail(email);
			return new User(customer.getEmail(), customer.getPwd(), Collections.emptyList());
		}
	}
	// SecurityConfig class
	@Configuration
	public class SecurityConfig {
		@Autowired
		private CustomerService custService;
		
		@Bean
		public PasswordEncoder getPasswordEncoder() {
			return new BCryptPasswordEncoder();
		}
		
		@Bean
		public AuthenticationProvider getAuthProvider() {
			DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(custService);
			authProvider.setPasswordEncoder(getPasswordEncoder());
			return authProvider;
		}
		
		@Bean
		@SneakyThrows
		public AuthenticationManager getAuthManager(AuthenticationConfiguration config) {
			return config.getAuthenticationManager();
		}
		
		@Bean
		@SneakyThrows
		public SecurityFilterChain getSecurityFilterChain(HttpSecurity httpSecurity) {
	        httpSecurity.csrf(csrf -> csrf.disable())
	                    .authorizeHttpRequests(req -> req
	                                .requestMatchers("/register", "/login")
	                                .permitAll()
	                                .anyRequest()
	                                .authenticated()
	                    );
			
			return httpSecurity.build();
		}
	}
	// Customer Rest Controller
	@RestController
	public class CustomerRestController {
		@Autowired
		private PasswordEncoder pwdEncoder;
		
		@Autowired
		private CustomerRepository custRepo;
		
		@Autowired
		private AuthenticationManager authManager;
		
		@PostMapping("/register")
		public ResponseEntity<String> registerCustomer(@RequestBody Customer customer) {
			customer.setPwd(pwdEncoder.encode(customer.getPwd()));
			custRepo.save(customer);
			return new ResponseEntity<>("Registration Successful !!", HttpStatus.CREATED);
		}
		
		@PostMapping("/login")
		public ResponseEntity<String> customerLogin(@RequestBody Customer cust) {
			ResponseEntity<String> respEntity = null;
			UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(cust.getEmail(), cust.getPwd());
			
			try {
				Authentication authenticate = authManager.authenticate(token);
				
				if(authenticate.isAuthenticated()) {
					respEntity = new ResponseEntity<>("Welcome to Ashok IT !!", HttpStatus.OK);
				}
			} catch (Exception e) {
				e.printStackTrace();
				respEntity = new ResponseEntity<>("Invalid Credentials !!", HttpStatus.BAD_REQUEST);
			}
			
			return respEntity;
		}
	}
	{
	    "name": "Ashok",
	    "email": "ashok@gmail.com",
	    "pwd": "ashokit@123",
	    "phn": 122383872
	}

OAuth 2.0 Authentication

OAuth 2.0

	@RestController
	public class WelcomeRestController {
		@GetMapping("/")
		public String getWelcomeMsg() {
			return "Welcome to Ashok IT !!";
		}
	}
	# OAuth client config
	spring:
	  security:
	    oauth2:
	      client:
	        registration:
	          github:
	            client-id: Ov23lizkyVTGdFnoVnfB
	            client-secret: 0f2d63799e4a925b14d244f57264b21412115697

Note: OAuth 2.0 is an authorization protocol not an authentication protocol.

Requirement: Develop a Spring Boot app with OAuth using Google. Get profile info also from Google and display that in response.

	@RestController
	public class MyRestController {
		@GetMapping("/")
		public Map<String, Object> getProfileDetails(@AuthenticationPrincipal OAuth2User principal) {
			return principal.getAttributes();
		}
	}
	# OAuth client config
	spring:
	  security:
	    oauth2:
	      client:
	        registration:
	          google:
	            client-id: 703761262972-qks94ugn2n4j5e1ou24tspuue3q66ehj.apps.googleusercontent.com
	            client-secret: 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

JWT Authentication

JWT Token Structure

Note: Every JWT has a configurable expiry time after which it becomes invalid.

JWT Token Generation

JWT Token Generation

JWT Token Validation

JWT Token Validation

Monolithic App with JWT Security

Monolithic App with JWT Security

Microservices with JWT Security

Microservices with JWT Security

Note: Microservices are stateless hence we maintain session info at frontend by storing the JWT token into session storage (or local storage) and in every subsequent request we will send this JWT token in request header to prove authentication.

Q) What happen if we send a request directly to a microservice URL instead of the API Gateway ?

Spring Batch

What is Spring Batch ?

Spring Batch

ItemReader : It is used to read the data from the source

ItemProcessor : It is used to process the data

ItemWriter : It is used to write the data to the destination

Step : It represents sequence of our batch execution (reader + processor + writer)

Job : It is used to represent set of steps for batch processing

JobRepository : It maintains job execution history and steps info

JobLauncher : It is used to start the job execution

Batch Processing Usecases

	- bulk msg
	- bulk email
	- sending emps salaries
	- sending bank stmt to acc holders
	- sending post paid bill reports
	- sending payment reminder notifications

Spring Batch App using Spring Boot

Requirement : Read the data from csv file and write into database table using spring batch framework.

	# Datasource properties
	spring.datasource.url=jdbc:mysql://localhost:3306/sbms
	spring.datasource.username=ashokit
	spring.datasource.password=AshokIT@123
	spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
	
	# ORM properties
	spring.jpa.hibernate.ddl-auto=update
	spring.jpa.show-sql=true
	// Customer entity class
	@Data
	@Entity
	@Table(name = "CUST_TBL")
	public class Customer {
		@Id
		@Column(name = "CUST_ID")
		private Integer id;
		
		@Column(name = "FIRST_NAME")
		private String firstName;
		
		@Column(name = "LAST_NAME")
		private String lastName;
		
		@Column(name = "EMAIL")
		private String email;
		
		@Column(name = "GENDER")
		private String gender;
		
		@Column(name = "CONTACT_NO")
		private String contactNo;
		
		@Column(name = "COUNTRY")
		private String country;
		
		@Column(name = "DOB")
		private String dob;
	}
	// Customer Repositoty interface
	public interface CustomerRepository extends JpaRepository<Customer, Serializable> {
	
	}
	// Customer Item Processor
	public class CustomerItemProcessor implements ItemProcessor<Customer, Customer>{
		@Override
		public Customer process(Customer item) throws Exception {
			// logic to process data
			return item;
		}
	}
	// Spring Batch Config class
	@Configuration
	public class SpringBatchConfig {
		@Autowired
		private CustomerRepository customerRepo;
		
		@Bean
		public FlatFileItemReader<Customer> itemReader() {
		  return new FlatFileItemReaderBuilder<Customer>()
		    .name("csv-reader")
		    .linesToSkip(1)
		    .resource(new ClassPathResource("customers.csv"))
		    .delimited()
		    .names("id", "firstName", "lastName", "email", "gender", "contactNo", "country", "dob")
		    .targetType(Customer.class)
		    .build();
		}
	
		@Bean
		public CustomerItemProcessor itemProcessor() {
		  return new CustomerItemProcessor();
		}
	
		@Bean
		public RepositoryItemWriter<Customer> customerWriter() {
			RepositoryItemWriter<Customer> writer = new RepositoryItemWriter<>();
			writer.setRepository(customerRepo);
			writer.setMethodName("save");
	
			return writer;
		}
		
		@Bean
		public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager,
		          FlatFileItemReader<Customer> reader, CustomerItemProcessor processor, RepositoryItemWriter<Customer> writer) {
		  return new StepBuilder("step1", jobRepository)
		    .<Customer, Customer>chunk(25, transactionManager)
		    .reader(reader)
		    .processor(processor)
		    .writer(writer)
		    .build();
		}
		
		@Bean
		public Job importUserJob(JobRepository jobRepository, Step step1) {
		  return new JobBuilder("importUserJob", jobRepository)
		    .start(step1)
		    .build();
		}
	}
	// Customer Rest Controller
	@RestController
	public class CustomerRestController {
		@Autowired
		private JobLauncher jobLauncher;
		
		@Autowired
		private Job job;
		
		@GetMapping("/import")
		public String loadDataToDB() throws Exception{
			JobParameters jobParams = new JobParametersBuilder()
											.addLong("startAt", System.currentTimeMillis()).toJobParameters();
			jobLauncher.run(job, jobParams);
			return "Data Imported !!";
		}
	}

Spring Core

Dependency Injection

Autowiring

Spring Boot

@SpringBootApplication Annotation

Autowiring

Ambiguity Problem in Autowiring

Lombok

Spring Data JPA

Custom Queries

JpaRepository

Primary Key Generation

Spring Web MVC

Spring Web MVC Architecture

What is Context-Path ?

Sending Data from Controller to UI

RESTFul Services

What is HTTP ?

XML

JSON

HTTP GET Request

HTTP POST Request

REST API Cloud Deployment

application.properties file vs application.yml file

Working with Dynamic Properties

Spring Boot Actuators

Microservices

Microservices Architecture

Load Balancing

Other Topics

Circuit Breaker

Redis Cache

Apache Kafka

Config Server

Mono & Flux Objects

Exception Handling in REST API's

REST API Unit Testing

Spring Security

JWT Authentication

Spring Batch