Add DemoRunner.java

This commit is contained in:
2026-07-25 05:36:13 +00:00
parent 36c678ee80
commit 48c6c068c6

View File

@@ -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<Author> 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<Author> nullsFirst = authors.findByNameStartingWith(
"", Sort.by(Sort.Order.asc("country").nullsFirst()));
System.out.println("country ASC NULLS FIRST -> "
+ nullsFirst.stream().map(Author::getCountry).toList());
List<Author> 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");
}
}