Careeroza
Product Entity and Repository
The Entity
package com.shopease.product;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 20)
private String sku;
@Column(nullable = false, length = 120)
private String name;
@Column(length = 2000)
private String description;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
private int stock;
@Column(nullable = false, length = 60)
private String category; // a @ManyToOne Category is shown in the Hibernate & JPA material
protected Product() {} // JPA needs a no-arg constructor
public Product(String sku, String name, String description,
BigDecimal price, int stock, String category) {
this.sku = sku; this.name = name; this.description = description;
this.price = price; this.stock = stock; this.category = category;
}
// getters and setters ...
}Note: JPA entities should be normal classes, not records: Hibernate needs a no-arg constructor and mutable fields. Records are for DTOs.
The Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
boolean existsBySku(String sku);
List<Product> findByCategoryIgnoreCase(String category);
List<Product> findByPriceLessThanEqualOrderByPriceAsc(BigDecimal maxPrice);
Page<Product> findByNameContainingIgnoreCase(String text, Pageable pageable);
}No implementation to write. JpaRepository already gives you save, findById, findAll, deleteById, count, paging and sorting. Extra methods are derived from their names.
How Method Names Become Queries
Example
Spring Data parses the method name at startup. A simplified version of that parser:
import java.util.*;
public class Main {
static String toJpql(String method) {
String rest = method.substring(method.indexOf("By") + 2);
String order = "";
int o = rest.indexOf("OrderBy");
if (o >= 0) {
String ob = rest.substring(o + 7);
String dir = ob.endsWith("Desc") ? " desc" : " asc";
ob = ob.replaceAll("(Asc|Desc)$", "");
order = " order by p." + lower(ob) + dir;
rest = rest.substring(0, o);
}
List<String> conditions = new ArrayList<>();
int param = 1;
for (String part : rest.split("And")) {
String op = "=", field = part;
for (String[] kw : new String[][] {
{"LessThanEqual", "<="}, {"GreaterThanEqual", ">="}, {"LessThan", "<"},
{"GreaterThan", ">"}, {"Containing", "like"}}) {
if (part.endsWith(kw[0])) { op = kw[1]; field = part.substring(0, part.length() - kw[0].length()); break; }
}
String value = op.equals("like") ? "concat('%', ?" + param + ", '%')" : "?" + param;
conditions.add("p." + lower(field) + " " + op + " " + value);
param++;
}
String select = method.startsWith("count") ? "select count(p)" : method.startsWith("exists") ? "select count(p) > 0" : "select p";
return select + " from Product p where " + String.join(" and ", conditions) + order;
}
static String lower(String s) { return Character.toLowerCase(s.charAt(0)) + s.substring(1); }
public static void main(String[] args) {
for (String m : List.of("findByCategory", "existsBySku", "findByCategoryAndPriceLessThan",
"findByNameContaining", "findByPriceLessThanEqualOrderByPriceAsc", "countByStockGreaterThan")) {
System.out.println(m);
System.out.println(" -> " + toJpql(m));
}
}
}Output
findByCategory
-> select p from Product p where p.category = ?1
existsBySku
-> select count(p) > 0 from Product p where p.sku = ?1
findByCategoryAndPriceLessThan
-> select p from Product p where p.category = ?1 and p.price < ?2
findByNameContaining
-> select p from Product p where p.name like concat('%', ?1, '%')
findByPriceLessThanEqualOrderByPriceAsc
-> select p from Product p where p.price <= ?1 order by p.price asc
countByStockGreaterThan
-> select count(p) from Product p where p.stock > ?1Tip: A typo such as findByPrize fails at startup ("No property 'prize' found for type 'Product'"), not at runtime. For complex queries use @Query (Hibernate & JPA material).