# Spring Data JPA - Code Examples ## Example 1: Simple CRUD Application ### Entity Classes ```java @Entity @Table(name = "categories") public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 100) private String name; @Column(columnDefinition = "TEXT") private String description; @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) private List products = new ArrayList<>(); public Category() {} public Category(String name) { this.name = name; } // getters, setters, equals, hashCode } @Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 255) private String name; @Column(precision = 10, scale = 2) private BigDecimal price; @Column(columnDefinition = "INT DEFAULT 0") private Integer stock; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "category_id", nullable = false) private Category category; @CreatedDate @Column(nullable = false, updatable = false) private LocalDateTime createdAt; @LastModifiedDate private LocalDateTime updatedAt; public Product() {} public Product(String name, BigDecimal price, Category category) { this.name = name; this.price = price; this.category = category; } // getters, setters } ``` ### Repository Interfaces ```java @Repository public interface CategoryRepository extends JpaRepository { Optional findByName(String name); boolean existsByName(String name); } @Repository public interface ProductRepository extends JpaRepository { List findByCategory(Category category); List findByCategoryAndPriceGreaterThan(Category category, BigDecimal price); Page findByNameContainingIgnoreCase(String name, Pageable pageable); @Query("SELECT p FROM Product p WHERE p.stock = 0 ORDER BY p.updatedAt DESC") List findOutOfStockProducts(); @Query("SELECT p FROM Product p WHERE p.price BETWEEN :minPrice AND :maxPrice") List findByPriceRange( @Param("minPrice") BigDecimal minPrice, @Param("maxPrice") BigDecimal maxPrice ); } ``` ### Service Layer ```java @Service @Transactional public class ProductService { private final ProductRepository productRepository; private final CategoryRepository categoryRepository; public ProductService(ProductRepository productRepository, CategoryRepository categoryRepository) { this.productRepository = productRepository; this.categoryRepository = categoryRepository; } @Transactional(readOnly = true) public Page searchProducts(String query, int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by("createdAt").descending()); return productRepository.findByNameContainingIgnoreCase(query, pageable); } @Transactional(readOnly = true) public List getProductsByCategory(Long categoryId) { Category category = categoryRepository.findById(categoryId) .orElseThrow(() -> new CategoryNotFoundException(categoryId)); return productRepository.findByCategory(category); } @Transactional(readOnly = true) public List getExpensiveProducts(Long categoryId, BigDecimal minPrice) { Category category = categoryRepository.findById(categoryId) .orElseThrow(() -> new CategoryNotFoundException(categoryId)); return productRepository.findByCategoryAndPriceGreaterThan(category, minPrice); } public Product createProduct(CreateProductRequest request) { Category category = categoryRepository.findById(request.categoryId()) .orElseThrow(() -> new CategoryNotFoundException(request.categoryId())); Product product = new Product(request.name(), request.price(), category); return productRepository.save(product); } public Product updateProduct(Long id, UpdateProductRequest request) { Product product = productRepository.findById(id) .orElseThrow(() -> new ProductNotFoundException(id)); product.setName(request.name()); product.setPrice(request.price()); return productRepository.save(product); } public void deleteProduct(Long id) { productRepository.deleteById(id); } @Transactional(readOnly = true) public List getOutOfStockProducts() { return productRepository.findOutOfStockProducts(); } } record CreateProductRequest(String name, BigDecimal price, Long categoryId) {} record UpdateProductRequest(String name, BigDecimal price) {} ``` ## Example 2: Complex Query with Auditing ```java @Entity @Table(name = "orders") @EntityListeners(AuditingEntityListener.class) public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderNumber; private String status; // PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED @Column(precision = 12, scale = 2) private BigDecimal totalAmount; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id", nullable = false) private Customer customer; @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) private List items = new ArrayList<>(); @CreatedDate @Column(nullable = false, updatable = false) private LocalDateTime createdAt; @LastModifiedDate private LocalDateTime modifiedAt; @CreatedBy @Column(nullable = false, updatable = false) private String createdBy; @LastModifiedBy private String modifiedBy; // getters, setters, methods } @Entity @Table(name = "order_items") public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id", nullable = false) private Order order; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "product_id", nullable = false) private Product product; private Integer quantity; @Column(precision = 10, scale = 2) private BigDecimal unitPrice; // getters, setters } @Repository public interface OrderRepository extends JpaRepository { @Query(""" SELECT o FROM Order o JOIN FETCH o.items i JOIN FETCH o.customer WHERE o.status = :status ORDER BY o.createdAt DESC """) List findOrdersWithItems(@Param("status") String status); @Query(""" SELECT o FROM Order o WHERE o.customer.id = :customerId AND o.createdAt BETWEEN :startDate AND :endDate """) List findCustomerOrdersByDateRange( @Param("customerId") Long customerId, @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate ); @Query(value = """ SELECT o.id, o.order_number, SUM(oi.quantity * oi.unit_price) as total FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE o.status = :status GROUP BY o.id HAVING total > :minAmount """, nativeQuery = true) List> findHighValueOrdersByStatus( @Param("status") String status, @Param("minAmount") BigDecimal minAmount ); @Modifying @Transactional @Query("UPDATE Order o SET o.status = :newStatus WHERE o.id = :orderId") void updateOrderStatus( @Param("orderId") Long orderId, @Param("newStatus") String newStatus ); @Modifying @Transactional @Query(""" DELETE FROM Order o WHERE o.status = :status AND o.createdAt < :cutoffDate """) int deleteOldOrders( @Param("status") String status, @Param("cutoffDate") LocalDateTime cutoffDate ); } @Service @Transactional public class OrderService { private final OrderRepository orderRepository; @Transactional(readOnly = true) public List getPendingOrders() { return orderRepository.findOrdersWithItems("PENDING"); } @Transactional(readOnly = true) public List getCustomerOrders(Long customerId, LocalDateTime from, LocalDateTime to) { return orderRepository.findCustomerOrdersByDateRange(customerId, from, to); } @Transactional(readOnly = true) public List> getHighValueOrders(BigDecimal minAmount) { return orderRepository.findHighValueOrdersByStatus("COMPLETED", minAmount); } public void processOrder(Long orderId) { orderRepository.updateOrderStatus(orderId, "PROCESSING"); } public int cleanupCancelledOrders(LocalDateTime before) { return orderRepository.deleteOldOrders("CANCELLED", before); } } ``` ## Example 3: Many-to-Many Relationship ```java @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String email; @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinTable( name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id") ) private Set roles = new HashSet<>(); public void addRole(Role role) { this.roles.add(role); role.getUsers().add(this); } public void removeRole(Role role) { this.roles.remove(role); role.getUsers().remove(this); } } @Entity @Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; @ManyToMany(mappedBy = "roles") private Set users = new HashSet<>(); } @Repository public interface UserRepository extends JpaRepository { @Query(""" SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.roles WHERE u.id = :id """) Optional findByIdWithRoles(@Param("id") Long id); @Query(""" SELECT u FROM User u JOIN u.roles r WHERE r.name = :roleName """) List findUsersByRole(@Param("roleName") String roleName); } @Service public class UserManagementService { private final UserRepository userRepository; private final RoleRepository roleRepository; @Transactional public void assignRoleToUser(Long userId, Long roleId) { User user = userRepository.findById(userId) .orElseThrow(() -> new UserNotFoundException(userId)); Role role = roleRepository.findById(roleId) .orElseThrow(() -> new RoleNotFoundException(roleId)); user.addRole(role); userRepository.save(user); } @Transactional public void removeRoleFromUser(Long userId, Long roleId) { User user = userRepository.findByIdWithRoles(userId) .orElseThrow(() -> new UserNotFoundException(userId)); Role role = user.getRoles().stream() .filter(r -> r.getId().equals(roleId)) .findFirst() .orElseThrow(() -> new RoleNotFoundException(roleId)); user.removeRole(role); userRepository.save(user); } } ``` ## Example 4: Pagination with Dynamic Filtering ```java public record ProductFilter( String name, Long categoryId, BigDecimal minPrice, BigDecimal maxPrice, Boolean inStock ) {} @Repository public interface ProductRepository extends JpaRepository, ProductCustomRepository { } public interface ProductCustomRepository { Page findByFilter(ProductFilter filter, Pageable pageable); } @Repository public class ProductCustomRepositoryImpl implements ProductCustomRepository { @PersistenceContext private EntityManager entityManager; @Override public Page findByFilter(ProductFilter filter, Pageable pageable) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Product.class); Root root = cq.from(Product.class); List predicates = new ArrayList<>(); if (filter.name() != null) { predicates.add(cb.like( cb.lower(root.get("name")), "%" + filter.name().toLowerCase() + "%" )); } if (filter.categoryId() != null) { predicates.add(cb.equal(root.get("category").get("id"), filter.categoryId())); } if (filter.minPrice() != null) { predicates.add(cb.greaterThanOrEqualTo(root.get("price"), filter.minPrice())); } if (filter.maxPrice() != null) { predicates.add(cb.lessThanOrEqualTo(root.get("price"), filter.maxPrice())); } if (filter.inStock() != null && filter.inStock()) { predicates.add(cb.greaterThan(root.get("stock"), 0)); } cq.where(cb.and(predicates.toArray(new Predicate[0]))); cq.orderBy(cb.desc(root.get("createdAt"))); TypedQuery query = entityManager.createQuery(cq); query.setFirstResult((int) pageable.getOffset()); query.setMaxResults(pageable.getPageSize()); List results = query.getResultList(); long total = getTotal(filter); return new PageImpl<>(results, pageable, total); } private long getTotal(ProductFilter filter) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Long.class); Root root = cq.from(Product.class); List predicates = new ArrayList<>(); // Add same predicates as above cq.select(cb.count(root)); cq.where(cb.and(predicates.toArray(new Predicate[0]))); return entityManager.createQuery(cq).getSingleResult(); } } @Service public class ProductSearchService { private final ProductRepository productRepository; @Transactional(readOnly = true) public Page search(ProductFilter filter, int page, int size) { Sort sort = Sort.by("createdAt").descending(); Pageable pageable = PageRequest.of(page, size, sort); return productRepository.findByFilter(filter, pageable); } } ``` ## Example 5: Batch Operations ```java @Service @Transactional public class BatchOperationService { private final ProductRepository productRepository; private static final int BATCH_SIZE = 50; public void importProducts(List products) { for (int i = 0; i < products.size(); i++) { Product product = mapToEntity(products.get(i)); productRepository.save(product); if ((i + 1) % BATCH_SIZE == 0) { productRepository.flush(); } } } public void importProductsBatch(List products) { List entities = products.stream() .map(this::mapToEntity) .collect(Collectors.toList()); productRepository.saveAll(entities); productRepository.flush(); } public long deleteOldProducts(LocalDateTime cutoffDate) { return productRepository.deleteByCreatedAtBefore(cutoffDate); } public int bulkUpdatePrices(List productIds, BigDecimal newPrice) { return productRepository.updatePriceForIds(productIds, newPrice); } private Product mapToEntity(ProductDTO dto) { Product product = new Product(); product.setName(dto.name()); product.setPrice(dto.price()); return product; } } @Repository public interface ProductRepository extends JpaRepository { long deleteByCreatedAtBefore(LocalDateTime date); @Modifying @Transactional @Query(""" UPDATE Product p SET p.price = :newPrice WHERE p.id IN :ids """) int updatePriceForIds( @Param("ids") List ids, @Param("newPrice") BigDecimal newPrice ); } ``` ## Example 6: Entity Graph for Eager Loading ```java @Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private String content; @ManyToOne(fetch = FetchType.LAZY) private User author; @OneToMany(mappedBy = "post", fetch = FetchType.LAZY) private List comments = new ArrayList<>(); } @Entity public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String text; @ManyToOne(fetch = FetchType.LAZY) private Post post; @ManyToOne(fetch = FetchType.LAZY) private User author; } @Repository public interface PostRepository extends JpaRepository { // Using EntityGraph annotation @EntityGraph(attributePaths = {"author", "comments"}) Optional findById(Long id); @EntityGraph(attributePaths = {"author", "comments", "comments.author"}) List findAll(); // Using @Query with JOIN FETCH @Query(""" SELECT DISTINCT p FROM Post p JOIN FETCH p.author JOIN FETCH p.comments c JOIN FETCH c.author WHERE p.id = :id """) Optional findByIdWithDetails(@Param("id") Long id); } @Service @Transactional(readOnly = true) public class PostService { private final PostRepository postRepository; public Post getPostWithComments(Long id) { return postRepository.findById(id) .orElseThrow(() -> new PostNotFoundException(id)); } } ``` ## Example 7: Transaction Propagation ```java @Service public class OrderProcessingService { private final OrderRepository orderRepository; private final PaymentService paymentService; private final InventoryService inventoryService; @Transactional public void processOrder(Long orderId) throws PaymentException { Order order = orderRepository.findById(orderId) .orElseThrow(); try { processPayment(order); updateInventory(order); order.setStatus("COMPLETED"); orderRepository.save(order); } catch (PaymentException e) { order.setStatus("PAYMENT_FAILED"); orderRepository.save(order); throw e; } } @Transactional(propagation = Propagation.REQUIRED) private void processPayment(Order order) throws PaymentException { paymentService.charge(order.getCustomer(), order.getTotalAmount()); } @Transactional(propagation = Propagation.REQUIRES_NEW) private void updateInventory(Order order) { order.getItems().forEach(item -> { inventoryService.decreaseStock(item.getProduct().getId(), item.getQuantity()); }); } } ``` ## Example 8: UUID Primary Keys ```java @Entity @Table(name = "articles", indexes = { @Index(name = "idx_author_created", columnList = "author_id, created_date DESC"), @Index(name = "idx_status", columnList = "status") }) public class Article { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; private String title; private String content; private String status; // DRAFT, PUBLISHED private LocalDateTime createdDate; private LocalDateTime publishedDate; @ManyToOne private User author; } @Entity @Table(name = "users", indexes = { @Index(name = "idx_email", columnList = "email", unique = true), @Index(name = "idx_username", columnList = "username", unique = true) }) public class User { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; @Column(unique = true, length = 100) private String email; @Column(unique = true, length = 100) private String username; private String firstName; private String lastName; } @Repository public interface ArticleRepository extends JpaRepository { // Indexes support these queries efficiently List
findByStatusOrderByPublishedDateDesc(String status); List
findByAuthorOrderByCreatedDateDesc(User author); Page
findByStatusAndPublishedDateAfter( String status, LocalDateTime date, Pageable pageable ); } @Repository public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Optional findByUsername(String username); } @Service @Transactional public class ArticleService { private final ArticleRepository articleRepository; private final UserRepository userRepository; public Article createArticle(CreateArticleRequest request, UUID authorId) { User author = userRepository.findById(authorId) .orElseThrow(() -> new UserNotFoundException(authorId)); Article article = new Article(); article.setTitle(request.title()); article.setContent(request.content()); article.setStatus("DRAFT"); article.setCreatedDate(LocalDateTime.now()); article.setAuthor(author); return articleRepository.save(article); // UUID generated automatically } @Transactional(readOnly = true) public Page
getPublishedArticles(int page, int size) { Pageable pageable = PageRequest.of(page, size, Sort.by("publishedDate").descending()); return articleRepository.findByStatusAndPublishedDateAfter( "PUBLISHED", LocalDateTime.now().minusDays(30), pageable ); } @Transactional(readOnly = true) public Article getArticle(UUID id) { return articleRepository.findById(id) .orElseThrow(() -> new ArticleNotFoundException(id)); } } record CreateArticleRequest(String title, String content) {} ``` ## Example 9: Index Optimization ```java @Entity @Table(name = "orders", indexes = { // Single column index @Index(name = "idx_status", columnList = "status"), // Composite index for common query pattern @Index(name = "idx_customer_date", columnList = "customer_id, created_date DESC"), // For date range queries @Index(name = "idx_created_date", columnList = "created_date DESC"), // Unique index @Index(name = "idx_order_number", columnList = "order_number", unique = true), // Multi-column ordering @Index(name = "idx_status_amount", columnList = "status ASC, total_amount DESC") }) public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, length = 50) private String orderNumber; @Column(length = 20) private String status; // PENDING, PROCESSING, SHIPPED, DELIVERED private LocalDateTime createdDate; @Column(precision = 12, scale = 2) private BigDecimal totalAmount; @ManyToOne @JoinColumn(name = "customer_id") private Customer customer; } @Entity @Table(name = "order_items", indexes = { // Index on foreign key for JOIN performance @Index(name = "idx_order_id", columnList = "order_id"), // Composite index for finding items by order and status @Index(name = "idx_order_status", columnList = "order_id, status"), // Index on product foreign key @Index(name = "idx_product_id", columnList = "product_id") }) public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "order_id") private Order order; @ManyToOne @JoinColumn(name = "product_id") private Product product; private Integer quantity; private String status; } @Entity @Table(name = "customers", indexes = { @Index(name = "idx_email", columnList = "email", unique = true), @Index(name = "idx_country_city", columnList = "country, city") }) public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; @Column(unique = true) private String email; private String country; private String city; } @Repository public interface OrderRepository extends JpaRepository { // Uses idx_status List findByStatus(String status); // Uses idx_customer_date List findByCustomerOrderByCreatedDateDesc( Customer customer, Pageable pageable ); // Uses idx_created_date Page findByCreatedDateAfterOrderByCreatedDateDesc( LocalDateTime date, Pageable pageable ); // Uses idx_status_amount List findByStatusOrderByTotalAmountDesc(String status); // Custom query using indexes @Query(""" SELECT o FROM Order o WHERE o.status = :status AND o.createdDate BETWEEN :start AND :end ORDER BY o.totalAmount DESC """) List findHighValueOrdersInPeriod( @Param("status") String status, @Param("start") LocalDateTime start, @Param("end") LocalDateTime end ); } @Repository public interface CustomerRepository extends JpaRepository { // Uses idx_email Optional findByEmail(String email); // Uses idx_country_city List findByCountryAndCity(String country, String city); } @Service @Transactional(readOnly = true) public class OrderAnalyticsService { private final OrderRepository orderRepository; public Page getRecentOrders(int page, int size) { LocalDateTime weekAgo = LocalDateTime.now().minusDays(7); Pageable pageable = PageRequest.of(page, size); return orderRepository.findByCreatedDateAfterOrderByCreatedDateDesc( weekAgo, pageable ); // Uses idx_created_date } public List getPendingOrders() { return orderRepository.findByStatus("PENDING"); // Uses idx_status } public List getHighValueOrders(LocalDateTime from, LocalDateTime to) { return orderRepository.findHighValueOrdersInPeriod( "COMPLETED", from, to ); // Uses idx_status_amount } @Transactional public void optimizeIndexUsage() { // These queries benefit from composite indexes List customerOrders = orderRepository.findByCustomerOrderByCreatedDateDesc( new Customer(), PageRequest.of(0, 50) ); // Uses idx_customer_date } } ``` These examples demonstrate real-world usage patterns for Spring Data JPA, from simple CRUD operations to complex scenarios involving relationships, auditing, pagination, batch processing, UUID keys, and index optimization.