From faef32494c24e1d82819381b7db79c17a20ab64a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:57:31 +0000 Subject: [PATCH 1/2] Replace SQLite with PostgreSQL for production, add Testcontainers for tests - Add PostgreSQL driver and Testcontainers dependencies to build.gradle - Create Spring profiles: dev (SQLite), prod (PostgreSQL), test (Testcontainers+PostgreSQL) - Add vendor-specific Flyway migrations for SQLite and PostgreSQL - Fix MyBatis LIMIT syntax for cross-database compatibility - Rename reserved keyword alias AT to ATAG in mapper XMLs - Add SQL cleanup to test base classes for data isolation - Add docker-compose.yml with PostgreSQL service Co-Authored-By: Doris Tian --- build.gradle | 6 ++- docker-compose.yml | 28 +++++++++++ src/main/resources/application-dev.properties | 6 +++ .../resources/application-prod.properties | 6 +++ .../resources/application-test.properties | 7 ++- src/main/resources/application.properties | 6 +-- .../postgresql/V1__create_tables.sql | 49 +++++++++++++++++++ .../db/migration/sqlite/V1__create_tables.sql | 49 +++++++++++++++++++ src/main/resources/mapper/ArticleMapper.xml | 6 +-- .../resources/mapper/ArticleReadService.xml | 14 +++--- .../io/spring/infrastructure/DbTestBase.java | 12 +++++ .../ArticleRepositoryTransactionTest.java | 12 +++++ 12 files changed, 185 insertions(+), 16 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/main/resources/application-dev.properties create mode 100644 src/main/resources/application-prod.properties create mode 100644 src/main/resources/db/migration/postgresql/V1__create_tables.sql create mode 100644 src/main/resources/db/migration/sqlite/V1__create_tables.sql diff --git a/build.gradle b/build.gradle index da384dc69..490688cfb 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,8 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2', 'io.jsonwebtoken:jjwt-jackson:0.11.2' implementation 'joda-time:joda-time:2.10.13' - implementation 'org.xerial:sqlite-jdbc:3.36.0.3' + runtimeOnly 'org.xerial:sqlite-jdbc:3.36.0.3' + runtimeOnly 'org.postgresql:postgresql' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' @@ -54,6 +55,9 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.2.2' + testImplementation 'org.testcontainers:testcontainers:1.16.3' + testImplementation 'org.testcontainers:postgresql:1.16.3' + testImplementation 'org.testcontainers:junit-jupiter:1.16.3' } tasks.named('test') { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..b844b5443 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + postgresql: + image: postgres:14-alpine + environment: + POSTGRES_DB: realworld + POSTGRES_USER: realworld + POSTGRES_PASSWORD: realworld + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + app: + build: . + ports: + - "8080:8080" + environment: + SPRING_PROFILES_ACTIVE: prod + DATABASE_URL: jdbc:postgresql://postgresql:5432/realworld + DATABASE_USERNAME: realworld + DATABASE_PASSWORD: realworld + depends_on: + - postgresql + +volumes: + postgres_data: diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 000000000..34e106e58 --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,6 @@ +spring.datasource.url=jdbc:sqlite:dev.db +spring.datasource.driver-class-name=org.sqlite.JDBC +spring.datasource.username= +spring.datasource.password= + +spring.flyway.locations=classpath:db/migration/sqlite diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 000000000..30fb6bfba --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,6 @@ +spring.datasource.url=${DATABASE_URL:jdbc:postgresql://localhost:5432/realworld} +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.username=${DATABASE_USERNAME:realworld} +spring.datasource.password=${DATABASE_PASSWORD:realworld} + +spring.flyway.locations=classpath:db/migration/postgresql diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 0902f5cba..df0651ec9 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -1 +1,6 @@ -spring.datasource.url=jdbc:sqlite::memory: \ No newline at end of file +spring.datasource.url=jdbc:tc:postgresql:14-alpine:///realworld_test +spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver +spring.datasource.username=test +spring.datasource.password=test + +spring.flyway.locations=classpath:db/migration/postgresql diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7c1947fc2..6deaf3c07 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,7 +1,5 @@ -spring.datasource.url=jdbc:sqlite:dev.db -spring.datasource.driver-class-name=org.sqlite.JDBC -spring.datasource.username= -spring.datasource.password= +spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev} + spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true image.default=https://static.productionready.io/images/smiley-cyrus.jpg diff --git a/src/main/resources/db/migration/postgresql/V1__create_tables.sql b/src/main/resources/db/migration/postgresql/V1__create_tables.sql new file mode 100644 index 000000000..6145ce7ab --- /dev/null +++ b/src/main/resources/db/migration/postgresql/V1__create_tables.sql @@ -0,0 +1,49 @@ +CREATE TABLE users ( + id VARCHAR(255) PRIMARY KEY, + username VARCHAR(255) UNIQUE, + password VARCHAR(255), + email VARCHAR(255) UNIQUE, + bio TEXT, + image VARCHAR(511) +); + +CREATE TABLE articles ( + id VARCHAR(255) PRIMARY KEY, + user_id VARCHAR(255), + slug VARCHAR(255) UNIQUE, + title VARCHAR(255), + description TEXT, + body TEXT, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE article_favorites ( + article_id VARCHAR(255) NOT NULL, + user_id VARCHAR(255) NOT NULL, + PRIMARY KEY(article_id, user_id) +); + +CREATE TABLE follows ( + user_id VARCHAR(255) NOT NULL, + follow_id VARCHAR(255) NOT NULL +); + +CREATE TABLE tags ( + id VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL +); + +CREATE TABLE article_tags ( + article_id VARCHAR(255) NOT NULL, + tag_id VARCHAR(255) NOT NULL +); + +CREATE TABLE comments ( + id VARCHAR(255) PRIMARY KEY, + body TEXT, + article_id VARCHAR(255), + user_id VARCHAR(255), + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/src/main/resources/db/migration/sqlite/V1__create_tables.sql b/src/main/resources/db/migration/sqlite/V1__create_tables.sql new file mode 100644 index 000000000..5bccdd5ed --- /dev/null +++ b/src/main/resources/db/migration/sqlite/V1__create_tables.sql @@ -0,0 +1,49 @@ +create table users ( + id varchar(255) primary key, + username varchar(255) UNIQUE, + password varchar(255), + email varchar(255) UNIQUE, + bio text, + image varchar(511) +); + +create table articles ( + id varchar(255) primary key, + user_id varchar(255), + slug varchar(255) UNIQUE, + title varchar(255), + description text, + body text, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +create table article_favorites ( + article_id varchar(255) not null, + user_id varchar(255) not null, + primary key(article_id, user_id) +); + +create table follows ( + user_id varchar(255) not null, + follow_id varchar(255) not null +); + +create table tags ( + id varchar(255) primary key, + name varchar(255) not null +); + +create table article_tags ( + article_id varchar(255) not null, + tag_id varchar(255) not null +); + +create table comments ( + id varchar(255) primary key, + body text, + article_id varchar(255), + user_id varchar(255), + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/src/main/resources/mapper/ArticleMapper.xml b/src/main/resources/mapper/ArticleMapper.xml index 60a9811f6..4b6137df9 100644 --- a/src/main/resources/mapper/ArticleMapper.xml +++ b/src/main/resources/mapper/ArticleMapper.xml @@ -45,8 +45,8 @@ T.id tagId, T.name tagName from articles A - left join article_tags AT on A.id = AT.article_id - left join tags T on T.id = AT.tag_id + left join article_tags ATAG on A.id = ATAG.article_id + left join tags T on T.id = ATAG.tag_id