Spring
Spring
---
With IoC, the container creates and injects objects for you.
Constructor Injection
Setter Injection
@Component
public class Engine {
public String start() { return "Engine started"; }
}
@Component
public class Car {
private Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public void drive() {
System.out.println(engine.start());
}
}
---
Chapter 3: Spring Beans & Configuration
1. XML-based
3. Java-based Configuration:
@Configuration
public class AppConfig {
@Bean
public Engine engine() {
return new Engine();
}
}
---
Model → Data.
4.2 Flow
3. Controller processes.
Example Controller:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring!";
}
}
---
1. Go to start.spring.io
2. Choose:
Maven Project
Java 17+
---
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
---
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
}
7.2 Repository
@Service
public class UserService {
@Autowired private UserRepository repo;
public List<User> getAll() { return repo.findAll(); }
}
@RestController
public class UserController {
@Autowired private UserService service;
@GetMapping("/users")
public List<User> list() { return service.getAll(); }
}
---
---
---
DayTopic
1–2 IoC, DI
3–4 Beans, Scopes
5–6 MVC Basics
7–8 Spring Boot Setup
9–10 JPA + CRUD
11–12 Security Basics
13–15 Build Mini Project
---
If you want, I can now start Chapter 1 in full “textbook depth” — with theory,
real-world analogies, and diagrams in words — so it feels like reading a
printed Java Spring book from the first page.
---
---
Web applications
REST APIs
Microservices
Desktop/Batch applications
> 🛠 “A big toolbox for Java developers where you pick only the tools you
need.”
---
With IoC, you give control to Spring — it creates objects and gives them to
you when needed.
📌 Real-world analogy:
Imagine hiring a personal assistant (Spring) who brings you everything you
need instead of you going to get it yourself.
---
B. Dependency Injection (DI)
Example:
@Component
class Engine {}
@Component
class Car {
private Engine engine;
@Autowired
Car(Engine engine) {
this.engine = engine;
}
}
---
Means you don’t write the same logging/security code in every method.
---
+---------------------------------------------------+
| SPRING FRAMEWORK |
+---------------------------------------------------+
| Spring Core Container | IoC, DI |
| Spring Context | Bean management |
| Spring AOP | Cross-cutting concerns |
| Spring DAO / ORM | Database integration |
| Spring Web MVC | Web applications |
| Spring Security | Authentication/Access |
+---------------------------------------------------+
---
Module Purpose
---
Without Spring
With Spring
@Component
class Engine {}
@Component
class Car {
private Engine engine;
@Autowired
Car(Engine engine) { this.engine = engine; }
}
---
When you need integration with multiple technologies (DB, Messaging, Cloud).
1.9 Summary
---
✅ Exercise 1
---
📌 Next Chapter (2) → We’ll dive deep into IoC & DI — the heart of Spring —
with diagrams, multiple injection types, and a real mini-project example.
---
Do you want me to continue with Chapter 2 now so you get the IoC & DI
details immediately before moving to Spring Boot later? That way, the flow
will be crystal clear.
---
---
Here:
Inversion of Control
With IoC:
The container creates objects (called beans) and wires them together.
📌 Analogy:
In a restaurant, you don’t go to the kitchen and cook your food.
You just place an order (declare requirement) → Chef (IoC container) prepares
and delivers the dish.
---
---
---
Why DI is Important?
---
1. Constructor Injection
Dependencies are passed via the constructor.
@Component
class Engine {
public String start() { return "Engine started"; }
}
@Component
class Car {
private Engine engine;
@Autowired
public Car(Engine engine) { // Injection here
this.engine = engine;
}
public void drive() {
System.out.println(engine.start());
}
}
---
2. Setter Injection
@Component
class Car {
private Engine engine;
@Autowired
public void setEngine(Engine engine) {
this.engine = engine;
}
}
---
3. Field Injection
Dependencies are injected directly into fields.
@Component
class Car {
@Autowired
private Engine engine;
}
✅ Best for: Quick demos, but not recommended in large projects (harder to
test).
---
Example:
@Component
@Scope("prototype")
class Engine {}
---
Annotations:
Configuration Classes:
---
Without Spring:
With Spring:
@Component
class Chef {
public String cook() { return "Cooking pasta"; }
}
@Component
class Restaurant {
private Chef chef;
@Autowired
public Restaurant(Chef chef) {
this.chef = chef;
}
}
You can replace Chef with ItalianChef, FrenchChef easily without touching
Restaurant.
---
@Component
class Engine {
public String start() { return "Engine started"; }
}
@Component
class Car {
private Engine engine;
@Autowired
public Car(Engine engine) {
this.engine = engine;
}
public void drive() {
System.out.println(engine.start() + " — Car is moving!");
}
}
@SpringBootApplication
public class SpringDemoApp {
public static void main(String[] args) {
ApplicationContext context =
SpringApplication.run(SpringDemoApp.class, args);
Car car = context.getBean(Car.class);
car.drive();
}
}
Output:
Reusable components.
Flexible architecture.
---
✅ Exercise 2
3. List all Bean Scopes and give one use case for each.
---
📌 Next Chapter (3) → We’ll cover Spring Beans & Configuration in depth —
including:
If you want, I can make Chapter 3 even more practical by adding bean
lifecycle diagrams and a live example with init/destroy methods so you can
see how Spring manages objects.
Alright, let’s dive into Chapter 3 — we’ll go deep into Spring Beans &
Configuration, and I’ll include lifecycle diagrams (in text form) + real coding
examples so it’s clear how Spring manages objects from birth to destruction.
---
---
📌 Analogy:
Think of Spring as a nursery that takes care of your plants (beans) — planting
(creating), watering (configuring), pruning (managing), and removing
(destroying) them when they are no longer needed.
---
---
A. XML-based Configuration
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="engine" class="com.example.Engine" />
</beans>
---
B. Annotation-based Configuration
@Component
public class Engine {}
---
C. Java-based Configuration
@Configuration
public class AppConfig {
@Bean
public Engine engine() {
return new Engine();
}
}
---
Example:
@Component
@Scope("prototype")
public class Engine {}
---
[1] Instantiate → [2] Populate Properties → [3] Call init() → [4] Bean Ready
→ [5] Destroy
1. Instantiate
2. Populate Properties
4. Bean Ready
5. Destroy
When the container shuts down, beans are destroyed (if singleton).
---
Using Annotations
@Component
public class Engine {
@PostConstruct
public void init() {
System.out.println("Engine is being initialized...");
}
@PreDestroy
public void cleanup() {
System.out.println("Engine is being destroyed...");
}
}
---
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public Engine engine() {
return new Engine();
}
}
---
---
@Component
class Engine {
@PostConstruct
public void start() {
System.out.println("🚗 Engine started");
}
@PreDestroy
public void stop() {
System.out.println("🛑 Engine stopped");
}
}
@SpringBootApplication
public class BeanLifecycleDemo {
public static void main(String[] args) {
ConfigurableApplicationContext context =
SpringApplication.run(BeanLifecycleDemo.class, args);
context.close(); // Trigger destroy phase
}
}
Output:
🚗 Engine started
🛑 Engine stopped
---
---
---
✅ Exercise 3
1. Write a bean using Java-based configuration with custom init and destroy
methods.
📌 Next Chapter (4) → We’ll move into Spring MVC — learning the Model-View-
Controller pattern, how Spring handles HTTP requests, and building your first
controller with @RestController.
---
If you want, I can make Chapter 4 not just theory but a hands-on Spring Boot
mini web app so you immediately see MVC in action while learning.
Alright 👍
Let’s make Chapter 4 both theoretical and practical — you’ll understand
Spring MVC and also build a small working Spring Boot web app so the
concepts stick.
---
---
Spring MVC is a web framework within Spring for building web applications
and REST APIs.
It follows the MVC Pattern:
Component Responsibility
📌 Analogy:
Model = Kitchen ingredients (data).
---
Step-by-step:
---
---
---
Go to start.spring.io
Dependencies:
Spring Web
---
@SpringBootApplication
public class HelloMvcApp {
public static void main(String[] args) {
SpringApplication.run(HelloMvcApp.class, args);
}
}
---
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "Hello, Spring MVC!";
}
}
---
Output:
---
@RestController
@RequestMapping("/api")
public class GreetingController {
// Example: /api/greet/John
@GetMapping("/greet/{name}")
public String greetByName(@PathVariable String name) {
return "Hello, " + name + "!";
}
// Example: /api/hi?name=John&age=25
@GetMapping("/hi")
public String greetWithParams(@RequestParam String name,
@RequestParam int age) {
return "Hi " + name + ", you are " + age + " years old!";
}
}
---
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/one")
public User getUser() {
return new User("Alice", 30);
}
}
Output JSON:
{
"name": "Alice",
"age": 30
}
---
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Create Controller:
@Controller
public class WebController {
@GetMapping("/welcome")
public String welcomePage(Model model) {
model.addAttribute("name", "Tharani");
return "welcome";
}
}
<!DOCTYPE html>
<html>
<head><title>Welcome</title></head>
<body>
<h1>Welcome, <span th:text="${name}"></span>!</h1>
</body>
</html>
Visit http://localhost:8080/welcome
---
---
✅ Exercise 4
2. Create a /sum endpoint that takes two query parameters (a, b) and returns
their sum in JSON.
---
📌 Next Chapter (5) → We’ll introduce Spring Boot — why it’s faster than plain
Spring, how to set it up, and how to use Starters & Auto-Configuration.
We’ll also upgrade our current app into a Spring Boot + Database project.
---
Perfect 👍
Then Chapter 5 will be about Spring Boot + MySQL CRUD so you learn Boot,
JPA, and Database integration in one go.
---
---
---
Concept Meaning
---
We will:
2. Connect to MySQL.
Create Student
Update Student
Delete Student
---
Go to start.spring.io
Dependencies:
Spring Web
MySQL Driver
---
spring.datasource.url=jdbc:mysql://localhost:3306/schooldb
spring.datasource.username=root
spring.datasource.password=your_password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
💡 Make sure you have MySQL running and schooldb database created.
---
import jakarta.persistence.*;
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
---
import org.springframework.data.jpa.repository.JpaRepository;
---
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentService {
---
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/students")
public class StudentController {
private final StudentService service;
@PostMapping
public Student create(@RequestBody Student student) {
return service.save(student);
}
@GetMapping
public List<Student> getAll() {
return service.getAll();
}
@GetMapping("/{id}")
public Student getById(@PathVariable Long id) {
return service.getById(id);
}
@PutMapping("/{id}")
public Student update(@PathVariable Long id, @RequestBody Student
student) {
return service.update(id, student);
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Long id) {
service.delete(id);
return "Deleted Successfully";
}
}
---
{
"name": "Tharani",
"email": "tharani@example.com",
"age": 23
}
---
---
✅ Exercise 5
---
Do you want me to make Chapter 6 like that? That would give you full-stack
Spring experience.
Alright 🚀
Let’s build Chapter 6 — Spring Boot + Thymeleaf + MySQL so you can create
a complete full-stack web app without needing React or Angular yet.
---
---
---
Delete student
---
Go to start.spring.io
Dependencies:
Spring Web
MySQL Driver
Thymeleaf
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/schooldb
spring.datasource.username=root
spring.datasource.password=your_password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
---
import jakarta.persistence.*;
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
---
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class StudentService {
private final StudentRepository repo;
---
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class StudentController {
private final StudentService service;
public StudentController(StudentService service) {
this.service = service;
}
@GetMapping("/")
public String viewHomePage(Model model) {
model.addAttribute("listStudents", service.getAllStudents());
return "index";
}
@GetMapping("/showNewStudentForm")
public String showNewStudentForm(Model model) {
model.addAttribute("student", new Student());
return "new_student";
}
@PostMapping("/saveStudent")
public String saveStudent(@ModelAttribute("student") Student student) {
service.saveStudent(student);
return "redirect:/";
}
@GetMapping("/showFormForUpdate/{id}")
public String showFormForUpdate(@PathVariable Long id, Model model) {
model.addAttribute("student", service.getStudentById(id));
return "update_student";
}
@GetMapping("/deleteStudent/{id}")
public String deleteStudent(@PathVariable Long id) {
service.deleteStudent(id);
return "redirect:/";
}
}
---
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Student Management</title>
</head>
<body>
<h2>Student List</h2>
<a href="/showNewStudentForm">Add New Student</a>
<table border="1">
<tr>
<th>ID</th><th>Name</th><th>Email</th><th>Age</th><th>Actio
ns</th>
</tr>
<tr th:each="student : ${listStudents}">
<td th:text="${student.id}"></td>
<td th:text="${student.name}"></td>
<td th:text="${student.email}"></td>
<td th:text="${student.age}"></td>
<td>
<a th:href="@{/showFormForUpdate/{id}(id=$
{student.id})}">Edit</a>
<a th:href="@{/deleteStudent/{id}(id=${student.id})}">Delete</a>
</td>
</tr>
</table>
</body>
</html>
---
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Add Student</title>
</head>
<body>
<h2>Add Student</h2>
<form th:action="@{/saveStudent}" th:object="${student}"
method="post">
Name: <input type="text" th:field="*{name}"><br>
Email: <input type="text" th:field="*{email}"><br>
Age: <input type="number" th:field="*{age}"><br>
<button type="submit">Save</button>
</form>
</body>
</html>
---
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Update Student</title>
</head>
<body>
<h2>Update Student</h2>
<form th:action="@{/saveStudent}" th:object="${student}"
method="post">
<input type="hidden" th:field="*{id}">
Name: <input type="text" th:field="*{name}"><br>
Email: <input type="text" th:field="*{email}"><br>
Age: <input type="number" th:field="*{age}"><br>
<button type="submit">Update</button>
</form>
</body>
</html>
---
Edit details
Delete students
---
---
Do you want me to prepare Chapter 7 like that? That will make your app a
real-world secure application.
Alright — Chapter 7 will be the Spring Boot Security + Login System part of
your tutorial book.
We’ll add authentication and role-based access control to your Student
Management App from Chapter 6.
---
---
7.1 What is Spring Security?
Key Concepts:
---
Two roles:
Logout button
---
In your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
---
7.4 Step 2 — Create User Entity
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
---
import org.springframework.data.jpa.repository.JpaRepository;
---
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import
org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login").permitAll()
.requestMatchers("/showNewStudentForm", "/saveStudent",
"/showFormForUpdate/**", "/deleteStudent/**")
.hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/", true)
.permitAll()
)
.logout(logout -> logout.permitAll());
return http.build();
}
}
---
import org.springframework.security.core.userdetails.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
User user = userRepo.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
Collections.singleton(new SimpleGrantedAuthority(user.getRole()))
);
}
}
---
📂 src/main/resources/templates/login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form th:action="@{/login}" method="post">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<button type="submit">Login</button>
</form>
</body>
</html>
---
7.9 Step 7 — Insert Users into DB
Example SQL:
---
Modify index.html:
<a href="/logout">Logout</a>
---
1. Start app
2. Visit http://localhost:8080/login
3. Login as:
---
---
If you’re ready, Chapter 8 can be Deploying Spring Boot App to the Cloud
(Heroku / Railway / AWS) so you can put your app online.
That will make your Student Management System live for anyone to use.
Alright — Chapter 8 will be Deploying Your Spring Boot App to the Cloud so
your Student Management System can be accessed from anywhere.
We’ll use Heroku (free tier) because it’s beginner-friendly, and then I’ll briefly
explain how to move to AWS or Railway later.
---
---
Right now, your app runs only on your local machine (localhost:8080).
Deployment lets you:
---
8.2 Requirements
Before starting:
Maven installed
Git installed
---
Spring Boot must run on the port Heroku assigns (not 8080).
📂 src/main/resources/application.properties:
spring.datasource.url=jdbc:mysql://your-db-host/db_name
spring.datasource.username=your_db_user
spring.datasource.password=your_db_password
spring.jpa.hibernate.ddl-auto=update
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
---
---
---
8.7 Step 5 — Deploy to Heroku
# Login to Heroku
heroku login
---
For PostgreSQL:
spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}
---
heroku open
Your app will open in the browser with a public URL like:
https://your-app-name.herokuapp.com
---
8.10 Step 8 — Free Hosting Alternatives
---
Alternatives to Heroku
---
---
---
Sign up at Razorpay
You’ll get:
key_id = rzp_test_abc123
key_secret = xyz456
<dependency>
<groupId>com.razorpay</groupId>
<artifactId>razorpay-java</artifactId>
<version>1.4.4</version>
</dependency>
Step 3 — Payment Service 📄 PaymentService.java
@Service
public class PaymentService {
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
<button id="rzp-button1">Pay Now</button>
<script>
var options = {
"key": "rzp_test_abc123",
"amount": "50000", // example
"currency": "INR",
"name": "ShopEase",
"description": "Test Transaction",
"handler": function (response){
alert("Payment Successful: " + response.razorpay_payment_id);
}
};
var rzp1 = new Razorpay(options);
document.getElementById('rzp-button1').onclick = function(e){
rzp1.open();
e.preventDefault();
}
</script>
---
We’ll allow admin to upload an image file instead of typing the URL.
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=5MB
@PostMapping("/admin/products")
public String saveProduct(@RequestParam("file") MultipartFile file,
@ModelAttribute Product product) throws IOException {
String uploadDir = "src/main/resources/static/images/";
Path path = Paths.get(uploadDir + file.getOriginalFilename());
Files.write(path, file.getBytes());
product.setImageUrl("/images/" + file.getOriginalFilename());
productRepo.save(product);
return "redirect:/admin/products";
}
---
Right now, our cart is temporary. We’ll link it to the logged-in user so it’s
saved in the database.
Cart Entity
@Entity
@Data
public class CartItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private User user;
@ManyToOne
private Product product;
Repository
@Repository
public interface CartItemRepository extends JpaRepository<CartItem, Long> {
List<CartItem> findByUser(User user);
}
---
When a user checks out, we’ll move cart items to an Order table.
Order Entity
@Entity
@Data
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private User user;
private LocalDateTime orderDate;
private double totalAmount;
}
---
Real payments
Saved carts
---
Awesome — let’s make your project interview-ready with clean API docs and
safe, repeatable DB changes.
---
---
11.1.1 Dependency
pom.xml
Create OpenApiConfig.java
package com.shopease.config;
import io.swagger.v3.oas.models.info.*;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.servers.Server;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI shopEaseOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("ShopEase API")
.description("Mini e-commerce API for products, carts, orders and
auth")
.version("v1.0")
.contact(new Contact().name("ShopEase
Team").email("support@shopease.example")))
.servers(List.of(
new Server().url("http://localhost:8080").description("Local Dev")
));
}
}
Example — ProductController
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.*;
application.yml
spring:
profiles:
active: dev
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**" // open docs in dev
).permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/cart/**", "/checkout").hasRole("USER")
.anyRequest().permitAll()
)
.formLogin(login -> login.loginPage("/login").permitAll())
.logout(logout -> logout.permitAll());
return http.build();
}
In prod, either:
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false
---
Why Flyway?
11.2.1 Dependency
pom.xml
src/main/resources/db/migration/
V1 — Schema V1__init_schema.sql
application.yml
spring:
flyway:
enabled: true
locations: classpath:db/migration
jpa:
hibernate:
ddl-auto: validate # Let Flyway manage schema; prevent Hibernate from
altering it
---
application-dev.yml (local)
spring:
datasource:
url: jdbc:mysql://localhost:3306/shopease
username: root
password: root
jpa:
show-sql: true
springdoc:
swagger-ui:
enabled: true
api-docs:
enabled: true
application-prod.yml (cloud)
spring:
datasource:
url: ${JDBC_URL}
username: ${JDBC_USER}
password: ${JDBC_PASSWORD}
jpa:
show-sql: false
springdoc:
swagger-ui:
enabled: false
api-docs:
enabled: false
# Prod
mvn spring-boot:run -Dspring-boot.run.profiles=prod
---
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application-prod.yml
management:
endpoints:
web:
exposure:
include: health,info
Health: /actuator/health
---
11.5 Optional: JWT-auth in Swagger (if you move to token auth later)
@Bean
public OpenAPI shopEaseOpenAPI() {
return new OpenAPI()
.components(new Components().addSecuritySchemes("bearerAuth",
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")))
.addSecurityItem(new SecurityRequirement().addList("bearerAuth"))
.info(new Info().title("ShopEase API").version("v1.0"));
}
In Swagger UI, click Authorize, paste a Bearer <token> when your login flow
returns JWT.
---
Try:
---
---
✅ Exercise 11
2. Add Swagger annotations for Cart and Order controllers (tags, summaries,
responses).
3. Lock Swagger UI to ROLE_ADMIN in prod (or disable entirely) and test both
profiles.
---
If you’d like, next I can add Chapter 12 — Testing Strategy (Unit, Slice, and
Integration tests with JUnit 5 & Testcontainers) so your app has real test
coverage that also impresses interviewers. Would you like me to continue?
Awesome — let’s make your project testable and interview-ready. This chapter
gives you a complete testing stack for Spring Boot: unit, slice, and full
integration tests with JUnit 5, Mockito/MockMvc, and Testcontainers (real
database in Docker).
---
12.1 Goals
---
<dependencies>
<!-- Spring Boot test bundle: JUnit 5, AssertJ, Mockito, MockMvc, JSONassert
-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
---
Example — ProductServiceTest.java
@ExtendWith(MockitoExtension.class)
class ProductServiceTest {
@Test
void createsProduct_withDefaultStock() {
CreateProductRequest req = new CreateProductRequest("Phone X", "Great",
49999.0, null);
Product saved = new Product(1L, "Phone X", "Great", 49999.0, null);
when(repo.save(any(Product.class))).thenReturn(saved);
assertThat(result.getId()).isEqualTo(1L);
verify(repo).save(argThat(p -> p.getName().equals("Phone X")));
}
}
Tips
Prefer constructor injection in your code → easier to unit test (no reflection).
What: Load just the layer you’re testing with Spring auto-config for that layer.
Types: @DataJpaTest, @WebMvcTest, @JsonTest, etc.
@DataJpaTest
class ProductRepositoryTest {
@Test
void findsById_afterPersist() {
Product p = new Product(null, "Book", "Nice", 499.0, null);
Long id = em.persistAndGetId(p, Long.class);
em.flush();
Loads MVC infra + your controller, not services or DB (you mock them).
@WebMvcTest(ProductController.class)
class ProductControllerTest {
@Test
void listsProducts_asJson() throws Exception {
when(service.findAll()).thenReturn(List.of(new ProductDto(1L,"Phone",
"Great",49999.0,null,0)));
mvc.perform(get("/api/products"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Phone"));
}
}
---
What: Boot up the whole app with an actual DB (Testcontainers), real HTTP via
MockMvc or WebTestClient.
Purpose: Confidence that configuration, security, MVC, JPA, and Flyway all
work together.
@SpringBootTest
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class ShopEaseIT {
@Test @Order(1)
void healthEndpoint_works() throws Exception {
mvc.perform(get("/actuator/health"))
.andExpect(status().isOk());
}
}
---
@Testcontainers
public abstract class AbstractContainerIT {
// Choose one:
static MySQLContainer<?> DB = new MySQLContainer<>("mysql:8.4.0");
// static PostgreSQLContainer<?> DB = new
PostgreSQLContainer<>("postgres:16");
@DynamicPropertySource
static void registerProps(DynamicPropertyRegistry r) {
DB.start(); // starts once per JVM
r.add("spring.datasource.url", DB::getJdbcUrl);
r.add("spring.datasource.username", DB::getUsername);
r.add("spring.datasource.password", DB::getPassword);
r.add("spring.jpa.hibernate.ddl-auto", () -> "validate"); // Flyway manages
schema
}
}
@SpringBootTest
@AutoConfigureMockMvc
class ProductApiIT extends AbstractContainerIT {
@Test
void createAndFetchProduct() throws Exception {
String body = """
{"name":"Phone Z","description":"Solid","price":29999.0,"imageUrl":null}
""";
// Create
mvc.perform(post("/api/products")
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").isNumber());
// List
mvc.perform(get("/api/products"))
.andExpect(status().isOk())
.andExpect(jsonPath("$
[*].name").value(org.hamcrest.Matchers.hasItem("Phone Z")));
}
}
Notes
The first test run pulls the container image (slower). Subsequent runs are fast.
---
@WebMvcTest(AdminController.class)
class AdminSecurityTest {
@Test
@WithMockUser(roles = "ADMIN")
void adminCanAccess() throws Exception {
mvc.perform(get("/admin/products"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "USER")
void userForbiddenOnAdmin() throws Exception {
mvc.perform(get("/admin/products"))
.andExpect(status().isForbidden());
}
}
---
Seed test data via a test-only migration or data.sql under test resources.
src/test/resources/db/migration/
V999__test_seed.sql
Option B — data.sql
src/test/resources/data.sql
INSERT INTO product(name, price) VALUES ('Seed Item', 1000.00);
> Flyway runs main migrations first, then Spring runs data.sql.
---
Run locally:
# Unit + slice
mvn -q -Dtest='*Test' test
---
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.4
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: shopease
ports: ["3306:3306"]
options: >-
--health-cmd="mysqladmin ping -proot"
--health-interval=10s --health-timeout=5s --health-retries=5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Build & test
run: mvn -B verify
(Or use Testcontainers with Docker enabled; you don’t need the services:
block then.)
---
Focus on core business rules (service) and critical flows (login, checkout).
---
✅ Exercise 12
1. Add a service unit test for cart total calculation, covering: empty cart,
single item, multi-item, discount.
3. Create a Testcontainers integration test for the checkout flow: seed a user
+ product, POST /checkout, assert an orders row is created and cart becomes
empty.
---