From e47adac0def1ca407783d4d6dc5c690414e4793a Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sun, 15 Jun 2025 01:24:02 +0900 Subject: [PATCH 1/5] Add aggregate strategy for Employee-Department association MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement aggregate strategy pattern to automatically map Department entities when selecting Employees. This includes: - EmployeeAggregateStrategy with association linker - Updated DAOs to use aggregate strategy in select methods - Modified SQL queries to join with department table - Initialize employees list in Department entities - Update build script to generate aggregate strategy classes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- build.gradle.kts | 47 +++++++++++++++++-- src/main/java/com/example/Department.java | 4 +- .../example/EmployeeAggregateStrategy.java | 17 +++++++ src/main/java/com/example/EmployeeDao.java | 2 +- src/main/java/com/example/Main.java | 9 +++- .../com/example/EmployeeDao/selectById.sql | 6 ++- 6 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/example/EmployeeAggregateStrategy.java diff --git a/build.gradle.kts b/build.gradle.kts index 33ba098..632f159 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,6 +61,8 @@ tasks { (1..generationSize).forEach { i -> val employeeDaoFile = File(sourceDir, "Employee${i}Dao.java") writeEmployeeDaoCode(employeeDaoFile, i) + val employeeAggregateStrategyFile = File(sourceDir, "Employee${i}AggregateStrategy.java") + writeEmployeeAggregateStrategyCode(employeeAggregateStrategyFile, i) } println("Generated DAO files in src/main/java/com/example/dao") } @@ -87,7 +89,15 @@ tasks { dir.mkdirs() val sqlFile = File(dir, "selectById.sql") - sqlFile.writeText("select /*%expand*/* from employee$i where id = /*id*/0\n") + sqlFile.writeText( + """ + SELECT /*%expand*/* + FROM employee$i e + INNER JOIN department$i d + ON e.department_id = d.id + WHERE e.id = /*id*/0 + """.trimIndent() + ) } println("Generated SQL files in src/main/resources/META-INF/com/example/dao/EmployeeXxxDao") } @@ -125,13 +135,43 @@ fun writeEmployeeDaoCode( @Delete int delete(Employee$i entity); - @Select + @Select(aggregateStrategy = Employee${i}AggregateStrategy.class) Employee$i selectById(Long id); } """.trimIndent(), ) } +fun writeEmployeeAggregateStrategyCode( + file: File, + i: Int, +) { + file.writeText( + """ + package com.example.dao; + + import org.seasar.doma.AggregateStrategy; + import org.seasar.doma.AssociationLinker; + + import java.util.function.BiConsumer; + + import com.example.entity.Department$i; + import com.example.entity.Employee$i; + + @AggregateStrategy(root = Employee${i}.class, tableAlias = "e") + public interface Employee${i}AggregateStrategy { + + @AssociationLinker(propertyPath = "department", tableAlias = "d") + BiConsumer department = + (e, d) -> { + e.department = d; + d.employees.add(e); + }; + } + """.trimIndent(), + ) +} + fun writeEmployeeCode( file: File, i: Int, @@ -191,6 +231,7 @@ fun writeDepartmentCode( """ package com.example.entity; + import java.util.ArrayList; import java.util.List; import org.seasar.doma.Association; @@ -211,7 +252,7 @@ fun writeDepartmentCode( @Version public Integer version; @Association - public List employees; + public List employees = new ArrayList<>(); } """.trimIndent(), ) diff --git a/src/main/java/com/example/Department.java b/src/main/java/com/example/Department.java index 386c613..6eb56a7 100644 --- a/src/main/java/com/example/Department.java +++ b/src/main/java/com/example/Department.java @@ -1,6 +1,8 @@ package com.example; import com.example.domain.Name; + +import java.util.ArrayList; import java.util.List; import org.seasar.doma.Association; import org.seasar.doma.Entity; @@ -18,5 +20,5 @@ public class Department { public Name name; @Version public Integer version; - @Association public List employees; + @Association public List employees = new ArrayList<>(); } diff --git a/src/main/java/com/example/EmployeeAggregateStrategy.java b/src/main/java/com/example/EmployeeAggregateStrategy.java new file mode 100644 index 0000000..19c1b07 --- /dev/null +++ b/src/main/java/com/example/EmployeeAggregateStrategy.java @@ -0,0 +1,17 @@ +package com.example; + +import org.seasar.doma.AggregateStrategy; +import org.seasar.doma.AssociationLinker; + +import java.util.function.BiConsumer; + +@AggregateStrategy(root = Employee.class, tableAlias = "e") +public interface EmployeeAggregateStrategy { + + @AssociationLinker(propertyPath = "department", tableAlias = "d") + BiConsumer department = + (e, d) -> { + e.department = d; + d.employees.add(e); + }; +} diff --git a/src/main/java/com/example/EmployeeDao.java b/src/main/java/com/example/EmployeeDao.java index a6cc26e..d88b1ab 100644 --- a/src/main/java/com/example/EmployeeDao.java +++ b/src/main/java/com/example/EmployeeDao.java @@ -18,7 +18,7 @@ public interface EmployeeDao { @Delete int delete(Employee entity); - @Select + @Select(aggregateStrategy = EmployeeAggregateStrategy.class) Employee selectById(Long id); @Script diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index db5076f..c274b78 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -6,6 +6,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Objects; + public class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); @@ -16,10 +18,15 @@ public static void main(String[] args) { .naming(Naming.SNAKE_LOWER_CASE) .jdbcLogger(new Slf4jJdbcLogger()) .build(); - var employeeDao = new EmployeeDaoImpl(config); + EmployeeDao employeeDao = new EmployeeDaoImpl(config); employeeDao.create(); var employee = employeeDao.selectById(1L); + Objects.requireNonNull(employee); + Objects.requireNonNull(employee.name); + Objects.requireNonNull(employee.department); + logger.info(employee.name.value()); // John Smith + logger.info(employee.department.name.value()); // Engineering } } diff --git a/src/main/resources/META-INF/com/example/EmployeeDao/selectById.sql b/src/main/resources/META-INF/com/example/EmployeeDao/selectById.sql index e3011f9..1e030aa 100644 --- a/src/main/resources/META-INF/com/example/EmployeeDao/selectById.sql +++ b/src/main/resources/META-INF/com/example/EmployeeDao/selectById.sql @@ -1 +1,5 @@ -select /*%expand*/* from employee where id = /*id*/0 +SELECT /*%expand*/* + FROM employee e + INNER JOIN department d + ON e.department_id = d.id + WHERE e.id = /*id*/0 From 9d867265f403cfb0ed7b84da0bde2f11a78efcc4 Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sun, 15 Jun 2025 06:37:04 +0900 Subject: [PATCH 2/5] Add aggregate strategy and CI workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement aggregate strategy pattern for Employee-Department association - Add EmployeeAggregateStrategy with association linker - Update DAOs and SQL queries to use aggregate strategy - Add GitHub Actions CI workflow with Java 17 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..18a2a50 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: [ master, main ] + pull_request: + branches: [ master, main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4.4.1 + + - name: Check code formatting + run: ./gradlew spotlessCheck + + - name: Build with generated files + run: ./gradlew clean generateAll build + + - name: Run application + run: ./gradlew run + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: build/test-results/test/ + + - name: Upload build reports + uses: actions/upload-artifact@v4 + if: always() + with: + name: build-reports + path: build/reports/ \ No newline at end of file From 1e41bd617ff31d30a16a55f016e4f7490c4cb019 Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sun, 15 Jun 2025 06:48:43 +0900 Subject: [PATCH 3/5] Apply code formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorder import statements - Fix string interpolation in build.gradle.kts - Apply consistent code style 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- build.gradle.kts | 6 +++--- src/main/java/com/example/Department.java | 1 - src/main/java/com/example/EmployeeAggregateStrategy.java | 3 +-- src/main/java/com/example/Main.java | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 632f159..192aa92 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -96,7 +96,7 @@ tasks { INNER JOIN department$i d ON e.department_id = d.id WHERE e.id = /*id*/0 - """.trimIndent() + """.trimIndent(), ) } println("Generated SQL files in src/main/resources/META-INF/com/example/dao/EmployeeXxxDao") @@ -158,11 +158,11 @@ fun writeEmployeeAggregateStrategyCode( import com.example.entity.Department$i; import com.example.entity.Employee$i; - @AggregateStrategy(root = Employee${i}.class, tableAlias = "e") + @AggregateStrategy(root = Employee$i.class, tableAlias = "e") public interface Employee${i}AggregateStrategy { @AssociationLinker(propertyPath = "department", tableAlias = "d") - BiConsumer department = + BiConsumer department = (e, d) -> { e.department = d; d.employees.add(e); diff --git a/src/main/java/com/example/Department.java b/src/main/java/com/example/Department.java index 6eb56a7..e79b620 100644 --- a/src/main/java/com/example/Department.java +++ b/src/main/java/com/example/Department.java @@ -1,7 +1,6 @@ package com.example; import com.example.domain.Name; - import java.util.ArrayList; import java.util.List; import org.seasar.doma.Association; diff --git a/src/main/java/com/example/EmployeeAggregateStrategy.java b/src/main/java/com/example/EmployeeAggregateStrategy.java index 19c1b07..44d79ff 100644 --- a/src/main/java/com/example/EmployeeAggregateStrategy.java +++ b/src/main/java/com/example/EmployeeAggregateStrategy.java @@ -1,10 +1,9 @@ package com.example; +import java.util.function.BiConsumer; import org.seasar.doma.AggregateStrategy; import org.seasar.doma.AssociationLinker; -import java.util.function.BiConsumer; - @AggregateStrategy(root = Employee.class, tableAlias = "e") public interface EmployeeAggregateStrategy { diff --git a/src/main/java/com/example/Main.java b/src/main/java/com/example/Main.java index c274b78..ede7edb 100644 --- a/src/main/java/com/example/Main.java +++ b/src/main/java/com/example/Main.java @@ -1,13 +1,12 @@ package com.example; +import java.util.Objects; import org.seasar.doma.jdbc.Naming; import org.seasar.doma.jdbc.SimpleConfig; import org.seasar.doma.slf4j.Slf4jJdbcLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Objects; - public class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); From 6c0528d4b93f869d688f06d276a62f21d79c4d7f Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sun, 15 Jun 2025 06:54:41 +0900 Subject: [PATCH 4/5] Update Doma to stable version 3.9.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change from snapshot version 3.9.1-SNAPSHOT to stable release 3.9.0 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b5f8aab..a5af2ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] junit = "5.10.0" -doma = "3.9.1-SNAPSHOT" +doma = "3.9.0" h2 = "2.3.232" slf4j = "2.0.17" logback = "1.5.18" From 862c28aea4306d8d00c667682c8a241d8ef1bd6a Mon Sep 17 00:00:00 2001 From: Toshihiro Nakamura Date: Sun, 15 Jun 2025 07:05:12 +0900 Subject: [PATCH 5/5] Refactor build configuration and exclude generated files from Spotless MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move package path variables to top level for better accessibility - Exclude generated DAO, entity, and SQL files from Spotless formatting 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- build.gradle.kts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 192aa92..6e0e0a7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,6 +31,10 @@ application { mainClass.set("com.example.Main") } +var daoPackagePath = "src/main/java/com/example/dao" +var entityPackagePath = "src/main/java/com/example/entity" +var sqlFileDirPath = "src/main/resources/META-INF/com/example/dao" + tasks { test { useJUnitPlatform() @@ -48,10 +52,6 @@ tasks { dependsOn("generateDAOs", "generateEntities", "generateSqlFiles") } - var daoPackagePath = "src/main/java/com/example/dao" - var entityPackagePath = "src/main/java/com/example/entity" - var sqlFileDirPath = "src/main/resources/META-INF/com/example/dao" - register("generateDAOs") { dependsOn("generateEntities") doLast { @@ -262,7 +262,7 @@ spotless { java { googleJavaFormat(libs.versions.googleJavaFormat.get()) target("src/*/java/**/*.java") - targetExclude("**/generated/**") + targetExclude("**/generated/**", "$daoPackagePath/**", "$entityPackagePath/**", "$sqlFileDirPath/**") } kotlin { target("*.gradle.kts")