Real World Example

ShopEase Product Catalogue

The catalogue service behind a Myntra- or Flipkart-style product listing: search, category pages, new arrivals, and a low-stock report for the operations team.

public interface ProductRepository extends JpaRepository<Product, Long> {
    Optional<Product> findBySku(String sku);
    boolean existsBySku(String sku);
    List<Product> findByCategoryAndStatusOrderByPriceAsc(String category, ProductStatus status);
    List<Product> findByNameContainingIgnoreCaseAndStatus(String text, ProductStatus status);
    List<Product> findTop8ByStatusOrderByCreatedAtDesc(ProductStatus status);
    List<Product> findByStockLessThanEqualAndStatus(int threshold, ProductStatus status);
}

public record ProductResponse(Long id, String name, BigDecimal price, boolean inStock) {
    static ProductResponse from(Product p) {
        return new ProductResponse(p.getId(), p.getName(), p.getPrice(), p.isInStock());
    }
}

@Service
@Transactional(readOnly = true)
public class CatalogService {
    private final ProductRepository repo;
    public CatalogService(ProductRepository repo) { this.repo = repo; }

    public List<ProductResponse> category(String category) {
        return repo.findByCategoryAndStatusOrderByPriceAsc(category, ProductStatus.ACTIVE)
                   .stream().map(ProductResponse::from).toList();
    }

    public List<ProductResponse> search(String q) {
        return repo.findByNameContainingIgnoreCaseAndStatus(q.trim(), ProductStatus.ACTIVE)
                   .stream().map(ProductResponse::from).toList();
    }

    public List<ProductResponse> newArrivals() {
        return repo.findTop8ByStatusOrderByCreatedAtDesc(ProductStatus.ACTIVE)
                   .stream().map(ProductResponse::from).toList();
    }

    @Transactional
    public ProductResponse create(CreateProductRequest req) {
        if (repo.existsBySku(req.sku())) throw new DuplicateSkuException(req.sku());
        Product saved = repo.save(new Product(req.sku(), req.name(), req.price(), req.stock(), req.category()));
        return ProductResponse.from(saved);
    }
}

Request and SQL

Category page

Opening the Electronics category page:

curl localhost:8080/api/catalog/categories/Electronics

Example output

Hibernate: select p1_0.id,p1_0.category,p1_0.created_at,p1_0.description,p1_0.name,p1_0.price,p1_0.sku,p1_0.status,p1_0.stock from products p1_0 where p1_0.category=? and p1_0.status=? order by p1_0.price

HTTP/1.1 200 OK
[{"id":7,"name":"USB-C Cable","price":199.00,"inStock":true},
 {"id":57,"name":"Boat Airdopes 141","price":1099.00,"inStock":true},
 {"id":42,"name":"Boat Rockerz 450","price":1499.00,"inStock":false}]
  • DTOs in the response: entities stay inside the service; the API returns records.
  • readOnly = true on the class: Hibernate skips dirty checking for reads. Only create is a read-write transaction.
  • No SQL written by hand: every query is derived from a method name and logged for review.

Tip: Next steps (medium level): turn category into a @ManyToOne Category, add pagination with Pageable for long category pages, and write complex filters with @Query and Specifications.