Use a running local MongoDB server or your own Atlas cluster. The web application listens on 8089; MongoDB normally listens on 27017. These are different processes. The connection instructions below include local defaults and Atlas environment variables.
@Document("products") chooses the collection. @Id marks the product id as MongoDB’s identifier. These examples retain numeric IDs 1–30 and reserve 31 for the sample button. A collection holds product documents; a document contains the fields of one product.
ProductRepository now extends MongoRepository<Product, Integer>. Spring Data supplies a runtime implementation with methods such as findAll, findById, insert, save and deleteById. We remove FileProductRepository so there is one repository choice. Unlike Stage 10, we are using an interface specifically supported by Spring Data.
ProductDataSeeder runs at startup through CommandLineRunner. If the products collection is empty, it copies the 30 file products into MongoDB. If data already exists, it leaves it alone. The file is now seed input; database reads supply the pages. Deleting every product and restarting will seed the collection again. Use a separate learning database.
The list page has Create sample product (ID 31). Click it to insert a product and open its details. Change its stock to 0 and click Save stock. The controller validates non-negative stock and the service saves the change. Click Delete this product to remove it. Repeating Create returns the existing sample while it exists.
Each write form sends POST. Java changes the database and returns redirect:/products or the details URL. The browser then requests that page with GET. Refreshing the resulting page therefore repeats a read. Unknown IDs produce 404; a negative submitted stock produces 400.
Create ID 31, change its stock, stop the app and start it with the same database settings. The product and changed stock remain. This is persistence: the stored value survives the Java process ending. This small local learning app has no login or concurrent-edit protection; those are later topics.
In Stage 11, Spring Data MongoDB supplies the repository implementation. We declare the interface below; we do not write the MongoDB query code for these inherited methods.
public interface ProductRepository
extends MongoRepository<Product, Integer> {
}Product identifies the Java objects handled by the repository. Integer identifies their ID type. During startup, Spring Data creates a repository object implementing this interface. Spring supplies that object to ProductService through its constructor.
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}Our service can then call these methods on the supplied object:
repository.findAll(); // Read all products from MongoDB
repository.findById(1); // Find product ID 1; returns Optional<Product>Keep the two findAll methods separate: ProductService.findAll(boolean inStockOnly) is our own method. It calls repository.findAll() to read the stored products, then applies our Java stock-filtering rule. Spring Data supplies the repository method, not our business rule.
DAO — Data Access Object names the responsibility of accessing stored data. A DAO exposes operations such as finding, saving and deleting records, keeping storage-specific code away from the service. It is an architectural term, not a Java keyword or a required class name.
| Stage | Contract | Object performing data access | Who supplies the implementation? |
|---|---|---|---|
| 10 | ProductRepository interface | FileProductRepository object | We write its method bodies. |
| 11 | ProductRepository extends MongoRepository | The repository object created by Spring Data | Spring Data supplies MongoDB access for the inherited operations. |
DAO emphasizes access to stored data. A repository presents operations on application objects, such as products. They are closely related ideas, with different emphasis. Our product repository fulfills the data-access responsibility you learned about as DAO. This example does not need an additional ProductDao layer wrapping the same operations.
Check your understanding: Who writes the stock filter? We do, in ProductService. Who implements the inherited repository findAll() and findById()? Spring Data MongoDB does. Does @Repository alone implement any ordinary Java interface? No; Stage 10 still needs our FileProductRepository method bodies.
Local: start your installed MongoDB service/server on localhost:27017. The supplied properties already use that address and the dedicated database spring_stage11_products. No environment variables are needed for that default.
Atlas: create a cluster, a database user and an IP access-list entry for your current IP. Choose Connect → Drivers → Java and copy your cluster URI. Replace its username and password; percent-encode reserved characters in credentials. Use your own cluster hostname, not the placeholders below. See the complete Atlas setup guide.
Set the variables in the same terminal used to start Maven. Do not put real passwords in source code or commit them.
set "MONGODB_URI=mongodb+srv://YOUR_USER:YOUR_ENCODED_PASSWORD@YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority"
set "MONGODB_DATABASE=spring_stage11_products"
mvn spring-boot:run$env:MONGODB_URI="mongodb+srv://YOUR_USER:YOUR_ENCODED_PASSWORD@YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority"
$env:MONGODB_DATABASE="spring_stage11_products"
mvn spring-boot:runexport MONGODB_URI='mongodb+srv://YOUR_USER:YOUR_ENCODED_PASSWORD@YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority'
export MONGODB_DATABASE='spring_stage11_products'
mvn spring-boot:runIf startup reports a connection timeout, check the server/cluster status, URI, database credentials, Atlas IP access list and network access. The application needs a reachable database to start successfully.
Use JDK 21+ and Maven 3.9+. Extract spring-products-mongodb.zip and open a terminal in the spring-products-mongodb folder containing pom.xml. Keep the source files in their supplied paths.
mvn spring-boot:runOpen http://localhost:8089/products. Keep the terminal open. Stop with Ctrl+C. If the port is occupied, change server.port in src/main/resources/application.properties, restart and use the new port in the browser.
# Choose another available port if this port is busy.
server.port=8089
# Local MongoDB is the default; set MONGODB_URI to use Atlas.
spring.mongodb.uri=${MONGODB_URI:mongodb://localhost:27017}
spring.mongodb.database=${MONGODB_DATABASE:spring_stage11_products}
Fresh database: 30 rows, 29 in stock. Create ID 31: 31 total. Set its stock to 0: still 31 total, 29 in stock. Restart: ID 31 still has stock 0. Delete ID 31: 30 total and its details URL returns 404.
Every project file is reproduced below. Create each file at the indicated path if building manually.
/target/
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/>
</parent>
<groupId>com.edupoly</groupId>
<artifactId>spring-products-mongodb</artifactId>
<version>1.0.0</version>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package com.edupoly.products;
import java.math.BigDecimal;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Document("products")
public class Product {
@Id
private int id;
private String name;
private String category;
private BigDecimal price;
private String brand;
private int stock;
private String description;
private int warrantyMonths;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getWarrantyMonths() {
return warrantyMonths;
}
public void setWarrantyMonths(int warrantyMonths) {
this.warrantyMonths = warrantyMonths;
}
}
package com.edupoly.products;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
package com.edupoly.products;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
@Controller
public class ProductController {
private final ProductService service;
public ProductController(ProductService service) {
this.service = service;
}
@GetMapping({"/", "/products"})
public String listProducts(
@RequestParam(name = "inStockOnly", defaultValue = "false") boolean inStockOnly,
Model model) {
model.addAttribute("products", service.findAll(inStockOnly));
return "products";
}
@GetMapping("/products/{id}")
public String productDetails(@PathVariable("id") int id, Model model) {
Product product = service.findById(id).orElseThrow(() ->
new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found"));
model.addAttribute("product", product);
return "product-details";
}
@PostMapping("/products/sample")
public String createSample() {
Product product = service.createSample();
return "redirect:/products/" + product.getId();
}
@PostMapping("/products/{id}/stock")
public String updateStock(@PathVariable("id") int id,
@RequestParam("stock") int stock) {
if (stock < 0) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Stock cannot be negative");
}
Product product = service.findById(id).orElseThrow(() ->
new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found"));
service.updateStock(product, stock);
return "redirect:/products/" + id;
}
@PostMapping("/products/{id}/delete")
public String delete(@PathVariable("id") int id) {
if (service.findById(id).isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found");
}
service.delete(id);
return "redirect:/products";
}
}
package com.edupoly.products;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class ProductDataSeeder implements CommandLineRunner {
private final ProductRepository repository;
private final ProductFileReader reader;
public ProductDataSeeder(ProductRepository repository, ProductFileReader reader) {
this.repository = repository;
this.reader = reader;
}
@Override
public void run(String... args) {
if (repository.count() == 0) {
repository.saveAll(Arrays.asList(reader.getProducts()));
System.out.println("Seeded 30 products into the empty demo database");
}
}
}
package com.edupoly.products;
import java.io.IOException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import tools.jackson.databind.json.JsonMapper;
@Component
public class ProductFileReader {
private final Product[] products;
public ProductFileReader() throws IOException {
var resource = new ClassPathResource("data/products.txt");
try (var input = resource.getInputStream()) {
JsonMapper mapper = JsonMapper.builder().build();
products = mapper.readValue(input, Product[].class);
}
System.out.println("Created ProductFileReader");
}
public Product[] getProducts() {
return products;
}
}
package com.edupoly.products;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ProductRepository extends MongoRepository<Product, Integer> {
}
package com.edupoly.products;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
public List<Product> findAll(boolean inStockOnly) {
List<Product> selected = new ArrayList<>();
for (Product product : repository.findAll()) {
if (!inStockOnly || product.getStock() > 0) {
selected.add(product);
}
}
return selected;
}
public Optional<Product> findById(int id) {
return repository.findById(id);
}
public Product createSample() {
Optional<Product> existing = repository.findById(31);
if (existing.isPresent()) {
return existing.get();
}
Product product = new Product();
product.setId(31);
product.setName("Practice Product");
product.setCategory("Study Essentials");
product.setPrice(new java.math.BigDecimal("250.00"));
product.setBrand("StudyWorks");
product.setStock(8);
product.setDescription("Created through the application's Create sample button.");
product.setWarrantyMonths(6);
return repository.insert(product);
}
public Product updateStock(Product product, int stock) {
product.setStock(stock);
return repository.save(product);
}
public void delete(int id) {
repository.deleteById(id);
}
}
# Choose another available port if this port is busy.
server.port=8089
# Local MongoDB is the default; set MONGODB_URI to use Atlas.
spring.mongodb.uri=${MONGODB_URI:mongodb://localhost:27017}
spring.mongodb.database=${MONGODB_DATABASE:spring_stage11_products}
[
{
"id": 1,
"name": "Wireless Mouse",
"category": "Electronics",
"price": 699,
"brand": "EduTech",
"stock": 12,
"description": "Wireless Mouse for everyday learning, study and work. Product 1 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 2,
"name": "Mechanical Keyboard",
"category": "Electronics",
"price": 3499,
"brand": "EduTech",
"stock": 19,
"description": "Mechanical Keyboard for everyday learning, study and work. Product 2 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 3,
"name": "USB-C Hub",
"category": "Electronics",
"price": 1599,
"brand": "EduTech",
"stock": 26,
"description": "USB-C Hub for everyday learning, study and work. Product 3 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 4,
"name": "Laptop Stand",
"category": "Electronics",
"price": 1299,
"brand": "EduTech",
"stock": 33,
"description": "Laptop Stand for everyday learning, study and work. Product 4 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 5,
"name": "Webcam",
"category": "Electronics",
"price": 2199,
"brand": "EduTech",
"stock": 0,
"description": "Webcam for everyday learning, study and work. Product 5 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 6,
"name": "Bluetooth Speaker",
"category": "Electronics",
"price": 2499,
"brand": "EduTech",
"stock": 47,
"description": "Bluetooth Speaker for everyday learning, study and work. Product 6 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 7,
"name": "Noise-Cancelling Headphones",
"category": "Electronics",
"price": 4999,
"brand": "EduTech",
"stock": 54,
"description": "Noise-Cancelling Headphones for everyday learning, study and work. Product 7 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 8,
"name": "Portable SSD",
"category": "Electronics",
"price": 6499,
"brand": "EduTech",
"stock": 61,
"description": "Portable SSD for everyday learning, study and work. Product 8 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 9,
"name": "USB Flash Drive",
"category": "Electronics",
"price": 599,
"brand": "EduTech",
"stock": 68,
"description": "USB Flash Drive for everyday learning, study and work. Product 9 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 10,
"name": "Monitor",
"category": "Electronics",
"price": 12999,
"brand": "EduTech",
"stock": 15,
"description": "Monitor for everyday learning, study and work. Product 10 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 11,
"name": "Desk Lamp",
"category": "Electronics",
"price": 899,
"brand": "EduTech",
"stock": 22,
"description": "Desk Lamp for everyday learning, study and work. Product 11 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 12,
"name": "Power Bank",
"category": "Electronics",
"price": 1799,
"brand": "EduTech",
"stock": 29,
"description": "Power Bank for everyday learning, study and work. Product 12 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 13,
"name": "Smartphone Stand",
"category": "Electronics",
"price": 399,
"brand": "EduTech",
"stock": 36,
"description": "Smartphone Stand for everyday learning, study and work. Product 13 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 14,
"name": "HDMI Cable",
"category": "Electronics",
"price": 499,
"brand": "EduTech",
"stock": 43,
"description": "HDMI Cable for everyday learning, study and work. Product 14 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 15,
"name": "Ethernet Adapter",
"category": "Electronics",
"price": 999,
"brand": "EduTech",
"stock": 50,
"description": "Ethernet Adapter for everyday learning, study and work. Product 15 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 16,
"name": "Graphics Tablet",
"category": "Electronics",
"price": 3999,
"brand": "EduTech",
"stock": 57,
"description": "Graphics Tablet for everyday learning, study and work. Product 16 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 17,
"name": "Microphone",
"category": "Electronics",
"price": 2999,
"brand": "EduTech",
"stock": 64,
"description": "Microphone for everyday learning, study and work. Product 17 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 18,
"name": "Laptop Backpack",
"category": "Electronics",
"price": 1499,
"brand": "EduTech",
"stock": 71,
"description": "Laptop Backpack for everyday learning, study and work. Product 18 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 19,
"name": "Screen Cleaner",
"category": "Electronics",
"price": 249,
"brand": "EduTech",
"stock": 18,
"description": "Screen Cleaner for everyday learning, study and work. Product 19 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 20,
"name": "Mouse Pad",
"category": "Electronics",
"price": 299,
"brand": "EduTech",
"stock": 25,
"description": "Mouse Pad for everyday learning, study and work. Product 20 includes a simple user guide and protective packaging.",
"warrantyMonths": 12
},
{
"id": 21,
"name": "Notebook",
"category": "Study Essentials",
"price": 149,
"brand": "StudyWorks",
"stock": 32,
"description": "Notebook for everyday learning, study and work. Product 21 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 22,
"name": "Ballpoint Pen Set",
"category": "Study Essentials",
"price": 199,
"brand": "StudyWorks",
"stock": 39,
"description": "Ballpoint Pen Set for everyday learning, study and work. Product 22 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 23,
"name": "Water Bottle",
"category": "Study Essentials",
"price": 599,
"brand": "StudyWorks",
"stock": 46,
"description": "Water Bottle for everyday learning, study and work. Product 23 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 24,
"name": "Desk Organizer",
"category": "Study Essentials",
"price": 449,
"brand": "StudyWorks",
"stock": 53,
"description": "Desk Organizer for everyday learning, study and work. Product 24 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 25,
"name": "Reading Light",
"category": "Study Essentials",
"price": 699,
"brand": "StudyWorks",
"stock": 60,
"description": "Reading Light for everyday learning, study and work. Product 25 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 26,
"name": "Calculator",
"category": "Study Essentials",
"price": 499,
"brand": "StudyWorks",
"stock": 67,
"description": "Calculator for everyday learning, study and work. Product 26 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 27,
"name": "Whiteboard",
"category": "Study Essentials",
"price": 1299,
"brand": "StudyWorks",
"stock": 14,
"description": "Whiteboard for everyday learning, study and work. Product 27 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 28,
"name": "Marker Set",
"category": "Study Essentials",
"price": 299,
"brand": "StudyWorks",
"stock": 21,
"description": "Marker Set for everyday learning, study and work. Product 28 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 29,
"name": "Book Stand",
"category": "Study Essentials",
"price": 799,
"brand": "StudyWorks",
"stock": 28,
"description": "Book Stand for everyday learning, study and work. Product 29 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
},
{
"id": 30,
"name": "Travel Mug",
"category": "Study Essentials",
"price": 649,
"brand": "StudyWorks",
"stock": 35,
"description": "Travel Mug for everyday learning, study and work. Product 30 includes a simple user guide and protective packaging.",
"warrantyMonths": 6
}
]
body{margin:0;padding:28px 16px;background:#f5f7fb;color:#17253d;font:16px/1.6 system-ui,sans-serif}main{max-width:1100px;margin:20px auto;padding:28px;background:#fff;border:1px solid #dbe2ec;border-radius:12px}h1{margin-top:0}a{color:#2457a6;text-underline-offset:3px}.table-scroll{overflow-x:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:12px;border-bottom:1px solid #dbe2ec}th{background:#edf3fc}tbody tr:hover{background:#f8faff}.detail{max-width:760px}dl{display:grid;grid-template-columns:160px minmax(0,1fr);gap:12px 20px;margin:24px 0}dt{font-weight:650}dd{margin:0;overflow-wrap:anywhere}@media(max-width:500px){main{padding:16px}dl{grid-template-columns:1fr;gap:4px}dd{margin-bottom:12px}}
.catalog-nav{margin:20px 0}form{margin:18px 0}button{padding:9px 14px;cursor:pointer}input{padding:8px;max-width:140px}form button{margin-left:8px}
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title th:text="${product.name}">Product details</title>
<link rel="stylesheet" th:href="@{/style.css}">
</head>
<body>
<main class="detail">
<h1>Product details</h1>
<h2 th:text="${product.name}">Wireless Mouse</h2>
<dl>
<dt>ID</dt><dd th:text="${product.id}">1</dd>
<dt>Name</dt><dd th:text="${product.name}">Wireless Mouse</dd>
<dt>Category</dt><dd th:text="${product.category}">Electronics</dd>
<dt>Price (INR)</dt><dd th:text="${product.price}">699</dd>
<dt>Brand</dt><dd th:text="${product.brand}">EduTech</dd>
<dt>Stock</dt><dd th:text="${product.stock}">12</dd>
<dt>Description</dt><dd th:text="${product.description}">Product description</dd>
<dt>Warranty (months)</dt><dd th:text="${product.warrantyMonths}">12</dd>
</dl>
<form th:action="@{/products/{id}/stock(id=${product.id})}" method="post">
<label for="stock">New stock</label>
<input id="stock" name="stock" type="number" min="0" required th:value="${product.stock}">
<button type="submit">Save stock</button>
</form>
<form th:action="@{/products/{id}/delete(id=${product.id})}" method="post">
<button type="submit">Delete this product</button>
</form>
<a th:href="@{/products}">Back to products</a>
</main>
</body>
</html>
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Products</title>
<link rel="stylesheet" th:href="@{/style.css}">
</head>
<body>
<main>
<h1>Products</h1>
<p>Stage 11: Products stored in MongoDB. Select More details for a product.</p>
<nav class="catalog-nav"><a th:href="@{/products}">All products</a> · <a th:href="@{/products(inStockOnly=true)}">In-stock products</a></nav>
<form th:action="@{/products/sample}" method="post"><button type="submit">Create sample product (ID 31)</button></form>
<div class="table-scroll">
<table>
<thead>
<tr><th>ID</th><th>Name</th><th>Category</th><th>Price (INR)</th><th>Action</th></tr>
</thead>
<tbody>
<tr th:each="product : ${products}">
<td th:text="${product.id}">1</td>
<td th:text="${product.name}">Wireless Mouse</td>
<td th:text="${product.category}">Electronics</td>
<td th:text="${product.price}">699</td>
<td><a th:href="@{/products/{id}(id=${product.id})}">More details</a></td>
</tr>
</tbody>
</table>
</div>
</main>
</body>
</html>
Create the sample, update stock to 0, restart and verify persistence, then delete the sample. Keep notes of the URL, HTTP method and database operation for each action.
References: Spring constructor injection, Spring Data MongoDB repositories.
| Term | Our concrete example | Its place in Spring |
|---|---|---|
| @Document and @Id | Product mapped to products; numeric id | Spring Data uses these annotations to map Java fields to MongoDB documents. |
| MongoRepository | ProductRepository extends MongoRepository<Product, Integer> | Spring Data supplies the data-access implementation for this supported interface. |
| CRUD | Insert, find, save, delete | Create, read, update and delete are operations on stored data. |
| Persistence | A stock update survives restart | MongoDB retains the data independently of this running web app. |
One small check: Point to the class, constructor or annotation that demonstrates each term before continuing.