From c6f8dee0050a55f409b7fcc3a45771aac9e777e1 Mon Sep 17 00:00:00 2001 From: Nano Taboada Date: Wed, 3 Dec 2025 13:05:01 -0300 Subject: [PATCH 1/2] feat: migrate database from H2 to SQLite - Replace H2 with Xerial SQLite JDBC driver and hibernate-community-dialects. - Keep H2 in test scope only for fast in-memory test execution. - Store dates as Unix timestamps with UnixTimestampConverter for robustness. - Pre-seed the SQLite database with sample books. - Implement Docker hold pattern for DB persistence across restarts. - Remove BooksDataInitializer since seeding is handled by the pre-seeded DB. - Update Dockerfile, compose.yaml, and entrypoint.sh for SQLite support. --- .github/copilot-instructions.md | 43 ++- Dockerfile | 14 +- README.md | 25 +- compose.yaml | 7 + pom.xml | 30 +- scripts/entrypoint.sh | 26 +- .../java/samples/spring/boot/Application.java | 23 -- .../java/samples/spring/boot/models/Book.java | 24 +- .../boot/models/BooksDataInitializer.java | 286 ------------------ .../boot/models/UnixTimestampConverter.java | 40 +++ .../boot/repositories/BooksRepository.java | 10 +- src/main/resources/application.properties | 12 +- src/main/resources/logback-spring.xml | 14 +- src/test/resources/application.properties | 14 + storage/books-sqlite3.db | Bin 0 -> 53248 bytes 15 files changed, 216 insertions(+), 352 deletions(-) delete mode 100644 src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/BooksDataInitializer.java create mode 100644 src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java create mode 100644 src/test/resources/application.properties create mode 100644 storage/books-sqlite3.db diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 41281d1..fed4ea2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,7 +2,7 @@ ## Project Overview -This is a RESTful Web Service proof-of-concept built with **Spring Boot 4** targeting **JDK 25 (LTS)**. The application demonstrates a clean, layered architecture implementing a CRUD API for managing books. It uses an in-memory H2 database for data persistence and includes comprehensive test coverage. +This is a RESTful Web Service proof-of-concept built with **Spring Boot 4** targeting **JDK 25 (LTS)**. The application demonstrates a clean, layered architecture implementing a CRUD API for managing books. It uses a **SQLite database** for runtime persistence (with a pre-seeded database in Docker) and **H2 in-memory** for fast test execution. **Key URLs:** @@ -17,7 +17,7 @@ This is a RESTful Web Service proof-of-concept built with **Spring Boot 4** targ - **Java**: JDK 25 (LTS) - use modern Java features where appropriate - **Spring Boot**: 4.0.0 with modular starter dependencies (WebMVC, Data JPA, Validation, Cache, Actuator) - **Build Tool**: Maven 3.9+ (use `./mvnw` wrapper, NOT system Maven) -- **Database**: H2 in-memory database (runtime scope) +- **Database**: SQLite (runtime) with Xerial JDBC driver; H2 in-memory (test scope only) ### Key Dependencies @@ -26,6 +26,8 @@ This is a RESTful Web Service proof-of-concept built with **Spring Boot 4** targ - **SpringDoc OpenAPI** 2.8.14: API documentation (Swagger UI) - **JaCoCo** 0.8.14: Code coverage reporting - **AssertJ** 3.27.6: Fluent test assertions +- **SQLite JDBC** 3.47.1.0: SQLite database driver (Xerial) +- **Hibernate Community Dialects**: Provides `SQLiteDialect` for JPA/Hibernate ### Testing @@ -36,8 +38,8 @@ This is a RESTful Web Service proof-of-concept built with **Spring Boot 4** targ ### DevOps & CI/CD -- **Docker**: Multi-stage build with Eclipse Temurin Alpine images -- **Docker Compose**: Local containerized deployment +- **Docker**: Multi-stage build with Eclipse Temurin Alpine images and pre-seeded SQLite database +- **Docker Compose**: Local containerized deployment with persistent storage volume - **GitHub Actions**: CI pipeline with coverage reporting (Codecov, Codacy) ## Project Structure @@ -54,7 +56,7 @@ src/main/java/ar/com/nanotaboada/java/samples/spring/boot/ └── models/ # Domain entities & DTOs ├── Book.java # JPA entity ├── BookDTO.java # Data Transfer Object with validation - └── BooksDataInitializer.java # Seed data + └── UnixTimestampConverter.java # JPA converter for LocalDate ↔ Unix timestamp src/test/java/.../test/ ├── controllers/ # Controller tests (@WebMvcTest) @@ -64,12 +66,18 @@ src/test/java/.../test/ └── BookDTOFakes.java # Test data factory for BookDTO src/main/resources/ -├── application.properties # Application configuration +├── application.properties # Application configuration (SQLite) └── logback-spring.xml # Logging configuration +src/test/resources/ +└── application.properties # Test configuration (H2 in-memory) + scripts/ -├── entrypoint.sh # Docker container entrypoint +├── entrypoint.sh # Docker entrypoint (copies seed DB on first run) └── healthcheck.sh # Docker health check using Actuator + +storage/ +└── books-sqlite3.db # Pre-seeded SQLite database with sample books ``` **Package Naming Convention**: `ar.com.nanotaboada.java.samples.spring.boot.` @@ -164,6 +172,15 @@ docker compose logs -f - `9000`: Main API server - `9001`: Actuator management endpoints +**Persistent Storage**: + +The Docker container uses a "hold" pattern for the pre-seeded SQLite database: + +1. Build stage copies `storage/books-sqlite3.db` to `/app/hold/` in the image +2. On first container run, `entrypoint.sh` copies the database to `/storage/` volume +3. Subsequent runs use the existing database from the volume +4. To reset: `docker compose down -v` removes volumes, next `up` restores seed data + ## Common Tasks & Patterns ### Adding a New REST Endpoint @@ -203,15 +220,18 @@ docker compose logs -f ### Build Failures - **Lombok not working**: Ensure annotation processor is enabled in IDE and `maven-compiler-plugin` includes Lombok path -- **Tests failing**: Check if H2 database is properly initialized; review `BooksDataInitializer.seed()` +- **Tests failing**: Tests use H2 in-memory database via `src/test/resources/application.properties` - **Port already in use**: Change `server.port` in `application.properties` or kill process using ports 9000/9001 - **JAVA_HOME not set**: Run `export JAVA_HOME=$(/usr/libexec/java_home -v 25)` on macOS or set to JDK 25 path on other systems - **CacheManager errors in tests**: Add `@AutoConfigureCache` annotation to slice tests (`@WebMvcTest`, `@DataJpaTest`) +- **SQLite file not found**: Ensure `storage/books-sqlite3.db` exists for local development ### Docker Issues - **Container health check failing**: Verify Actuator is accessible at `http://localhost:9001/actuator/health` - **Build context too large**: Ensure `.dockerignore` excludes `target/` and `.git/` +- **Database not persisting**: Check that `java-samples-spring-boot_storage` volume exists (`docker volume ls`) +- **Stale seed data**: Run `docker compose down -v` to remove volumes and restore fresh seed data on next `up` ### Common Pitfalls @@ -221,6 +241,13 @@ docker compose logs -f - **Repository interfaces**: Custom query methods may not show in coverage (JaCoCo limitation) - **Spring Boot 4.0 modular packages**: Test annotations like `@WebMvcTest`, `@DataJpaTest`, and `@AutoConfigureCache` are now in modular packages (e.g., `org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest`) +### SQLite Configuration Notes + +- **Date storage**: Dates are stored as Unix timestamps (INTEGER) for robustness - no parsing issues +- **Converter**: `UnixTimestampConverter` handles LocalDate ↔ epoch seconds conversion via JPA `@Convert` +- **DDL auto**: Use `ddl-auto=none` since the database is pre-seeded (SQLite has limited ALTER TABLE support) +- **Tests use H2**: The converter works seamlessly with both H2 and SQLite databases + ## CI/CD Pipeline ### GitHub Actions Workflow (`.github/workflows/maven.yml`) diff --git a/Dockerfile b/Dockerfile index 0a083b1..b3a8b2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,9 +45,17 @@ COPY --chmod=555 assets/ ./assets/ COPY --chmod=555 scripts/entrypoint.sh ./entrypoint.sh COPY --chmod=555 scripts/healthcheck.sh ./healthcheck.sh -# Add system user -RUN addgroup -S spring && \ - adduser -S -G spring spring +# The 'hold' is our storage compartment within the image. Here, we copy a +# pre-seeded SQLite database file, which Compose will mount as a persistent +# 'storage' volume when the container starts up. +COPY --chmod=555 storage/ ./hold/ + +# Install SQLite runtime libs, add non-root user and prepare volume mount point +RUN apk add --no-cache sqlite-libs && \ + addgroup -S spring && \ + adduser -S -G spring spring && \ + mkdir -p /storage && \ + chown -R spring:spring /storage USER spring diff --git a/README.md b/README.md index 66d32bd..e62a7df 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ A proof-of-concept RESTful Web Service built with **Spring Boot 4** targeting ** The service showcases: - Multi-layer architecture (Controllers → Services → Repositories) -- In-memory H2 database with JPA/Hibernate +- SQLite database with JPA/Hibernate (H2 for tests) - Spring Cache abstraction for performance optimization - Comprehensive test coverage with JUnit 5, Mockito, and AssertJ - OpenAPI 3.0 documentation with Swagger UI - Production-ready monitoring with Spring Boot Actuator -- Containerized deployment with Docker +- Containerized deployment with Docker and persistent storage ## Features @@ -47,7 +47,7 @@ The service showcases: - ✅ **API Documentation** - Interactive Swagger UI powered by SpringDoc OpenAPI - ✅ **Health Monitoring** - Spring Boot Actuator endpoints - ✅ **Test Coverage** - JaCoCo reports with Codecov/Codacy integration -- ✅ **Docker Support** - Multi-stage builds with Eclipse Temurin Alpine images +- ✅ **Docker Support** - Multi-stage builds with pre-seeded SQLite database - ✅ **CI/CD Ready** - GitHub Actions with automated testing and container builds ## Architecture @@ -130,6 +130,17 @@ docker compose down - `9000` - Main API server - `9001` - Actuator management endpoints +**Persistent Storage:** + +The Docker container uses a pre-seeded SQLite database with sample book data. On first run, the database is copied from the image to a named volume (`java-samples-spring-boot_storage`) ensuring data persistence across container restarts. + +To reset the database to its initial state: + +```bash +docker compose down -v # Remove volumes +docker compose up # Fresh start with seed data +``` + ## API Reference The Books API provides standard CRUD operations: @@ -180,6 +191,7 @@ open target/site/jacoco/index.html **Test Structure:** - **Unit Tests** - `@WebMvcTest`, `@DataJpaTest` for isolated layer testing (with `@AutoConfigureCache` for caching support) +- **Test Database** - H2 in-memory database for fast, isolated test execution - **Mocking** - Mockito with `@MockitoBean` for dependency mocking - **Assertions** - AssertJ fluent assertions - **Naming Convention** - BDD style: `given_when_then` @@ -190,6 +202,13 @@ open target/site/jacoco/index.html - Services: 100% - Repositories: Custom query methods (interfaces excluded by JaCoCo design) +**SQLite Configuration Notes:** + +- Dates are stored as Unix timestamps (INTEGER) for robustness - no date format parsing issues +- A JPA `AttributeConverter` handles LocalDate ↔ epoch seconds conversion transparently +- Use `ddl-auto=none` since the database is pre-seeded (SQLite has limited ALTER TABLE support) +- Tests use H2 in-memory database - the converter works seamlessly with both databases + ## Documentation - **API Documentation**: Swagger UI at `http://localhost:9000/swagger/index.html` diff --git a/compose.yaml b/compose.yaml index 2626fe3..f62596c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,6 +10,13 @@ services: ports: - "9000:9000" - "9001:9001" + volumes: + - storage:/storage/ environment: - SPRING_PROFILES_ACTIVE=production + - STORAGE_PATH=/storage/books-sqlite3.db restart: unless-stopped + +volumes: + storage: + name: java-samples-spring-boot_storage diff --git a/pom.xml b/pom.xml index 479447c..327dc7b 100644 --- a/pom.xml +++ b/pom.xml @@ -146,18 +146,40 @@ spring-boot-starter-data-jpa-test test + + + + org.xerial + sqlite-jdbc + 3.47.1.0 + runtime + + + + + org.hibernate.orm + hibernate-community-dialects + com.h2database h2 2.4.240 - runtime + test diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index af8ed35..cf32c7c 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,11 +1,27 @@ #!/bin/sh set -e -echo "✔ Starting Spring Boot container..." +echo "✔ Executing entrypoint script..." -echo "✔ Server Port: ${SERVER_PORT:-9000}" -echo "✔ Management Port: ${MANAGEMENT_PORT:-9001}" -echo "✔ Active Profile(s): ${SPRING_PROFILES_ACTIVE:-default}" +IMAGE_STORAGE_PATH="/app/hold/books-sqlite3.db" +VOLUME_STORAGE_PATH="/storage/books-sqlite3.db" -echo "🚀 Launching Spring Boot app..." +echo "✔ Starting container..." + +if [ ! -f "$VOLUME_STORAGE_PATH" ]; then + echo "⚠️ No existing database file found in volume." + if [ -f "$IMAGE_STORAGE_PATH" ]; then + echo "Copying database file to writable volume..." + cp "$IMAGE_STORAGE_PATH" "$VOLUME_STORAGE_PATH" + echo "✔ Database initialized at $VOLUME_STORAGE_PATH" + else + echo "⚠️ Database file missing at $IMAGE_STORAGE_PATH" + exit 1 + fi +else + echo "✔ Existing database file found. Skipping seed copy." +fi + +echo "✔ Ready!" +echo "🚀 Launching app..." exec "$@" diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/Application.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/Application.java index 2f0aef0..66dcb4f 100644 --- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/Application.java +++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/Application.java @@ -1,18 +1,11 @@ package ar.com.nanotaboada.java.samples.spring.boot; -import java.util.List; - import org.modelmapper.ModelMapper; -import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; -import ar.com.nanotaboada.java.samples.spring.boot.models.Book; -import ar.com.nanotaboada.java.samples.spring.boot.models.BooksDataInitializer; -import ar.com.nanotaboada.java.samples.spring.boot.repositories.BooksRepository; - /** * A configuration class that declares one or more Bean methods and also * triggers auto-configuration and component scanning. @@ -21,27 +14,11 @@ @EnableCaching public class Application { - private final BooksRepository repository; - - public Application(BooksRepository repository) { - this.repository = repository; - } - @Bean ModelMapper modelMapper() { return new ModelMapper(); } - @Bean - CommandLineRunner seed() { - return args -> { - List books = BooksDataInitializer.seed(); - if (books != null) { - repository.saveAll(books); - } - }; - } - public static void main(String[] args) { SpringApplication.run(Application.class, args); } diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Book.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Book.java index 9724e83..8e96980 100644 --- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Book.java +++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/Book.java @@ -2,19 +2,19 @@ import java.time.LocalDate; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Lob; -import jakarta.persistence.Table; - import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; @Entity @Table(name = "books") @@ -28,11 +28,21 @@ public class Book { private String subtitle; private String author; private String publisher; + /** + * Stored as Unix timestamp (INTEGER) in SQLite for robustness. + * The converter handles LocalDate ↔ epoch seconds conversion. + */ @JsonDeserialize(using = LocalDateDeserializer.class) @JsonSerialize(using = LocalDateSerializer.class) + @Convert(converter = UnixTimestampConverter.class) private LocalDate published; private int pages; - @Lob + /** + * Maximum length set to 8192 (8^4 = 2^13) characters. + * This power-of-two value provides ample space for book descriptions + * while remaining compatible with both H2 (VARCHAR) and SQLite (TEXT). + */ + @Column(length = 8192) private String description; private String website; } diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/BooksDataInitializer.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/BooksDataInitializer.java deleted file mode 100644 index 312c239..0000000 --- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/BooksDataInitializer.java +++ /dev/null @@ -1,286 +0,0 @@ -package ar.com.nanotaboada.java.samples.spring.boot.models; - -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; - -public class BooksDataInitializer { - // Utility classes should not have public constructors (java:S1118) - // https://www.baeldung.com/java-sonar-hide-implicit-constructor - private BooksDataInitializer() { - throw new IllegalStateException("Utility class"); - } - - public static List seed() { - ArrayList books = new ArrayList<>(); - Book book9781838986698 = new Book(); - book9781838986698.setIsbn("9781838986698"); - book9781838986698.setTitle("The Java Workshop"); - book9781838986698 - .setSubtitle("Learn object-oriented programming and kickstart your career in software development"); - book9781838986698.setAuthor("David Cuartielles, Andreas Göransson, Eric Foster-Johnson"); - book9781838986698.setPublisher("Packt Publishing"); - book9781838986698.setPublished(LocalDate.of(2019, 10, 31)); - book9781838986698.setPages(606); - book9781838986698.setDescription( - """ - Java is a versatile, popular programming language used across a wide range of \ - industries. Learning how to write effective Java code can take your career to \ - the next level, and The Java Workshop will help you do just that. This book is \ - designed to take the pain out of Java coding and teach you everything you need \ - to know to be productive in building real-world software. The Workshop starts by \ - showing you how to use classes, methods, and the built-in Collections API to \ - manipulate data structures effortlessly. You'll dive right into learning about \ - object-oriented programming by creating classes and interfaces and making use of \ - inheritance and polymorphism. After learning how to handle exceptions, you'll \ - study the modules, packages, and libraries that help you organize your code. As \ - you progress, you'll discover how to connect to external databases and web \ - servers, work with regular expressions, and write unit tests to validate your \ - code. You'll also be introduced to functional programming and see how to \ - implement it using lambda functions. By the end of this Workshop, you'll be \ - well-versed with key Java concepts and have the knowledge and confidence to \ - tackle your own ambitious projects with Java."""); - book9781838986698.setWebsite("https://www.packtpub.com/free-ebook/the-java-workshop/9781838986698"); - books.add(book9781838986698); - Book book9781789613476 = new Book(); - book9781789613476.setIsbn("9781789613476"); - book9781789613476.setTitle("Hands-On Microservices with Spring Boot and Spring Cloud"); - book9781789613476.setSubtitle("Build and deploy Java microservices using Spring Cloud, Istio, and Kubernetes"); - book9781789613476.setAuthor("Magnus Larsson"); - book9781789613476.setPublisher("Packt Publishing"); - book9781789613476.setPublished(LocalDate.of(2019, 9, 20)); - book9781789613476.setPages(668); - book9781789613476.setDescription( - """ - Microservices architecture allows developers to build and maintain applications \ - with ease, and enterprises are rapidly adopting it to build software using \ - Spring Boot as their default framework. With this book, you'll learn how to \ - efficiently build and deploy microservices using Spring Boot. This microservices \ - book will take you through tried and tested approaches to building distributed \ - systems and implementing microservices architecture in your organization. \ - Starting with a set of simple cooperating microservices developed using Spring \ - Boot, you'll learn how you can add functionalities such as persistence, make \ - your microservices reactive, and describe their APIs using Swagger/OpenAPI. As \ - you advance, you'll understand how to add different services from Spring Cloud \ - to your microservice system. The book also demonstrates how to deploy your \ - microservices using Kubernetes and manage them with Istio for improved security \ - and traffic management. Finally, you'll explore centralized log management using \ - the EFK stack and monitor microservices using Prometheus and Grafana. By the end \ - of this book, you'll be able to build microservices that are scalable and robust \ - using Spring Boot and Spring Cloud."""); - book9781789613476.setWebsite( - "https://www.packtpub.com/free-ebook/hands-on-microservices-with-spring-boot-and-spring-cloud/9781789613476"); - books.add(book9781789613476); - Book book9781838555726 = new Book(); - book9781838555726.setIsbn("9781838555726"); - book9781838555726.setTitle("Mastering Kotlin"); - book9781838555726.setSubtitle( - "Learn advanced Kotlin programming techniques to build apps for Android, iOS, and the web"); - book9781838555726.setAuthor("Nate Ebel"); - book9781838555726.setPublisher("Packt Publishing"); - book9781838555726.setPublished(LocalDate.of(2019, 10, 11)); - book9781838555726.setPages(434); - book9781838555726.setDescription( - """ - Using Kotlin without taking advantage of its power and interoperability is like \ - owning a sports car and never taking it out of the garage. While documentation \ - and introductory resources can help you learn the basics of Kotlin, the fact \ - that it's a new language means that there are limited learning resources and \ - code bases available in comparison to Java and other established languages. This \ - Kotlin book will show you how to leverage software designs and concepts that \ - have made Java the most dominant enterprise programming language. You'll \ - understand how Kotlin is a modern approach to object-oriented programming (OOP). \ - This book will take you through the vast array of features that Kotlin provides \ - over other languages. These features include seamless interoperability with \ - Java, efficient syntax, built-in functional programming constructs, and support \ - for creating your own DSL. Finally, you will gain an understanding of \ - implementing practical design patterns and best practices to help you master the \ - Kotlin language. By the end of the book, you'll have obtained an advanced \ - understanding of Kotlin in order to be able to build production-grade \ - applications."""); - book9781838555726.setWebsite("https://www.packtpub.com/free-ebook/mastering-kotlin/9781838555726"); - books.add(book9781838555726); - Book book9781484242216 = new Book(); - book9781484242216.setIsbn("9781484242216"); - book9781484242216.setTitle("Rethinking Productivity in Software Engineering"); - book9781484242216.setAuthor("Caitlin Sadowski, Thomas Zimmermann"); - book9781484242216.setPublisher("Apress"); - book9781484242216.setPublished(LocalDate.of(2019, 5, 7)); - book9781484242216.setPages(301); - book9781484242216.setDescription( - """ - Get the most out of this foundational reference and improve the productivity of \ - your software teams. This open access book collects the wisdom of the 2017 \ - "Dagstuhl" seminar on productivity in software engineering, a meeting of \ - community leaders, who came together with the goal of rethinking traditional \ - definitions and measures of productivity."""); - book9781484242216.setWebsite("https://link.springer.com/book/10.1007/978-1-4842-4221-6"); - books.add(book9781484242216); - Book book9781642002232 = new Book(); - book9781642002232.setIsbn("9781642002232"); - book9781642002232.setTitle("Database Design Succinctly"); - book9781642002232.setAuthor("Joseph D. Booth"); - book9781642002232.setPublisher("Syncfusion"); - book9781642002232.setPublished(LocalDate.of(2022, 5, 25)); - book9781642002232.setPages(87); - book9781642002232.setDescription( - """ - The way a user might perceive and use data and the optimal way a computer \ - system might store it are often very different. In this Database Design \ - Succinctly®, learn how to model the user's information into data in a computer \ - database system in such a way as to allow the system to produce useful results \ - for the end user. Joseph D. Booth will cover how to design a database system to \ - allow businesses to get better reporting and control over their information, as \ - well as how to improve their data to make sure it is as accurate as possible."""); - book9781642002232.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/database-design-succinctly"); - books.add(book9781642002232); - Book book9781642001174 = new Book(); - book9781642001174.setIsbn("9781642001174"); - book9781642001174.setTitle("SOLID Principles Succinctly"); - book9781642001174.setAuthor("Gaurav Kumar Arora"); - book9781642001174.setPublisher("Syncfusion"); - book9781642001174.setPublished(LocalDate.of(2016, 10, 31)); - book9781642001174.setPages(78); - book9781642001174.setDescription( - """ - There is always room for improving one's coding ability, and SOLID design \ - principles offer one way to see marked improvements in final output. With SOLID \ - Principles Succinctly®, author Gaurav Kumar Arora will instruct you in how to \ - use SOLID principles to take your programming skills to the next level."""); - book9781642001174.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/solidprinciplessuccinctly"); - books.add(book9781642001174); - Book book9781642001440 = new Book(); - book9781642001440.setIsbn("9781642001440"); - book9781642001440.setTitle("Java Succinctly Part 1"); - book9781642001440.setAuthor("Christopher Rose"); - book9781642001440.setPublisher("Syncfusion"); - book9781642001440.setPublished(LocalDate.of(2017, 8, 29)); - book9781642001440.setPages(125); - book9781642001440.setDescription( - """ - Java is a high-level, cross-platform, object-oriented programming language that \ - allows applications to be written once and run on a multitude of different \ - devices. Java applications are ubiquitous, and the language is consistently \ - ranked as one of the most popular and dominant in the world. Christopher Rose's \ - Java Succinctly® Part 1 describes the foundations of Java—from printing a line \ - of text to the console, to inheritance hierarchies in object-oriented \ - programming. The e-book covers practical aspects of programming, such as \ - debugging and using an IDE, as well as the core mechanics of the language."""); - book9781642001440.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/java-succinctly-part-1"); - books.add(book9781642001440); - Book book9781642001457 = new Book(); - book9781642001457.setIsbn("9781642001457"); - book9781642001457.setTitle("Java Succinctly Part 2"); - book9781642001457.setAuthor("Christopher Rose"); - book9781642001457.setPublisher("Syncfusion"); - book9781642001457.setPublished(LocalDate.of(2017, 9, 5)); - book9781642001457.setPages(134); - book9781642001457.setDescription( - """ - In this second e-book on Java, Christopher Rose takes readers through some of \ - the more advanced features of the language. Java Succinctly® Part 2 explores \ - powerful and practical features of Java, such as multithreading, building GUI \ - applications, and 2-D graphics and game programming. Then learn techniques for \ - using these mechanisms in coherent projects by building a calculator app and a \ - simple game with the author."""); - book9781642001457.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/java-succinctly-part-2"); - books.add(book9781642001457); - Book book9781642001495 = new Book(); - book9781642001495.setIsbn("9781642001495"); - book9781642001495.setTitle("Scala Succinctly"); - book9781642001495.setAuthor("Chris Rose"); - book9781642001495.setPublisher("Syncfusion"); - book9781642001495.setPublished(LocalDate.of(2017, 10, 16)); - book9781642001495.setPages(110); - book9781642001495.setDescription( - """ - Learning a new programming language can be a daunting task, but Scala \ - Succinctly® makes it a simple matter. Author Chris Rose guides readers through \ - the basics of Scala, from installation to syntax shorthand, so that they can \ - get up and running quickly."""); - book9781642001495.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/scala-succinctly"); - books.add(book9781642001495); - Book book9781642001242 = new Book(); - book9781642001242.setIsbn("9781642001242"); - book9781642001242.setTitle("SQL Queries Succinctly"); - book9781642001242.setAuthor("Nick Harrison"); - book9781642001242.setPublisher("Syncfusion"); - book9781642001242.setPublished(LocalDate.of(2017, 2, 4)); - book9781642001242.setPages(102); - book9781642001242.setDescription( - """ - SQL is the language of data, and therefore the intermediary language for those \ - who straddle the line between technology and business. Every business \ - application needs a database and SQL is the key to working with these databases. \ - Nick Harrison's SQL Queries Succinctly® will show you how to craft queries in \ - SQL, from basic CRUD statements and slicing and dicing the data, to applying \ - filters and using aggregate functions to summarize the data. You will look at \ - solving common problems, navigating hierarchical data, and exploring the data \ - dictionary."""); - book9781642001242.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/sql-queries-succinctly"); - books.add(book9781642001242); - Book book9781642001563 = new Book(); - book9781642001563.setIsbn("9781642001563"); - book9781642001563.setTitle("Docker Succinctly"); - book9781642001563.setAuthor("Elton Stoneman"); - book9781642001563.setPublisher("Syncfusion"); - book9781642001563.setPublished(LocalDate.of(2018, 1, 16)); - book9781642001563.setPages(98); - book9781642001563.setDescription( - """ - Containers have revolutionized software development, allowing developers to \ - bundle their applications with everything they need, from the operating system \ - up, into a single package. Docker is one of the most popular platforms for \ - containers, allowing them to be hosted on-premises or on the cloud, and to run \ - on Linux, Windows, and Mac machines. With Docker Succinctly® by Elton Stoneman, \ - learn the basics of building Docker images, sharing them on the Docker Hub, \ - orchestrating containers to deliver large applications, and much more."""); - book9781642001563.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/docker-succinctly"); - books.add(book9781642001563); - Book book9781642001792 = new Book(); - book9781642001792.setIsbn("9781642001792"); - book9781642001792.setTitle("Kubernetes Succinctly"); - book9781642001792.setAuthor("Rahul Rai, Tarun Pabbi"); - book9781642001792.setPublisher("Syncfusion"); - book9781642001792.setPublished(LocalDate.of(2019, 3, 1)); - book9781642001792.setPages(121); - book9781642001792.setDescription( - """ - With excellent orchestration and routing capabilities, Kubernetes is an \ - enterprise-grade platform for building microservices applications. Kubernetes is \ - evolving as the de facto container management tool used by organizations and \ - cloud vendors all over the world. Kubernetes Succinctly® by Rahul Rai and Tarun \ - Pabbi is your guide to learning Kubernetes and leveraging its many capabilities \ - for developing, validating, and maintaining your applications."""); - book9781642001792.setWebsite("https://www.syncfusion.com/succinctly-free-ebooks/kubernetes-succinctly"); - books.add(book9781642001792); - Book book9781838820756 = new Book(); - book9781838820756.setIsbn("9781838820756"); - book9781838820756.setTitle("The Kubernetes Workshop"); - book9781838820756 - .setAuthor("Zachary Arnold, Sahil Dua, Wei Huang, Faisal Masood, Mélony Qin, Mohammed Abu Taleb"); - book9781838820756.setPublisher("Packt"); - book9781838820756.setPublished(LocalDate.of(2020, 9, 1)); - book9781838820756.setPages(780); - book9781838820756.setDescription( - """ - Thanks to its extensive support for managing hundreds of containers that run \ - cloud-native applications, Kubernetes is the most popular open source container \ - orchestration platform that makes cluster management easy. This workshop adopts a \ - practical approach to get you acquainted with the Kubernetes environment and its \ - applications. Starting with an introduction to the fundamentals of Kubernetes, \ - you'll install and set up your Kubernetes environment. You'll understand how to \ - write YAML files and deploy your first simple web application container using \ - Pod. You'll then assign human-friendly names to Pods, explore various Kubernetes \ - entities and functions, and discover when to use them. As you work through the \ - chapters, this Kubernetes book will show you how you can make full-scale use of \ - Kubernetes by applying a variety of techniques for designing components and \ - deploying clusters. You'll also get to grips with security policies for limiting \ - access to certain functions inside the cluster. Toward the end of the book, \ - you'll get a rundown of Kubernetes advanced features for building your own \ - controller and upgrading to a Kubernetes cluster without downtime."""); - book9781838820756.setWebsite("https://www.packtpub.com/free-ebook/the-kubernetes-workshop/9781838820756"); - books.add(book9781838820756); - return books; - } -} diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java new file mode 100644 index 0000000..44878d4 --- /dev/null +++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java @@ -0,0 +1,40 @@ +package ar.com.nanotaboada.java.samples.spring.boot.models; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +/** + * JPA AttributeConverter that converts between LocalDate and Unix timestamp + * (seconds since epoch). + * + * This converter stores dates as INTEGER in SQLite, which is more robust than + * TEXT-based + * date formats because: + * - No parsing ambiguity or locale-dependent formatting issues + * - Works consistently across all SQLite clients and tools + * - More efficient for date comparisons and indexing + * - Avoids the need for date_string_format in the JDBC connection URL + */ +@Converter +public class UnixTimestampConverter implements AttributeConverter { + + @Override + public Long convertToDatabaseColumn(LocalDate date) { + if (date == null) { + return null; + } + return date.atStartOfDay(ZoneOffset.UTC).toEpochSecond(); + } + + @Override + public LocalDate convertToEntityAttribute(Long timestamp) { + if (timestamp == null) { + return null; + } + return Instant.ofEpochSecond(timestamp).atZone(ZoneOffset.UTC).toLocalDate(); + } +} diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/BooksRepository.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/BooksRepository.java index cf0f2fd..f5f9748 100644 --- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/BooksRepository.java +++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/repositories/BooksRepository.java @@ -1,5 +1,8 @@ package ar.com.nanotaboada.java.samples.spring.boot.repositories; +import java.util.List; +import java.util.Optional; + import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; @@ -7,9 +10,6 @@ import ar.com.nanotaboada.java.samples.spring.boot.models.Book; -import java.util.List; -import java.util.Optional; - @Repository public interface BooksRepository extends CrudRepository { /** @@ -22,11 +22,11 @@ public interface BooksRepository extends CrudRepository { /** * Finds books whose description contains the given keyword (case-insensitive). - * Uses JPQL with CAST to handle CLOB description field. + * SQLite handles TEXT fields natively, so no CAST operation is needed. * * @param keyword the keyword to search for in the description * @return a list of books matching the search criteria */ - @Query("SELECT b FROM Book b WHERE LOWER(CAST(b.description AS string)) LIKE LOWER(CONCAT('%', :keyword, '%'))") + @Query("SELECT b FROM Book b WHERE LOWER(b.description) LIKE LOWER(CONCAT('%', :keyword, '%'))") List findByDescriptionContainingIgnoreCase(@Param("keyword") String keyword); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33aede6..f556a99 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,7 +9,17 @@ management.endpoint.health.show-details=always # http://localhost:9001/actuator/info management.info.env.enabled=true info.app.name=Sample RESTful Web Service with Spring Boot -info.app.description=Proof of Concept for a RESTful Web Service made with Spring Boot 3 targeting JDK 21 +info.app.description=Proof of Concept for a RESTful Web Service made with Spring Boot 4 targeting JDK 25 springdoc.api-docs.path=/docs springdoc.swagger-ui.path=/swagger/index.html + +# SQLite Database Configuration +# Uses environment variable STORAGE_PATH if set, otherwise defaults to local path +# Dates are stored as Unix timestamps (INTEGER) for robustness - no date format config needed +spring.datasource.url=jdbc:sqlite:${STORAGE_PATH:storage/books-sqlite3.db} +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect +spring.jpa.hibernate.ddl-auto=none +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 26b310a..e9604f1 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,12 +1,12 @@ - - - - - - + + + + + + @@ -14,4 +14,4 @@ - \ No newline at end of file + diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..4cdc0f5 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,14 @@ +# Test Configuration +# Uses H2 in-memory database for fast test execution + +# H2 Database Configuration (Test Only) +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driver-class-name=org.h2.Driver +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=false +spring.jpa.properties.hibernate.format_sql=true + +# Server Configuration (disabled for tests) +server.port=0 +management.server.port=0 diff --git a/storage/books-sqlite3.db b/storage/books-sqlite3.db new file mode 100644 index 0000000000000000000000000000000000000000..b138738a5852d06c3a6a90c40a3e3a0a281e13b4 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 From b78db1d57188b7b1130a464a0b9db2d22fa1b053 Mon Sep 17 00:00:00 2001 From: Nano Taboada Date: Wed, 3 Dec 2025 13:31:47 -0300 Subject: [PATCH 2/2] chore: implement CodeRabbit review comments - Update sqlite-jdbc from 3.47.1.0 to 3.51.0.0 (2025-11-05 release) - Add error handling for database copy in entrypoint.sh - Check cp exit status before printing success message - Print clear error with paths on failure and exit non-zero --- .github/copilot-instructions.md | 2 +- README.md | 2 +- pom.xml | 2 +- scripts/entrypoint.sh | 9 +++++++-- .../spring/boot/models/UnixTimestampConverter.java | 3 +-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index fed4ea2..6503b81 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -243,7 +243,7 @@ The Docker container uses a "hold" pattern for the pre-seeded SQLite database: ### SQLite Configuration Notes -- **Date storage**: Dates are stored as Unix timestamps (INTEGER) for robustness - no parsing issues +- **Date storage**: LocalDate fields are stored as Unix timestamps (INTEGER) for robustness - no parsing issues - **Converter**: `UnixTimestampConverter` handles LocalDate ↔ epoch seconds conversion via JPA `@Convert` - **DDL auto**: Use `ddl-auto=none` since the database is pre-seeded (SQLite has limited ALTER TABLE support) - **Tests use H2**: The converter works seamlessly with both H2 and SQLite databases diff --git a/README.md b/README.md index e62a7df..2d3b74e 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ open target/site/jacoco/index.html **SQLite Configuration Notes:** - Dates are stored as Unix timestamps (INTEGER) for robustness - no date format parsing issues -- A JPA `AttributeConverter` handles LocalDate ↔ epoch seconds conversion transparently +- A JPA `AttributeConverter` handles LocalDate ↔ epoch seconds conversion transparently (UTC-based) - Use `ddl-auto=none` since the database is pre-seeded (SQLite has limited ALTER TABLE support) - Tests use H2 in-memory database - the converter works seamlessly with both databases diff --git a/pom.xml b/pom.xml index 327dc7b..bbc8ace 100644 --- a/pom.xml +++ b/pom.xml @@ -156,7 +156,7 @@ org.xerial sqlite-jdbc - 3.47.1.0 + 3.51.0.0 runtime diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index cf32c7c..709925f 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -12,8 +12,13 @@ if [ ! -f "$VOLUME_STORAGE_PATH" ]; then echo "⚠️ No existing database file found in volume." if [ -f "$IMAGE_STORAGE_PATH" ]; then echo "Copying database file to writable volume..." - cp "$IMAGE_STORAGE_PATH" "$VOLUME_STORAGE_PATH" - echo "✔ Database initialized at $VOLUME_STORAGE_PATH" + if cp "$IMAGE_STORAGE_PATH" "$VOLUME_STORAGE_PATH"; then + echo "✔ Database initialized at $VOLUME_STORAGE_PATH" + else + echo "❌ Failed to copy database from $IMAGE_STORAGE_PATH to $VOLUME_STORAGE_PATH" + echo " Check file permissions and available disk space." + exit 1 + fi else echo "⚠️ Database file missing at $IMAGE_STORAGE_PATH" exit 1 diff --git a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java index 44878d4..dad5b78 100644 --- a/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java +++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java @@ -12,8 +12,7 @@ * (seconds since epoch). * * This converter stores dates as INTEGER in SQLite, which is more robust than - * TEXT-based - * date formats because: + * TEXT-based date formats because: * - No parsing ambiguity or locale-dependent formatting issues * - Works consistently across all SQLite clients and tools * - More efficient for date comparisons and indexing