Skip to content

Commit 74d5382

Browse files
authored
feat: adds Java-based Postgres demo to the library (#816)
* feat: initial creation of java-based postgres demo Signed-off-by: Anthony D. Mays <[email protected]> * fix: ensures .env file can be loaded from the right location Signed-off-by: Anthony D. Mays <[email protected]> * feat: adds JPA demo Signed-off-by: Anthony D. Mays <[email protected]> --------- Signed-off-by: Anthony D. Mays <[email protected]>
1 parent 55b7512 commit 74d5382

29 files changed

+1895
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Database Configuration
2+
DB_URL=jdbc:postgresql://localhost:5433/demo_db
3+
DB_USER=demo_user
4+
DB_PASSWORD=demo_password
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#
2+
# https://help.github.com/articles/dealing-with-line-endings/
3+
#
4+
# Linux start script should use lf
5+
/gradlew text eol=lf
6+
7+
# These are Windows script files and should use crlf
8+
*.bat text eol=crlf
9+
10+
# Binary files should be left untouched
11+
*.jar binary
12+

lib/java/postgres_demo/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Ignore Gradle project-specific cache directory
2+
.gradle
3+
4+
# Ignore Gradle build output directory
5+
build
6+
.env

lib/java/postgres_demo/README.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Postgres Demo
2+
3+
A Spring Boot application demonstrating PostgreSQL database connectivity with Flyway migrations, built with Gradle (Kotlin DSL).
4+
5+
## Features
6+
7+
- Spring Boot 3.2.1 application
8+
- PostgreSQL database connection using Spring Data JDBC
9+
- Flyway database migrations (managed by Spring Boot)
10+
- Configuration via `application.yml` with Spring's built-in environment variable support
11+
- Environment variable support via `.env` files (using spring-dotenv)
12+
- Docker Compose setup for local PostgreSQL instance
13+
- Gradle Kotlin DSL build configuration
14+
15+
## Prerequisites
16+
17+
- Java 17 or higher
18+
- Docker and Docker Compose (for running PostgreSQL)
19+
- Gradle (wrapper included)
20+
21+
## Project Structure
22+
23+
```
24+
postgres_demo/
25+
├── app/
26+
│ ├── build.gradle.kts # Gradle build configuration
27+
│ └── src/
28+
│ └── main/
29+
│ ├── java/
30+
│ │ └── com/codesociety/
31+
│ │ ├── PostgresDemoApplication.java # Spring Boot main class
32+
│ │ └── DemoRunner.java # CommandLineRunner for demo
33+
│ └── resources/
34+
│ ├── application.yml # Spring Boot configuration
35+
│ └── db/migration/
36+
│ ├── V1__create_users_table.sql # Flyway migration
37+
│ └── V2__create_posts_table.sql # Flyway migration
38+
├── docker-compose.yml # PostgreSQL Docker setup
39+
├── .env # Environment variables (local)
40+
└── .env.example # Environment variables template
41+
```
42+
43+
## Setup
44+
45+
1. **Copy environment variables:**
46+
```bash
47+
cp .env.example .env
48+
```
49+
50+
2. **Start PostgreSQL with Docker Compose:**
51+
```bash
52+
docker-compose up -d
53+
```
54+
55+
3. **Verify PostgreSQL is running:**
56+
```bash
57+
docker-compose ps
58+
```
59+
60+
## Configuration
61+
62+
Spring Boot automatically loads configuration from multiple sources in this order:
63+
64+
### 1. application.yml (Spring Boot standard)
65+
Located at `app/src/main/resources/application.yml`:
66+
```yaml
67+
spring:
68+
datasource:
69+
url: ${DB_URL:jdbc:postgresql://localhost:5432/demo_db}
70+
username: ${DB_USER:demo_user}
71+
password: ${DB_PASSWORD:demo_password}
72+
```
73+
74+
Spring Boot supports `${ENV_VAR:default_value}` syntax natively!
75+
76+
### 2. .env file (via spring-dotenv)
77+
Create a `.env` file in the project root:
78+
```
79+
DB_URL=jdbc:postgresql://localhost:5432/demo_db
80+
DB_USER=demo_user
81+
DB_PASSWORD=demo_password
82+
```
83+
84+
### 3. Environment variables
85+
Export environment variables directly:
86+
```bash
87+
export DB_URL=jdbc:postgresql://localhost:5432/demo_db
88+
export DB_USER=demo_user
89+
export DB_PASSWORD=demo_password
90+
```
91+
92+
## Running the Application
93+
94+
### Quick start (includes Docker setup):
95+
```bash
96+
./start.sh
97+
```
98+
99+
### Run with Gradle:
100+
```bash
101+
./gradlew :app:bootRun
102+
```
103+
104+
### Build the application:
105+
```bash
106+
./gradlew :app:build
107+
```
108+
109+
### Run as a JAR:
110+
```bash
111+
./gradlew :app:bootJar
112+
java -jar app/build/libs/app.jar
113+
```
114+
115+
## What the Application Does
116+
117+
1. Loads environment variables from `.env` file (via spring-dotenv)
118+
2. Reads configuration from `application.yml` (Spring Boot handles this)
119+
3. Runs Flyway database migrations automatically (Spring Boot integration)
120+
4. Connects to PostgreSQL database using Spring's DataSource
121+
5. Queries and displays users and posts from the database using JdbcTemplate
122+
123+
## Database Schema
124+
125+
The application creates two tables:
126+
127+
**Users table:**
128+
```sql
129+
CREATE TABLE users (
130+
id SERIAL PRIMARY KEY,
131+
username VARCHAR(100) NOT NULL UNIQUE,
132+
email VARCHAR(255) NOT NULL UNIQUE,
133+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
134+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
135+
);
136+
```
137+
138+
**Posts table:**
139+
```sql
140+
CREATE TABLE posts (
141+
id SERIAL PRIMARY KEY,
142+
user_id INTEGER NOT NULL,
143+
title VARCHAR(255) NOT NULL,
144+
content TEXT,
145+
published BOOLEAN NOT NULL DEFAULT false,
146+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
147+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
148+
CONSTRAINT fk_user FOREIGN KEY(user_id) REFERENCES users(id)
149+
);
150+
```
151+
152+
## Docker Commands
153+
154+
**Start PostgreSQL:**
155+
```bash
156+
docker-compose up -d
157+
```
158+
159+
**Stop PostgreSQL:**
160+
```bash
161+
docker-compose down
162+
```
163+
164+
**View PostgreSQL logs:**
165+
```bash
166+
docker-compose logs -f postgres
167+
```
168+
169+
**Connect to PostgreSQL CLI:**
170+
```bash
171+
docker-compose exec postgres psql -U demo_user -d demo_db
172+
```
173+
174+
**Stop and remove volumes (delete data):**
175+
```bash
176+
docker-compose down -v
177+
```
178+
179+
## Spring Boot Features Used
180+
181+
- **Auto-configuration:** Spring Boot automatically configures DataSource, JdbcTemplate, and Flyway
182+
- **Environment variable support:** Built-in `${VAR:default}` syntax in application.yml
183+
- **Flyway integration:** Migrations run automatically on startup
184+
- **CommandLineRunner:** DemoRunner executes after the application starts
185+
186+
## Dependencies
187+
188+
- Spring Boot Starter (3.2.1)
189+
- Spring Boot Starter Data JDBC
190+
- PostgreSQL JDBC Driver (42.7.1)
191+
- Flyway Core (managed by Spring Boot)
192+
- Flyway PostgreSQL
193+
- Spring Dotenv (4.0.0) - for .env file support
194+
195+
## Troubleshooting
196+
197+
**Connection refused:**
198+
- Ensure PostgreSQL is running: `docker-compose ps`
199+
- Check the database URL in `.env` matches your setup
200+
201+
**Flyway migration errors:**
202+
- Reset the database: `docker-compose down -v && docker-compose up -d`
203+
- Check migration files in `app/src/main/resources/db/migration/`
204+
205+
**Build errors:**
206+
- Clean and rebuild: `./gradlew clean :app:build`
207+
- Ensure Java 17+ is installed: `java -version`
208+
209+
**Tests fail:**
210+
- The tests require a running database or use H2 in-memory database
211+
- To skip tests: `./gradlew :app:build -x test`
212+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* This file was generated by the Gradle 'init' task.
3+
*
4+
* This generated file contains a sample Java application project to get you started.
5+
* For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.14.1/userguide/building_java_projects.html in the Gradle documentation.
6+
*/
7+
8+
plugins {
9+
// Apply the application plugin to add support for building a CLI application in Java.
10+
application
11+
// Spring Boot plugin
12+
id("org.springframework.boot") version "3.2.1"
13+
id("io.spring.dependency-management") version "1.1.4"
14+
}
15+
16+
repositories {
17+
// Use Maven Central for resolving dependencies.
18+
mavenCentral()
19+
}
20+
21+
dependencies {
22+
// Spring Boot Starter
23+
implementation("org.springframework.boot:spring-boot-starter")
24+
25+
// Spring Boot JDBC
26+
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
27+
28+
// Spring Boot JPA
29+
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
30+
31+
// PostgreSQL JDBC Driver
32+
implementation("org.postgresql:postgresql")
33+
34+
// Flyway Core (managed by Spring Boot)
35+
implementation("org.flywaydb:flyway-core")
36+
37+
// Dotenv for loading .env files
38+
implementation("me.paulschwarz:spring-dotenv:4.0.0")
39+
40+
// Testing
41+
testImplementation("org.springframework.boot:spring-boot-starter-test")
42+
// H2 Database for testing
43+
testImplementation("com.h2database:h2")
44+
}
45+
46+
// Apply a specific Java toolchain to ease working on different environments.
47+
java {
48+
toolchain {
49+
languageVersion = JavaLanguageVersion.of(17)
50+
}
51+
}
52+
53+
application {
54+
// Define the main class for the application.
55+
mainClass = "com.codesociety.PostgresDemoApplication"
56+
}
57+
58+
tasks.named<Test>("test") {
59+
// Use JUnit Platform for unit tests.
60+
useJUnitPlatform()
61+
}
62+
63+
tasks.named<org.springframework.boot.gradle.tasks.run.BootRun>("bootRun") {
64+
// Set working directory to project root so .env file can be found
65+
workingDir = project.rootDir
66+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.codesociety;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
import org.springframework.boot.CommandLineRunner;
7+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8+
import org.springframework.jdbc.core.JdbcTemplate;
9+
import org.springframework.stereotype.Component;
10+
11+
@Component
12+
@ConditionalOnProperty(name = "app.demo.enabled", havingValue = "true", matchIfMissing = true)
13+
@org.springframework.core.annotation.Order(1)
14+
public class DemoRunner implements CommandLineRunner {
15+
16+
private final JdbcTemplate jdbcTemplate;
17+
18+
public DemoRunner(JdbcTemplate jdbcTemplate) {
19+
this.jdbcTemplate = jdbcTemplate;
20+
}
21+
22+
@Override
23+
public void run(String... args) {
24+
System.out.println("\n=== Postgres Demo Application ===\n");
25+
26+
System.out.println("✓ Spring Boot application started successfully!");
27+
System.out.println("✓ Database connection established");
28+
System.out.println("✓ Flyway migrations executed\n");
29+
30+
// Query the users table
31+
List<Map<String, Object>> users = jdbcTemplate.queryForList("SELECT * FROM users ORDER BY id");
32+
33+
System.out.println("Users in database:");
34+
System.out.println("-------------------------------------------");
35+
System.out.printf("%-5s %-20s %-30s%n", "ID", "Username", "Email");
36+
System.out.println("-------------------------------------------");
37+
38+
for (Map<String, Object> user : users) {
39+
System.out.printf("%-5d %-20s %-30s%n",
40+
user.get("id"),
41+
user.get("username"),
42+
user.get("email"));
43+
}
44+
45+
System.out.println("-------------------------------------------");
46+
47+
// Query the posts table
48+
List<Map<String, Object>> posts = jdbcTemplate.queryForList(
49+
"SELECT p.id, p.title, u.username, p.published "
50+
+ "FROM posts p "
51+
+ "JOIN users u ON p.user_id = u.id "
52+
+ "ORDER BY p.id"
53+
);
54+
55+
System.out.println("\nPosts in database:");
56+
System.out.println("-------------------------------------------------------------------");
57+
System.out.printf("%-5s %-30s %-20s %-10s%n", "ID", "Title", "Author", "Published");
58+
System.out.println("-------------------------------------------------------------------");
59+
60+
for (Map<String, Object> post : posts) {
61+
System.out.printf("%-5d %-30s %-20s %-10s%n",
62+
post.get("id"),
63+
post.get("title"),
64+
post.get("username"),
65+
post.get("published"));
66+
}
67+
68+
System.out.println("-------------------------------------------------------------------");
69+
System.out.println("\n✓ Demo completed successfully!\n");
70+
}
71+
}

0 commit comments

Comments
 (0)