diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 41281d1..6503b81 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**: 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 + ## 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..2d3b74e 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 (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 + ## 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..bbc8ace 100644 --- a/pom.xml +++ b/pom.xml @@ -146,18 +146,40 @@ spring-boot-starter-data-jpa-test test + + + + org.xerial + sqlite-jdbc + 3.51.0.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..709925f 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,11 +1,32 @@ #!/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..." + 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 + 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..dad5b78 --- /dev/null +++ b/src/main/java/ar/com/nanotaboada/java/samples/spring/boot/models/UnixTimestampConverter.java @@ -0,0 +1,39 @@ +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 0000000..b138738 Binary files /dev/null and b/storage/books-sqlite3.db differ