From 48c6c068c61ebdb6ae78a4b7f13d822037153cd9 Mon Sep 17 00:00:00 2001 From: asmhatre Date: Sat, 25 Jul 2026 05:36:13 +0000 Subject: [PATCH] Add DemoRunner.java --- .../com/ankurm/sdjpa4demo/DemoRunner.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/main/java/com/ankurm/sdjpa4demo/DemoRunner.java diff --git a/src/main/java/com/ankurm/sdjpa4demo/DemoRunner.java b/src/main/java/com/ankurm/sdjpa4demo/DemoRunner.java new file mode 100644 index 0000000..62e76d9 --- /dev/null +++ b/src/main/java/com/ankurm/sdjpa4demo/DemoRunner.java @@ -0,0 +1,112 @@ +package com.ankurm.sdjpa4demo; + +import com.ankurm.sdjpa4demo.domain.Author; +import com.ankurm.sdjpa4demo.domain.Book; +import com.ankurm.sdjpa4demo.repo.AuthorRepository; +import com.ankurm.sdjpa4demo.repo.BookRepository; +import org.springframework.boot.CommandLineRunner; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.data.domain.Sort; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Optional; + +@Component +@Profile("!test") +public class DemoRunner implements CommandLineRunner { + + private final AuthorRepository authors; + private final BookRepository books; + + public DemoRunner(AuthorRepository authors, BookRepository books) { + this.authors = authors; + this.books = books; + } + + private static void section(String title) { + System.out.println("\n================ " + title + " ================"); + } + + @Override + @Transactional + public void run(String... args) { + Author gof = authors.save(new Author("Erich Gamma", "CH")); + Author bloch = authors.save(new Author("Joshua Bloch", "US")); + Author anon = authors.save(new Author("Anonymous", null)); // null country + authors.save(new Author("Martin Fowler", "UK")); + authors.save(new Author("Kent Beck", "DE")); // bookless: safe to delete in section G + + books.save(new Book("Design Patterns", new BigDecimal("42.00"), gof)); + books.save(new Book("Effective Java", new BigDecimal("45.00"), bloch)); + books.save(new Book("Refactoring", new BigDecimal("45.00"), anon)); // duplicate price 45.00 + + // (A) Optional vs non-null vs @Nullable single-result semantics + section("A. Null-handling of single-result query methods"); + Optional opt = authors.findByName("No Such Author"); + System.out.println("findByName(Optional) missing -> " + opt); + + try { + authors.getByName("No Such Author"); + } catch (EmptyResultDataAccessException ex) { + System.out.println("getByName(non-null) missing -> throws " + ex.getClass().getSimpleName()); + } + + Author byCountry = authors.findByCountry("ZZ"); + System.out.println("findByCountry(@Nullable) missing -> " + byCountry); + + // (B) JSpecify @NullMarked rejects null arguments at runtime + section("B. JSpecify @NullMarked parameter enforcement"); + try { + authors.getByName(null); + } catch (IllegalArgumentException ex) { + System.out.println("getByName(null) -> throws " + ex.getClass().getSimpleName() + + ": " + ex.getMessage()); + } + System.out.println("findByCountry(null) is allowed (@Nullable param) -> " + + authors.findByCountry(null)); + + // (C) NULLS precedence in Sort (consistent in SD JPA 4) + section("C. NULLS FIRST vs NULLS LAST in Sort"); + List nullsFirst = authors.findByNameStartingWith( + "", Sort.by(Sort.Order.asc("country").nullsFirst())); + System.out.println("country ASC NULLS FIRST -> " + + nullsFirst.stream().map(Author::getCountry).toList()); + List nullsLast = authors.findByNameStartingWith( + "", Sort.by(Sort.Order.asc("country").nullsLast())); + System.out.println("country ASC NULLS LAST -> " + + nullsLast.stream().map(Author::getCountry).toList()); + + // (D) Derived query now flows through JPQL (see generated SQL just above in log) + section("D. Derived query -> JPQL path"); + System.out.println("findByNameStartingWith('J') -> " + + authors.findByNameStartingWithOrderByNameAsc("J") + .stream().map(Author::getName).toList()); + + // (E) New JPA 3.2 JPQL: || concatenation + replace() + section("E. New JPQL functions ( || and replace() )"); + System.out.println("badgeFor(gof) -> " + authors.badgeFor(gof.getId())); + System.out.println("badgeFor(anon) -> " + authors.badgeFor(anon.getId())); + + // (F) Single-result that matches multiple rows -> non-unique edge case + section("F. Non-unique single result edge case"); + try { + Book b = books.findByPrice(new BigDecimal("45.00")); + System.out.println("findByPrice(45.00) -> " + b); + } catch (IncorrectResultSizeDataAccessException ex) { + System.out.println("findByPrice(45.00) matches 2 rows -> throws " + + ex.getClass().getSimpleName()); + } + + // (G) Derived DELETE returns affected count + section("G. Derived delete returns count"); + long deleted = authors.deleteByCountry("DE"); + System.out.println("deleteByCountry('DE') removed -> " + deleted + " row(s)"); + + section("DONE"); + } +}