Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ bin/
/src/main/java/com/example/dao/
/src/main/java/com/example/entity/
/src/main/resources/META-INF/com/example/dao/
/src/test/java/com/example/dao/
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ A Doma framework test project for benchmarking build performance with large numb
## Overview

This project is designed to test the build performance of Doma's annotation processor by automatically generating 200+ DAO interfaces and entity classes.
It includes comprehensive test suites for all generated DAOs to verify the functionality of batch operations and aggregate strategies.

## Requirements

Expand Down Expand Up @@ -57,6 +58,9 @@ Commands to generate large numbers of test classes:
# Generate only SQL files
./gradlew generateSqlFiles

# Generate test classes for all DAOs
./gradlew generateTests

# Remove all generated files
./gradlew removeGeneratedFiles
```
Expand Down Expand Up @@ -89,11 +93,21 @@ src/
│ └── resources/
│ └── META-INF/com/example/dao/ # SQL files
└── test/
└── java/
└── com/example/
└── dao/ # Generated DAO test classes
```

## Features

- **Batch Operations**: Support for batch insert, update, and delete operations
- **Aggregate Strategy**: Efficient loading of entity associations (Employee-Department relationships)
- **Performance Testing**: Designed to test annotation processor performance with 200+ entities
- **Comprehensive Test Suite**: Automated test generation for all DAOs

## Technology Stack

- **Doma 2**: Compile-time ORM framework
- **Doma 3.9.0**: Compile-time ORM framework with aggregate strategy support
- **H2 Database**: In-memory database for testing
- **JUnit 5**: Testing framework
- **SLF4J/Logback**: Logging
Expand Down
237 changes: 210 additions & 27 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ plugins {
group = "com.example"
version = "1.0-SNAPSHOT"

var generationSize = 200
var daoPackagePath = "src/main/java/com/example/dao"
var daoTestPackagePath = "src/test/java/com/example/dao"
var entityPackagePath = "src/main/java/com/example/entity"
var sqlFileDirPath = "src/main/resources/META-INF/com/example/dao"

repositories {
mavenLocal()
mavenCentral()
Expand All @@ -25,15 +31,27 @@ dependencies {
testImplementation(libs.junit.jupiter)
}

var generationSize = 200

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"
spotless {
java {
googleJavaFormat(libs.versions.googleJavaFormat.get())
target("src/*/java/**/*.java")
targetExclude(
"**/generated/**",
"$daoPackagePath/**",
"$daoTestPackagePath/**",
"$entityPackagePath/**",
"$sqlFileDirPath/**",
)
}
kotlin {
target("*.gradle.kts")
ktlint()
}
}

tasks {
test {
Expand All @@ -49,7 +67,7 @@ tasks {
}

register("generateAll") {
dependsOn("generateDAOs", "generateEntities", "generateSqlFiles")
dependsOn("generateDAOs", "generateTests", "generateEntities", "generateSqlFiles")
}

register("generateDAOs") {
Expand All @@ -68,6 +86,20 @@ tasks {
}
}

register("generateTests") {
dependsOn("generateDAOs")
doLast {
val sourceDir = file(daoTestPackagePath)
sourceDir.mkdirs()

(1..generationSize).forEach { i ->
val employeeDaoTestFile = File(sourceDir, "Employee${i}DaoTest.java")
writeEmployeeDaoTestCode(employeeDaoTestFile, i)
}
println("Generated DAO test files in src/test/java/com/example/dao")
}
}

register("generateEntities") {
doLast {
val sourceDir = file(entityPackagePath)
Expand All @@ -87,24 +119,19 @@ tasks {
(1..generationSize).forEach { i ->
val dir = file("$sqlFileDirPath/Employee${i}Dao")
dir.mkdirs()

val sqlFile = File(dir, "selectById.sql")
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(),
)
writeSelectByIdSqlFile(sqlFile, i)
val createScriptFile = File(dir, "create.script")
writeCreateScriptFile(createScriptFile, i)
val dropScriptFile = File(dir, "drop.script")
writeDropScriptFile(dropScriptFile, i)
}
println("Generated SQL files in src/main/resources/META-INF/com/example/dao/EmployeeXxxDao")
}
}

register<Delete>("removeGeneratedFiles") {
delete(file(entityPackagePath), file(daoPackagePath), file(sqlFileDirPath))
delete(file(daoPackagePath), file(daoTestPackagePath), file(entityPackagePath), file(sqlFileDirPath))
}
}

Expand All @@ -120,6 +147,7 @@ fun writeEmployeeDaoCode(
import org.seasar.doma.Insert;
import org.seasar.doma.Update;
import org.seasar.doma.Delete;
import org.seasar.doma.Script;
import org.seasar.doma.Select;
import org.seasar.doma.Sql;
import com.example.entity.Employee$i;
Expand All @@ -137,6 +165,12 @@ fun writeEmployeeDaoCode(

@Select(aggregateStrategy = Employee${i}AggregateStrategy.class)
Employee$i selectById(Long id);

@Script
void create();

@Script
void drop();
}
""".trimIndent(),
)
Expand Down Expand Up @@ -258,14 +292,163 @@ fun writeDepartmentCode(
)
}

spotless {
java {
googleJavaFormat(libs.versions.googleJavaFormat.get())
target("src/*/java/**/*.java")
targetExclude("**/generated/**", "$daoPackagePath/**", "$entityPackagePath/**", "$sqlFileDirPath/**")
}
kotlin {
target("*.gradle.kts")
ktlint()
}
fun writeSelectByIdSqlFile(
file: File,
i: Int,
) {
file.writeText(
"""
SELECT /*%expand*/*
FROM employee$i e
INNER JOIN department$i d
ON e.department_id = d.id
WHERE e.id = /*id*/0
""".trimIndent(),
)
}

fun writeCreateScriptFile(
file: File,
i: Int,
) {
file.writeText(
"""
CREATE TABLE department$i (
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
version INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);

CREATE TABLE employee$i (
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
age INTEGER,
birthday DATE,
profile_url VARCHAR(2048),
description1 VARCHAR(4000),
description2 VARCHAR(4000),
description3 VARCHAR(4000),
description4 VARCHAR(4000),
description5 VARCHAR(4000),
description6 VARCHAR(4000),
description7 VARCHAR(4000),
description8 VARCHAR(4000),
description9 VARCHAR(4000),
description10 VARCHAR(4000),
manager_id BIGINT,
department_id BIGINT,
version INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);

ALTER TABLE employee$i ADD CONSTRAINT fk_employee${i}_manager$i
FOREIGN KEY (manager_id) REFERENCES employee$i(id);

ALTER TABLE employee$i ADD CONSTRAINT fk_employee${i}_department$i
FOREIGN KEY (department_id) REFERENCES department$i(id);

-- Department data
INSERT INTO department$i (id, name, version) VALUES
(1, 'Engineering', 0),
(2, 'Sales', 0),
(3, 'Human Resources', 0),
(4, 'Administration', 0);

-- Employee data
INSERT INTO employee$i (id, name, age, birthday, profile_url, description1, description2, description3, description4, description5, description6, description7, description8, description9, description10, manager_id, department_id, version) VALUES
(1, 'John Smith', 45, '1979-04-15', 'https://example.com/profiles/john.smith', 'Engineering Department Head with 10 years experience', 'Expert in Java, Python, and Go programming', 'Strong team management skills', 'Promotes agile development practices', 'Strategic technology planning', 'Focus on team member development', 'Technical consultation with clients', 'Evaluation and adoption of new technologies', 'Project quality management', 'Cross-department coordination', NULL, 1, 0),
(2, 'Sarah Johnson', 42, '1982-08-22', 'https://example.com/profiles/sarah.johnson', 'Sales Department Head for 8 years', 'Contributed to customer satisfaction improvement', 'Team sales target achievement rate 120%', 'Expert in new business development', 'Strong proposal and presentation skills', 'Building long-term client relationships', 'Sales strategy planning and execution', 'Supporting team skill development', 'Market analysis and competitive research', 'Sales budget management', 1, 1, 0);

-- RESET IDENTITY
ALTER TABLE department$i ALTER COLUMN id RESTART WITH 100;
ALTER TABLE employee$i ALTER COLUMN id RESTART WITH 100;
""".trimIndent(),
)
}

fun writeDropScriptFile(
file: File,
i: Int,
) {
file.writeText(
"""
DROP TABLE IF EXISTS employee$i;
DROP TABLE IF EXISTS department$i;
""".trimIndent(),
)
}

fun writeEmployeeDaoTestCode(
file: File,
i: Int,
) {
file.writeText(
"""
package com.example.dao;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.Naming;
import org.seasar.doma.jdbc.SimpleConfig;
import org.seasar.doma.slf4j.Slf4jJdbcLogger;

import com.example.domain.Name;
import com.example.entity.Employee$i;

import static org.junit.jupiter.api.Assertions.*;

class Employee${i}DaoTest {

private final Config config =
SimpleConfig.builder("jdbc:h2:mem:test$i;DB_CLOSE_DELAY=-1")
.naming(Naming.SNAKE_LOWER_CASE)
.jdbcLogger(new Slf4jJdbcLogger())
.build();

private final Employee${i}Dao employeeDao = new Employee${i}DaoImpl(config);

@BeforeEach
void setup() {
employeeDao.drop();
employeeDao.create();
}

@Test
void insert() {
var employee = new Employee$i();
employee.name = new Name("ABC");
employee.departmentId = 2L;
var result = employeeDao.insert(employee);
assertEquals(1, result);
}

@Test
void update() {
var employee = employeeDao.selectById(2L);
employee.name = new Name("ABC");
var result = employeeDao.update(employee);
assertEquals(1, result);
}

@Test
void delete() {
var employee = employeeDao.selectById(2L);
var result = employeeDao.delete(employee);
assertEquals(1, result);
}

@Test
void selectById() {
var employee = employeeDao.selectById(1L);
assertNotNull(employee);
assertNotNull(employee.name);
assertNotNull(employee.department);
assertEquals("John Smith", employee.name.value());
assertEquals("Engineering", employee.department.name.value());
}
}
""".trimIndent(),
)
}
3 changes: 3 additions & 0 deletions src/main/java/com/example/EmployeeDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ public interface EmployeeDao {

@Script
void create();

@Script
void drop();
}
2 changes: 1 addition & 1 deletion src/main/java/com/example/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class Main {

public static void main(String[] args) {
var config =
SimpleConfig.builder("jdbc:h2:mem:tutorial;DB_CLOSE_DELAY=-1")
SimpleConfig.builder("jdbc:h2:mem:main;DB_CLOSE_DELAY=-1")
.naming(Naming.SNAKE_LOWER_CASE)
.jdbcLogger(new Slf4jJdbcLogger())
.build();
Expand Down
Loading