REST APIs with Spring Boot + JPA

Spring Boot ও JPA দিয়ে বাস্তব REST API

Read: ~45 min Advanced 5 practice problems JPA + CRUD

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.

আজকের বিশ্বের সবচেয়ে সাধারণ Java চাকরি — Spring Boot দিয়ে HTTP JSON API তৈরি করা, Spring Data JPA দিয়ে DB-র সাথে কথা বলা। এই মডিউলে আমরা একটি Customer entity-র পূর্ণাঙ্গ CRUD API তৈরি করব — Controller → Service → Repository → Database।
Sandbox note: Spring Boot and JPA are not installed in the browser sandbox. Install locally with Maven to run these — see 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".

HTTPPathMeaningবাংলায়
GET/customersList allসব customer তালিকা।
GET/customers/{id}Read oneএকটি customer পড়া।
POST/customersCreateনতুন customer যোগ।
PUT/customers/{id}Updateআপডেট।
DELETE/customers/{id}Deleteমুছে ফেলা।

3. The Three-Layer Picture

HTTP → Controller → Service → Repository → DB Client curl / browser JSON over HTTP @RestController parse · validate return JSON @Service business rules transactions @Repository JpaRepository SQL generated Each layer has one job. Easy to test, easy to change. Figure 48.1 — তিনটি layer: Controller (HTTP), Service (logic), Repository (DB)।

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.

JPA entity মানে @Entity annotation-যুক্ত plain Java class — প্রতিটি field একেকটি DB column। Repository শুধু একটি interface যা JpaRepository-এর extension; Spring নিজেই runtime-এ implementation তৈরি করে।
Customer.java + CustomerRepository.java
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.

Controller কখনো সরাসরি DB স্পর্শ করে না — input parse করে, service-কে call করে, তারপর HTTP response ফেরায়। Business rule থাকে service-এ।
CustomerController.java
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); }
}
CustomerService.java
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.

Spring Data JPA যা বিনা মূল্যে করে দেয় — বুঝতে, নিচে একই architecture-এ hand-written in-memory repo। Spring বা DB লাগছে না; কোডের গঠন real app-এর মতোই।
Main.java
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

TermMeaningবাংলায়
EntityA class mapped to a DB table.DB table-এ mapped class।
RepositoryData-access abstraction.DB access-এর abstraction।
DTOData Transfer Object — plain payload, not an entity.HTTP payload-এর জন্য plain object।
DDLData Definition Language — CREATE, ALTER.CREATE, ALTER ইত্যাদি SQL।
N+1 problemFetching 1 parent + N child queries — slow.১টি parent-এর জন্য N-টি child query — ধীর।
IdempotentSame result whether called once or many times.এক বার বা অনেক বার — ফলাফল একই।

8. Practice Problems

  1. Given a JPA method name findByCityAndNameStartingWith, write out the SQL Spring Data will generate.
    findByCityAndNameStartingWith method-এ Spring Data কী SQL তৈরি করবে, লিখুন।
    ✨ Show Answer

    Answer: SELECT c.* FROM customer c WHERE c.city = ? AND c.name LIKE ? || '%' — JPA parses the method name: By begins the predicate, City and Name are property names, And combines, and StartingWith turns into a LIKE with a trailing wildcard.

  2. Extend the in-memory repo with a deleteById(Long) method and a countByCity method. Run a demo.
    In-memory repo-তে deleteById(Long) এবং countByCity method যোগ করুন, একটি demo চালান।
    ✨ Show Answer
    Main.java
    import 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"));
        }
    }
  3. Which HTTP verb should an "assign role" operation use, and why — POST, PUT, or PATCH?
    একটি "assign role" API-র জন্য কোন HTTP verb ঠিক — POST, PUT, নাকি PATCH? কেন?
    ✨ Show Answer

    Answer: PATCH is the best fit. PUT replaces the entire resource representation, POST creates, and PATCH applies 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.

  4. Write a small validator utility that checks an email string matches x@y.z without any library. Print OK or BAD for three inputs.
    Library ছাড়া একটি ছোট email validator বানান যা x@y.z pattern যাচাই করবে — তিনটি input-এ পরীক্ষা করুন।
    ✨ Show Answer
    Main.java
    class 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"));
            }
        }
    }
  5. 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 Order list and then, for each row, lazily access order.getItems(), JPA issues 1 + N SELECTs — devastating at scale. Fix with a @EntityGraph attribute on the finder, a JOIN FETCH in a JPQL query, or preloading via fetch = 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.

Spring Boot + Spring Data JPA পৃথিবীর সবচেয়ে সাধারণ Java backend stack। Controller, Service, Repository — তিন layer, দায়িত্ব স্পষ্ট। Derived query এবং JpaRepository-র সাহায্যে দিনের বদলে মিনিটে পূর্ণাঙ্গ CRUD API চালু হয়।

Next Module → Algorithmic Problem Solving in Java — LeetCode-ভিত্তিক কৌশল।