REST APIs with Spring Boot + JPA
Spring Boot ও JPA দিয়ে বাস্তব REST API
1. The Most Common Java Job Today
Walk into nearly any backend team in Dhaka, Chattogram, Singapore, or Bangalore, and you will find the
same stack: Spring Boot serving HTTP JSON, Spring Data JPA talking to
PostgreSQL or MySQL. This module builds a complete CRUD API for a Customer entity —
Controller → Service → Repository → Database — end-to-end.
Customer entity-র পূর্ণাঙ্গ CRUD API তৈরি করব — Controller → Service → Repository → Database।
pom.xml snippet in Module 44.
2. REST in 90 Seconds
A RESTful API maps HTTP verbs to CRUD operations on resources. The resource here is "customer".
| HTTP | Path | Meaning | বাংলায় |
|---|---|---|---|
GET | /customers | List all | সব customer তালিকা। |
GET | /customers/{id} | Read one | একটি customer পড়া। |
POST | /customers | Create | নতুন customer যোগ। |
PUT | /customers/{id} | Update | আপডেট। |
DELETE | /customers/{id} | Delete | মুছে ফেলা। |
3. The Three-Layer Picture
4. Entity + Repository
A JPA entity is a plain Java class annotated with @Entity; each field maps
to a column. A repository is just an interface extending JpaRepository;
Spring generates the implementation at runtime — including custom finders derived from method names.
@Entity annotation-যুক্ত plain Java class — প্রতিটি field একেকটি DB column। Repository শুধু একটি interface যা JpaRepository-এর extension; Spring নিজেই runtime-এ implementation তৈরি করে।
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
@Entity
class Customer {
@Id @GeneratedValue
private Long id;
@NotBlank
private String name;
@Email
private String email;
private String city = "Dhaka";
// getters / setters elided for brevity
}
interface CustomerRepository extends JpaRepository<Customer, Long> {
// Spring generates the SQL from the method name
List<Customer> findByCity(String city);
List<Customer> findByEmailContainingIgnoreCase(String part);
}
Derived SQL (roughly):
findByCity("Dhaka")
→ SELECT * FROM customer WHERE city = ?
5. Controller + Service
The controller never touches the database directly. It parses input, calls the service, and returns a status plus a body. The service holds business rules; this is where you validate, throw, and orchestrate.
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import jakarta.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/customers")
class CustomerController {
private final CustomerService svc;
CustomerController(CustomerService svc) { this.svc = svc; }
@GetMapping
public List<Customer> all() { return svc.findAll(); }
@GetMapping("/{id}")
public ResponseEntity<Customer> one(@PathVariable Long id) {
return svc.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public Customer create(@Valid @RequestBody Customer c) { return svc.save(c); }
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { svc.deleteById(id); }
}
import org.springframework.stereotype.Service;
import java.util.*;
@Service
class CustomerService {
private final CustomerRepository repo;
CustomerService(CustomerRepository repo) { this.repo = repo; }
List<Customer> findAll() { return repo.findAll(); }
Optional<Customer> findById(Long id) { return repo.findById(id); }
Customer save(Customer c) {
if (c.getEmail() == null) throw new IllegalArgumentException("email required");
return repo.save(c);
}
void deleteById(Long id) { repo.deleteById(id); }
}
6. Make It Run Without Spring — In-Memory Repository
To feel what Spring Data JPA does for free, here is the same architecture with a hand-written in-memory repo — no Spring, no database. The code shape is identical to a real app.
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
class Customer {
Long id; String name; String city;
Customer(String n, String c) { name = n; city = c; }
public String toString() { return id + ":" + name + "@" + city; }
}
class InMemoryRepo {
private final Map<Long, Customer> db = new LinkedHashMap<>();
private final AtomicLong seq = new AtomicLong(0);
Customer save(Customer c) {
if (c.id == null) c.id = seq.incrementAndGet();
db.put(c.id, c);
return c;
}
Optional<Customer> findById(Long id) { return Optional.ofNullable(db.get(id)); }
Collection<Customer> findAll() { return db.values(); }
List<Customer> findByCity(String city) {
return db.values().stream().filter(c -> c.city.equals(city)).toList();
}
}
class Main {
public static void main(String[] args) {
InMemoryRepo repo = new InMemoryRepo();
repo.save(new Customer("Arif", "Dhaka"));
repo.save(new Customer("Fatima", "Chattogram"));
repo.save(new Customer("Rakib", "Dhaka"));
System.out.println("ALL: " + repo.findAll());
System.out.println("byCity=Dhaka:" + repo.findByCity("Dhaka"));
System.out.println("byId(2): " + repo.findById(2L).orElse(null));
}
}
7. Vocabulary
| Term | Meaning | বাংলায় |
|---|---|---|
| Entity | A class mapped to a DB table. | DB table-এ mapped class। |
| Repository | Data-access abstraction. | DB access-এর abstraction। |
| DTO | Data Transfer Object — plain payload, not an entity. | HTTP payload-এর জন্য plain object। |
| DDL | Data Definition Language — CREATE, ALTER. | CREATE, ALTER ইত্যাদি SQL। |
| N+1 problem | Fetching 1 parent + N child queries — slow. | ১টি parent-এর জন্য N-টি child query — ধীর। |
| Idempotent | Same result whether called once or many times. | এক বার বা অনেক বার — ফলাফল একই। |
8. Practice Problems
-
Given a JPA method name
findByCityAndNameStartingWith, write out the SQL Spring Data will generate.findByCityAndNameStartingWithmethod-এ Spring Data কী SQL তৈরি করবে, লিখুন।✨ Show Answer
Answer:
SELECT c.* FROM customer c WHERE c.city = ? AND c.name LIKE ? || '%'— JPA parses the method name:Bybegins the predicate,CityandNameare property names,Andcombines, andStartingWithturns into aLIKEwith a trailing wildcard. -
Extend the in-memory repo with a
deleteById(Long)method and acountByCitymethod. Run a demo.In-memory repo-তেdeleteById(Long)এবংcountByCitymethod যোগ করুন, একটি demo চালান।✨ Show Answer
Main.javaimport java.util.*; class Row { Long id; String name, city; } class Repo { Map<Long, Row> db = new LinkedHashMap<>(); long seq = 0; Row save(String name, String city) { Row r = new Row(); r.id = ++seq; r.name = name; r.city = city; db.put(r.id, r); return r; } void deleteById(Long id) { db.remove(id); } long countByCity(String c) { return db.values().stream().filter(r -> r.city.equals(c)).count(); } } class Main { public static void main(String[] args) { Repo r = new Repo(); r.save("A", "Dhaka"); r.save("B", "Dhaka"); r.save("C", "Sylhet"); System.out.println("Dhaka count: " + r.countByCity("Dhaka")); r.deleteById(1L); System.out.println("After delete: " + r.countByCity("Dhaka")); } } -
Which HTTP verb should an "assign role" operation use, and why —
POST,PUT, orPATCH?একটি "assign role" API-র জন্য কোন HTTP verb ঠিক —POST,PUT, নাকিPATCH? কেন?✨ Show Answer
Answer:
PATCHis the best fit.PUTreplaces the entire resource representation,POSTcreates, andPATCHapplies a partial update — which is exactly what setting a single field (role) is. It is also typically idempotent when the payload describes the new value directly. -
Write a small validator utility that checks an email string matches
x@y.zwithout any library. Print OK or BAD for three inputs.Library ছাড়া একটি ছোট email validator বানান যাx@y.zpattern যাচাই করবে — তিনটি input-এ পরীক্ষা করুন।✨ Show Answer
Main.javaclass Main { static boolean isEmail(String s) { return s != null && s.matches("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"); } public static void main(String[] args) { for (String e : new String[]{"a@b.com", "bad", "x@y"}) { System.out.println(e + " -> " + (isEmail(e) ? "OK" : "BAD")); } } } -
Describe the N+1 query problem and one way JPA lets you avoid it.N+1 query সমস্যা কী, এবং JPA-তে এটি এড়ানোর একটি উপায় বলুন।
✨ Show Answer
Answer: If you load 1
Orderlist and then, for each row, lazily accessorder.getItems(), JPA issues 1 + N SELECTs — devastating at scale. Fix with a@EntityGraphattribute on the finder, aJOIN FETCHin a JPQL query, or preloading viafetch = FetchType.EAGER. A single JOIN replaces N round-trips to the DB.N+1 মানে — ১টি parent query-র পর প্রতিটি row-র জন্য আরো N-টি child query।
@EntityGraphবা JPQL-এরJOIN FETCHদিয়ে সব একসাথে আনুন।
Summary — Module 48
Spring Boot plus Spring Data JPA is the most common Java backend stack on earth. Three layers —
Controller, Service, Repository — keep responsibilities crisp, tests easy, and changes local. Derived
queries and JpaRepository mean you can have a real CRUD API running in minutes, not days.
JpaRepository-র সাহায্যে দিনের বদলে মিনিটে পূর্ণাঙ্গ CRUD API চালু হয়।