From 9f59880468d9004968970e7125314ed3c01d6164 Mon Sep 17 00:00:00 2001 From: irof Date: Fri, 20 Feb 2026 22:06:49 +0900 Subject: [PATCH 01/22] =?UTF-8?q?feat:=20Spring=20Data=20JDBC=E3=81=AECRUD?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../outputs/OutputImplementations.java | 26 ++- .../datasource/DatasourceAngles.java | 4 +- .../jig/domain/model/sources/ReadStatus.java | 4 +- .../DefaultJigRepositoryFactory.java | 26 ++- .../SpringDataJdbcStatementsReader.java | 213 ++++++++++++++++++ .../SpringDataJdbcStatementReaderTest.java | 62 +++++ .../springdata/SpringDataJdbcOrder.java | 9 + .../SpringDataJdbcOrderRepository.java | 21 ++ .../data/jdbc/repository/query/Query.java | 12 + .../data/relational/core/mapping/Table.java | 12 + .../data/repository/CrudRepository.java | 10 + .../data/repository/Repository.java | 4 + 12 files changed, 394 insertions(+), 9 deletions(-) create mode 100644 jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java create mode 100644 jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java create mode 100644 jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrder.java create mode 100644 jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java create mode 100644 jig-core/src/test/java/stub/org/springframework/data/jdbc/repository/query/Query.java create mode 100644 jig-core/src/test/java/stub/org/springframework/data/relational/core/mapping/Table.java create mode 100644 jig-core/src/test/java/stub/org/springframework/data/repository/CrudRepository.java create mode 100644 jig-core/src/test/java/stub/org/springframework/data/repository/Repository.java diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/information/outputs/OutputImplementations.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/information/outputs/OutputImplementations.java index f2bb1d366..caad7d7fa 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/information/outputs/OutputImplementations.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/information/outputs/OutputImplementations.java @@ -1,8 +1,12 @@ package org.dddjava.jig.domain.model.information.outputs; import org.dddjava.jig.domain.model.information.types.JigTypes; +import org.dddjava.jig.domain.model.data.types.JavaTypeDeclarationKind; import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.function.Function; import java.util.stream.Stream; import static java.util.stream.Collectors.collectingAndThen; @@ -25,13 +29,29 @@ public Gateways repositoryMethods() { // FIXME これのテストがない public static OutputImplementations from(JigTypes jigTypes, OutputAdapters outputAdapters) { return outputAdapters.stream() - // output adapterの実装しているoutput portのgatewayを - .flatMap(outputAdapter -> outputAdapter.implementsPortStream(jigTypes) + // interfaceのRepository(Spring Data JDBCなど)は実装クラスが存在しないため、自身をoutput portとして扱う + .flatMap(outputAdapter -> outputPorts(outputAdapter, jigTypes) .flatMap(outputPort -> outputPort.gatewayStream() // 実装しているinvocationが .flatMap(gateway -> outputAdapter.resolveInvocation(gateway).stream() .map(invocation -> new OutputImplementation(gateway, invocation, outputPort))))) - .collect(collectingAndThen(toList(), OutputImplementations::new)); + .collect(collectingAndThen(toList(), outputImplementations -> + new OutputImplementations(outputImplementations.stream() + .collect(collectingAndThen( + java.util.stream.Collectors.toMap( + outputImplementation -> outputImplementation.outputPortGateway().jigMethodId().namespace() + + "#" + outputImplementation.outputPortGateway().name(), + Function.identity(), + (existing, ignored) -> existing, + LinkedHashMap::new), + map -> List.copyOf(map.values())))))); + } + + private static Stream outputPorts(OutputAdapter outputAdapter, JigTypes jigTypes) { + if (outputAdapter.jigType().jigTypeHeader().javaTypeDeclarationKind() == JavaTypeDeclarationKind.INTERFACE) { + return Stream.of(new OutputPort(outputAdapter.jigType())); + } + return outputAdapter.implementsPortStream(jigTypes); } public Stream stream() { diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java index fbbc10465..008f0c339 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java @@ -21,8 +21,10 @@ public static DatasourceAngles from(OutputImplementations outputImplementations, var crudTables = myBatisStatements.filterRelationOn(myBatisStatement -> { MyBatisStatementId myBatisStatementId = myBatisStatement.myBatisStatementId(); + boolean matchesSelf = outputImplementation.outputPortGateway().jigMethodId().namespace().equals(myBatisStatementId.namespace()) + && outputImplementation.outputPortGateway().name().equals(myBatisStatementId.id()); // namespaceはメソッドの型のFQNに該当し、idはメソッド名に該当するので、それを比較する。 - return outputImplementation.usingMethods() + return matchesSelf || outputImplementation.usingMethods() .containsAny(methodCall -> methodCall.methodOwner().fqn().equals(myBatisStatementId.namespace()) && methodCall.methodName().equals(myBatisStatementId.id())); }).crudTables(); diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/ReadStatus.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/ReadStatus.java index 2d72b0728..2a4a6d289 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/ReadStatus.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/ReadStatus.java @@ -13,8 +13,8 @@ public enum ReadStatus { "バイナリソース(*.class)が見つかりませんでした。出力ディレクトリの指定を確認してください。", "Binary Source file(*.class) was not found. Check the output directory specification."), SQLなし( - "SQLが見つかりませんでした。SQLを実装していない場合やMyBatisを使用していない場合は正常です。CRUDに関わる情報が出力されません。", - "SQL was not found. It is normal if you do not implement SQL or if you are not using MyBatis. If this message appears, CRUD is not output in the data source list."), + "SQLが見つかりませんでした。SQLを実装していない場合やMyBatis・Spring Data JDBCを使用していない場合は正常です。CRUDに関わる情報が出力されません。", + "SQL was not found. It is normal if you do not implement SQL or if you are not using MyBatis/Spring Data JDBC. If this message appears, CRUD is not output in the data source list."), SQL読み込み一部失敗( "SQLの読み込みに一部失敗しました。CRUDの出力に欠落が存在します。", "Partial loading of SQL failed. There is a missing in the output of CRUD."), diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java index cbd9638fb..c64b553d2 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java @@ -17,16 +17,19 @@ import org.dddjava.jig.domain.model.sources.filesystem.SourceBasePaths; import org.dddjava.jig.domain.model.sources.javasources.JavaSourceModel; import org.dddjava.jig.domain.model.sources.mybatis.MyBatisStatementsReader; +import org.dddjava.jig.domain.model.sources.mybatis.SqlReadStatus; import org.dddjava.jig.infrastructure.asm.AsmClassSourceReader; import org.dddjava.jig.infrastructure.asm.ClassDeclaration; import org.dddjava.jig.infrastructure.configuration.Configuration; import org.dddjava.jig.infrastructure.javaparser.JavaparserReader; import org.dddjava.jig.infrastructure.mybatis.MyBatisStatementsReaderImpl; +import org.dddjava.jig.infrastructure.springdatajdbc.SpringDataJdbcStatementsReader; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; public class DefaultJigRepositoryFactory { @@ -35,15 +38,17 @@ public class DefaultJigRepositoryFactory { private final AsmClassSourceReader asmClassSourceReader; private final JavaparserReader javaparserReader; private final MyBatisStatementsReader myBatisStatementsReader; + private final SpringDataJdbcStatementsReader springDataJdbcStatementsReader; private final JigEventRepository jigEventRepository; private final GlossaryRepository glossaryRepository; - public DefaultJigRepositoryFactory(ClassOrJavaSourceCollector sourceCollector, AsmClassSourceReader asmClassSourceReader, JavaparserReader javaparserReader, MyBatisStatementsReader myBatisStatementsReader, JigEventRepository jigEventRepository, GlossaryRepository glossaryRepository) { + public DefaultJigRepositoryFactory(ClassOrJavaSourceCollector sourceCollector, AsmClassSourceReader asmClassSourceReader, JavaparserReader javaparserReader, MyBatisStatementsReader myBatisStatementsReader, SpringDataJdbcStatementsReader springDataJdbcStatementsReader, JigEventRepository jigEventRepository, GlossaryRepository glossaryRepository) { this.sourceCollector = sourceCollector; this.asmClassSourceReader = asmClassSourceReader; this.javaparserReader = javaparserReader; this.myBatisStatementsReader = myBatisStatementsReader; + this.springDataJdbcStatementsReader = springDataJdbcStatementsReader; this.glossaryRepository = glossaryRepository; this.jigEventRepository = jigEventRepository; } @@ -54,6 +59,7 @@ public static DefaultJigRepositoryFactory init(Configuration configuration) { new AsmClassSourceReader(), new JavaparserReader(), new MyBatisStatementsReaderImpl(), + new SpringDataJdbcStatementsReader(), configuration.jigEventRepository(), configuration.glossaryRepository() ); } @@ -148,10 +154,24 @@ private MyBatisStatements readMyBatisStatements(FilesystemSources sources, Colle List classPaths = sources.sourceBasePaths().classSourceBasePaths(); var myBatisReadResult = myBatisStatementsReader.readFrom(jigTypeHeaders, classPaths); + MyBatisStatements springDataJdbcStatements = springDataJdbcStatementsReader.readFrom(jigTypeHeaders, classPaths); - if (myBatisReadResult.status().not正常()) { + MyBatisStatements mergedStatements = mergeStatements(myBatisReadResult.myBatisStatements(), springDataJdbcStatements); + + SqlReadStatus sqlReadStatus = myBatisReadResult.status(); + if (sqlReadStatus == SqlReadStatus.SQLなし && mergedStatements.isEmpty()) { + jigEventRepository.recordEvent(sqlReadStatus.toReadStatus()); + } else if (sqlReadStatus != SqlReadStatus.成功 && sqlReadStatus != SqlReadStatus.SQLなし) { jigEventRepository.recordEvent(myBatisReadResult.status().toReadStatus()); } - return myBatisReadResult.myBatisStatements(); + return mergedStatements; + } + + private MyBatisStatements mergeStatements(MyBatisStatements myBatisStatements, MyBatisStatements springDataJdbcStatements) { + return new MyBatisStatements(Stream.concat( + myBatisStatements.list().stream(), + springDataJdbcStatements.list().stream()) + .distinct() + .toList()); } } diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java new file mode 100644 index 000000000..38e495574 --- /dev/null +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -0,0 +1,213 @@ +package org.dddjava.jig.infrastructure.springdatajdbc; + +import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatement; +import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatementId; +import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; +import org.dddjava.jig.domain.model.data.rdbaccess.Query; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; +import org.dddjava.jig.domain.model.data.types.JigTypeHeader; +import org.dddjava.jig.domain.model.data.types.TypeId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Stream; + +public class SpringDataJdbcStatementsReader { + private static final Logger logger = LoggerFactory.getLogger(SpringDataJdbcStatementsReader.class); + + private static final String REPOSITORY_ANNOTATION = "org.springframework.stereotype.Repository"; + private static final String SPRING_DATA_REPOSITORY = "org.springframework.data.repository.Repository"; + private static final String SPRING_DATA_QUERY = "org.springframework.data.jdbc.repository.query.Query"; + private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; + + public MyBatisStatements readFrom(Collection jigTypeHeaders, List classPaths) { + Collection classNames = jigTypeHeaders.stream() + .filter(jigTypeHeader -> jigTypeHeader.jigTypeAttributes() + .declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION))) + .map(JigTypeHeader::fqn) + .toList(); + + if (classNames.isEmpty()) return MyBatisStatements.empty(); + + URL[] classLocationUrls = classPaths.stream() + .flatMap(path -> { + try { + return Stream.of(path.toUri().toURL()); + } catch (MalformedURLException e) { + logger.warn("classPath({})は読み飛ばします", path, e); + return Stream.empty(); + } + }) + .toArray(URL[]::new); + + try (URLClassLoader classLoader = new URLClassLoader(classLocationUrls, ClassLoader.getSystemClassLoader())) { + Class repositoryInterface = loadClassIfPresent(classLoader, SPRING_DATA_REPOSITORY).orElse(null); + if (repositoryInterface == null) return MyBatisStatements.empty(); + + Map statements = new LinkedHashMap<>(); + for (String className : classNames) { + Class repositoryType = Class.forName(className, false, classLoader); + if (!repositoryType.isInterface()) continue; + if (!repositoryInterface.isAssignableFrom(repositoryType)) continue; + + Optional tableName = resolveTableName(repositoryType); + extractStatements(repositoryType, tableName).forEach(statement -> + statements.put(statement.myBatisStatementId(), statement)); + } + return new MyBatisStatements(List.copyOf(statements.values())); + } catch (Exception e) { + logger.warn("Spring Data JDBC の読み取りに失敗しました。", e); + return MyBatisStatements.empty(); + } + } + + private Stream extractStatements(Class repositoryType, Optional tableName) { + return Arrays.stream(repositoryType.getMethods()) + .filter(method -> method.getDeclaringClass() != Object.class) + .flatMap(method -> createStatement(repositoryType, method, tableName).stream()); + } + + private Optional createStatement(Class repositoryType, Method method, Optional tableName) { + SqlType sqlType = inferSqlType(method).orElse(null); + if (sqlType == null) return Optional.empty(); + + Query query = readQuery(method).orElseGet(() -> + tableName.map(name -> Query.from(defaultQuery(sqlType, name))).orElse(Query.unsupported())); + + MyBatisStatementId statementId = MyBatisStatementId.from(repositoryType.getCanonicalName() + "." + method.getName()); + return Optional.of(new MyBatisStatement(statementId, query, sqlType)); + } + + private Optional inferSqlType(Method method) { + String methodName = method.getName().toLowerCase(Locale.ROOT); + + if (methodName.startsWith("find") + || methodName.startsWith("read") + || methodName.startsWith("get") + || methodName.startsWith("query") + || methodName.startsWith("count") + || methodName.startsWith("exists")) { + return Optional.of(SqlType.SELECT); + } + if (methodName.startsWith("save") + || methodName.startsWith("insert") + || methodName.startsWith("create") + || methodName.startsWith("add")) { + return Optional.of(SqlType.INSERT); + } + if (methodName.startsWith("update") + || methodName.startsWith("set")) { + return Optional.of(SqlType.UPDATE); + } + if (methodName.startsWith("delete") + || methodName.startsWith("remove")) { + return Optional.of(SqlType.DELETE); + } + return Optional.empty(); + } + + private Optional readQuery(Method method) { + return Arrays.stream(method.getAnnotations()) + .filter(annotation -> annotation.annotationType().getName().equals(SPRING_DATA_QUERY)) + .findFirst() + .flatMap(annotation -> { + try { + Method valueMethod = annotation.annotationType().getMethod("value"); + Object value = valueMethod.invoke(annotation); + if (!(value instanceof String query)) return Optional.empty(); + if (query.isBlank()) return Optional.empty(); + return Optional.of(Query.from(query)); + } catch (ReflectiveOperationException e) { + logger.debug("Queryアノテーションの読み取りに失敗しました。method={}", method, e); + return Optional.empty(); + } + }); + } + + private Optional resolveTableName(Class repositoryType) { + Optional> entityType = resolveEntityType(repositoryType, new HashSet<>()); + if (entityType.isEmpty()) return Optional.empty(); + + Annotation[] annotations = entityType.orElseThrow().getAnnotations(); + for (Annotation annotation : annotations) { + if (!annotation.annotationType().getName().equals(SPRING_DATA_TABLE)) continue; + try { + Method valueMethod = annotation.annotationType().getMethod("value"); + Object value = valueMethod.invoke(annotation); + if (value instanceof String tableName && !tableName.isBlank()) { + return Optional.of(tableName); + } + } catch (ReflectiveOperationException e) { + logger.debug("Tableアノテーションの読み取りに失敗しました。entity={}", entityType.orElseThrow(), e); + } + } + return Optional.of(toSnakeCase(entityType.orElseThrow().getSimpleName())); + } + + private Optional> resolveEntityType(Class repositoryType, Set> visited) { + if (!visited.add(repositoryType)) return Optional.empty(); + + for (Type genericInterface : repositoryType.getGenericInterfaces()) { + Optional> entityType = resolveEntityType(genericInterface, visited); + if (entityType.isPresent()) return entityType; + } + return Optional.empty(); + } + + private Optional> resolveEntityType(Type type, Set> visited) { + if (type instanceof ParameterizedType parameterizedType) { + Type rawType = parameterizedType.getRawType(); + if (rawType instanceof Class rawClass + && rawClass.getName().startsWith("org.springframework.data.repository.")) { + Type[] arguments = parameterizedType.getActualTypeArguments(); + if (arguments.length > 0 && arguments[0] instanceof Class entityType) return Optional.of(entityType); + } + if (rawType instanceof Class rawClass) { + Optional> entityType = resolveEntityType(rawClass, visited); + if (entityType.isPresent()) return entityType; + } + return Optional.empty(); + } + if (type instanceof Class interfaceType) { + return resolveEntityType(interfaceType, visited); + } + return Optional.empty(); + } + + private static Optional> loadClassIfPresent(ClassLoader classLoader, String className) { + try { + return Optional.of(Class.forName(className, false, classLoader)); + } catch (ClassNotFoundException ignored) { + return Optional.empty(); + } + } + + private String defaultQuery(SqlType sqlType, String tableName) { + return switch (sqlType) { + case INSERT -> "insert into " + tableName + " values (?)"; + case SELECT -> "select * from " + tableName; + case UPDATE -> "update " + tableName + " set id = id"; + case DELETE -> "delete from " + tableName; + }; + } + + private String toSnakeCase(String text) { + StringBuilder builder = new StringBuilder(text.length() + 4); + for (int i = 0; i < text.length(); i++) { + char current = text.charAt(i); + if (Character.isUpperCase(current) && i > 0) builder.append('_'); + builder.append(Character.toLowerCase(current)); + } + return builder.toString(); + } +} diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java new file mode 100644 index 000000000..007bf3b03 --- /dev/null +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java @@ -0,0 +1,62 @@ +package org.dddjava.jig.infrastructure.springdatajdbc; + +import org.dddjava.jig.application.JigService; +import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatementId; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; +import org.dddjava.jig.domain.model.information.JigRepository; +import org.junit.jupiter.api.Test; +import stub.infrastructure.datasource.springdata.SpringDataJdbcOrderRepository; +import testing.JigTest; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@JigTest +class SpringDataJdbcStatementReaderTest { + + @Test + void SpringDataJdbcのRepositoryメソッドをSQLとして取得できる(JigRepository jigRepository) { + var statements = jigRepository.jigDataProvider().fetchMybatisStatements(); + var namespace = SpringDataJdbcOrderRepository.class.getCanonicalName(); + + var save = statements.findById(MyBatisStatementId.from(namespace + ".save")).orElseThrow(); + assertEquals("[spring_data_jdbc_orders]", save.tables().asText()); + assertEquals(SqlType.INSERT, save.sqlType()); + + var findById = statements.findById(MyBatisStatementId.from(namespace + ".findById")).orElseThrow(); + assertEquals("[spring_data_jdbc_orders]", findById.tables().asText()); + assertEquals(SqlType.SELECT, findById.sqlType()); + + var deleteById = statements.findById(MyBatisStatementId.from(namespace + ".deleteById")).orElseThrow(); + assertEquals("[spring_data_jdbc_orders]", deleteById.tables().asText()); + assertEquals(SqlType.DELETE, deleteById.sqlType()); + } + + @Test + void DatasourceAnglesにSpringDataJdbcのCRUDが反映される(JigService jigService, JigRepository jigRepository) { + var datasourceAngles = jigService.datasourceAngles(jigRepository).list().stream() + .filter(angle -> angle.declaringType().fqn().equals(SpringDataJdbcOrderRepository.class.getCanonicalName())) + .toList(); + + assertEquals(4, datasourceAngles.size()); + assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() + .filter(angle -> angle.interfaceMethod().name().equals("save")) + .findFirst() + .orElseThrow() + .insertTables()); + assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() + .filter(angle -> angle.interfaceMethod().name().equals("findById")) + .findFirst() + .orElseThrow() + .selectTables()); + assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() + .filter(angle -> angle.interfaceMethod().name().equals("updateById")) + .findFirst() + .orElseThrow() + .updateTables()); + assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() + .filter(angle -> angle.interfaceMethod().name().equals("deleteById")) + .findFirst() + .orElseThrow() + .deleteTables()); + } +} diff --git a/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrder.java b/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrder.java new file mode 100644 index 000000000..19d853e08 --- /dev/null +++ b/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrder.java @@ -0,0 +1,9 @@ +package stub.infrastructure.datasource.springdata; + +import org.springframework.data.relational.core.mapping.Table; + +@Table("spring_data_jdbc_orders") +public class SpringDataJdbcOrder { + + Long id; +} diff --git a/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java b/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java new file mode 100644 index 000000000..d419aa7ca --- /dev/null +++ b/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java @@ -0,0 +1,21 @@ +package stub.infrastructure.datasource.springdata; + +import org.springframework.data.jdbc.repository.query.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SpringDataJdbcOrderRepository extends CrudRepository { + + @Override + SpringDataJdbcOrder save(SpringDataJdbcOrder entity); + + @Override + SpringDataJdbcOrder findById(Long id); + + @Override + void deleteById(Long id); + + @Query("update spring_data_jdbc_orders set id = :id where id = :id") + void updateById(Long id); +} diff --git a/jig-core/src/test/java/stub/org/springframework/data/jdbc/repository/query/Query.java b/jig-core/src/test/java/stub/org/springframework/data/jdbc/repository/query/Query.java new file mode 100644 index 000000000..4944f88b3 --- /dev/null +++ b/jig-core/src/test/java/stub/org/springframework/data/jdbc/repository/query/Query.java @@ -0,0 +1,12 @@ +package org.springframework.data.jdbc.repository.query; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Query { + String value(); +} diff --git a/jig-core/src/test/java/stub/org/springframework/data/relational/core/mapping/Table.java b/jig-core/src/test/java/stub/org/springframework/data/relational/core/mapping/Table.java new file mode 100644 index 000000000..bbbbae751 --- /dev/null +++ b/jig-core/src/test/java/stub/org/springframework/data/relational/core/mapping/Table.java @@ -0,0 +1,12 @@ +package org.springframework.data.relational.core.mapping; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Table { + String value() default ""; +} diff --git a/jig-core/src/test/java/stub/org/springframework/data/repository/CrudRepository.java b/jig-core/src/test/java/stub/org/springframework/data/repository/CrudRepository.java new file mode 100644 index 000000000..603f376d4 --- /dev/null +++ b/jig-core/src/test/java/stub/org/springframework/data/repository/CrudRepository.java @@ -0,0 +1,10 @@ +package org.springframework.data.repository; + +public interface CrudRepository extends Repository { + + S save(S entity); + + T findById(ID id); + + void deleteById(ID id); +} diff --git a/jig-core/src/test/java/stub/org/springframework/data/repository/Repository.java b/jig-core/src/test/java/stub/org/springframework/data/repository/Repository.java new file mode 100644 index 000000000..7070fdaec --- /dev/null +++ b/jig-core/src/test/java/stub/org/springframework/data/repository/Repository.java @@ -0,0 +1,4 @@ +package org.springframework.data.repository; + +public interface Repository { +} From 43920e1156cde986ad1580398e98e29f01fb8068 Mon Sep 17 00:00:00 2001 From: irof Date: Fri, 20 Feb 2026 22:11:51 +0900 Subject: [PATCH 02/22] =?UTF-8?q?refactor:=20=E5=A4=89=E3=81=AA=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E3=81=AB=E4=BD=9C=E3=81=A3=E3=81=A6=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/springframework/data/jdbc/repository/query/Query.java | 0 .../org/springframework/data/relational/core/mapping/Table.java | 0 .../org/springframework/data/repository/CrudRepository.java | 0 .../org/springframework/data/repository/Repository.java | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename jig-core/src/test/java/{stub => }/org/springframework/data/jdbc/repository/query/Query.java (100%) rename jig-core/src/test/java/{stub => }/org/springframework/data/relational/core/mapping/Table.java (100%) rename jig-core/src/test/java/{stub => }/org/springframework/data/repository/CrudRepository.java (100%) rename jig-core/src/test/java/{stub => }/org/springframework/data/repository/Repository.java (100%) diff --git a/jig-core/src/test/java/stub/org/springframework/data/jdbc/repository/query/Query.java b/jig-core/src/test/java/org/springframework/data/jdbc/repository/query/Query.java similarity index 100% rename from jig-core/src/test/java/stub/org/springframework/data/jdbc/repository/query/Query.java rename to jig-core/src/test/java/org/springframework/data/jdbc/repository/query/Query.java diff --git a/jig-core/src/test/java/stub/org/springframework/data/relational/core/mapping/Table.java b/jig-core/src/test/java/org/springframework/data/relational/core/mapping/Table.java similarity index 100% rename from jig-core/src/test/java/stub/org/springframework/data/relational/core/mapping/Table.java rename to jig-core/src/test/java/org/springframework/data/relational/core/mapping/Table.java diff --git a/jig-core/src/test/java/stub/org/springframework/data/repository/CrudRepository.java b/jig-core/src/test/java/org/springframework/data/repository/CrudRepository.java similarity index 100% rename from jig-core/src/test/java/stub/org/springframework/data/repository/CrudRepository.java rename to jig-core/src/test/java/org/springframework/data/repository/CrudRepository.java diff --git a/jig-core/src/test/java/stub/org/springframework/data/repository/Repository.java b/jig-core/src/test/java/org/springframework/data/repository/Repository.java similarity index 100% rename from jig-core/src/test/java/stub/org/springframework/data/repository/Repository.java rename to jig-core/src/test/java/org/springframework/data/repository/Repository.java From 4057a225edbc69b2f88802531509c17420b42291 Mon Sep 17 00:00:00 2001 From: irof Date: Fri, 20 Feb 2026 22:14:37 +0900 Subject: [PATCH 03/22] =?UTF-8?q?docs:=20=E4=BD=95=E8=80=85=E3=81=8B?= =?UTF-8?q?=E3=82=92=E6=9B=B8=E3=81=84=E3=81=A6=E3=81=8A=E3=81=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/org/springframework/data/package-info.java | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 jig-core/src/test/java/org/springframework/data/package-info.java diff --git a/jig-core/src/test/java/org/springframework/data/package-info.java b/jig-core/src/test/java/org/springframework/data/package-info.java new file mode 100644 index 000000000..e47029ae9 --- /dev/null +++ b/jig-core/src/test/java/org/springframework/data/package-info.java @@ -0,0 +1,6 @@ +/** + * SpringDataJdbc対応のテスト用 + * + * テストだけなら依存させていい気はする + */ +package org.springframework.data; \ No newline at end of file From 9fd8852dd6fef43332da92e6b1ef5823ea291849 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 21 Feb 2026 23:30:28 +0900 Subject: [PATCH 04/22] =?UTF-8?q?refactor:=20SpringDataJdbcStatementsReade?= =?UTF-8?q?r=E3=81=AE=E6=88=BB=E3=82=8A=E5=80=A4=E3=82=92=E4=B8=AD?= =?UTF-8?q?=E7=AB=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../DefaultJigRepositoryFactory.java | 7 ++++--- .../SpringDataJdbcStatementsReader.java | 12 +++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java index c64b553d2..db5a848f5 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java @@ -6,6 +6,7 @@ import org.dddjava.jig.application.GlossaryRepository; import org.dddjava.jig.application.JigEventRepository; import org.dddjava.jig.domain.model.data.JigDataProvider; +import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatement; import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; import org.dddjava.jig.domain.model.data.terms.Glossary; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; @@ -154,7 +155,7 @@ private MyBatisStatements readMyBatisStatements(FilesystemSources sources, Colle List classPaths = sources.sourceBasePaths().classSourceBasePaths(); var myBatisReadResult = myBatisStatementsReader.readFrom(jigTypeHeaders, classPaths); - MyBatisStatements springDataJdbcStatements = springDataJdbcStatementsReader.readFrom(jigTypeHeaders, classPaths); + var springDataJdbcStatements = springDataJdbcStatementsReader.readFrom(jigTypeHeaders, classPaths); MyBatisStatements mergedStatements = mergeStatements(myBatisReadResult.myBatisStatements(), springDataJdbcStatements); @@ -167,10 +168,10 @@ private MyBatisStatements readMyBatisStatements(FilesystemSources sources, Colle return mergedStatements; } - private MyBatisStatements mergeStatements(MyBatisStatements myBatisStatements, MyBatisStatements springDataJdbcStatements) { + private MyBatisStatements mergeStatements(MyBatisStatements myBatisStatements, List springDataJdbcStatements) { return new MyBatisStatements(Stream.concat( myBatisStatements.list().stream(), - springDataJdbcStatements.list().stream()) + springDataJdbcStatements.stream()) .distinct() .toList()); } diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index 38e495574..c55165146 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -2,7 +2,6 @@ import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatement; import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatementId; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; import org.dddjava.jig.domain.model.data.rdbaccess.Query; import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; @@ -10,7 +9,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; @@ -30,14 +28,14 @@ public class SpringDataJdbcStatementsReader { private static final String SPRING_DATA_QUERY = "org.springframework.data.jdbc.repository.query.Query"; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; - public MyBatisStatements readFrom(Collection jigTypeHeaders, List classPaths) { + public List readFrom(Collection jigTypeHeaders, List classPaths) { Collection classNames = jigTypeHeaders.stream() .filter(jigTypeHeader -> jigTypeHeader.jigTypeAttributes() .declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION))) .map(JigTypeHeader::fqn) .toList(); - if (classNames.isEmpty()) return MyBatisStatements.empty(); + if (classNames.isEmpty()) return List.of(); URL[] classLocationUrls = classPaths.stream() .flatMap(path -> { @@ -52,7 +50,7 @@ public MyBatisStatements readFrom(Collection jigTypeHeaders, List try (URLClassLoader classLoader = new URLClassLoader(classLocationUrls, ClassLoader.getSystemClassLoader())) { Class repositoryInterface = loadClassIfPresent(classLoader, SPRING_DATA_REPOSITORY).orElse(null); - if (repositoryInterface == null) return MyBatisStatements.empty(); + if (repositoryInterface == null) return List.of(); Map statements = new LinkedHashMap<>(); for (String className : classNames) { @@ -64,10 +62,10 @@ public MyBatisStatements readFrom(Collection jigTypeHeaders, List extractStatements(repositoryType, tableName).forEach(statement -> statements.put(statement.myBatisStatementId(), statement)); } - return new MyBatisStatements(List.copyOf(statements.values())); + return List.copyOf(statements.values()); } catch (Exception e) { logger.warn("Spring Data JDBC の読み取りに失敗しました。", e); - return MyBatisStatements.empty(); + return List.of(); } } From 526c25c9acce8cc0b117352f117523a81a9a3a35 Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 21 Feb 2026 23:35:31 +0900 Subject: [PATCH 05/22] =?UTF-8?q?refactor:=20SQL=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=83=A1=E3=83=B3=E3=83=88=E5=9E=8B=E5=90=8D?= =?UTF-8?q?=E3=82=92=E4=B8=AD=E7=AB=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../InfrastructureQueryService.java | 2 +- .../domain/model/data/JigDataProvider.java | 8 +-- .../data/rdbaccess/MyBatisStatementId.java | 60 ------------------- ...yBatisStatement.java => SqlStatement.java} | 4 +- .../model/data/rdbaccess/SqlStatementId.java | 41 +++++++++++++ ...atisStatements.java => SqlStatements.java} | 22 +++---- .../domain/model/data/rdbaccess/SqlType.java | 6 +- .../datasource/DatasourceAngles.java | 18 +++--- .../sources/mybatis/MyBatisReadResult.java | 8 +-- .../DefaultJigDataProvider.java | 8 +-- .../DefaultJigRepositoryFactory.java | 16 ++--- .../mybatis/MyBatisStatementsReaderImpl.java | 10 ++-- .../SpringDataJdbcStatementsReader.java | 18 +++--- .../mybatis/MyBatisStatementReaderTest.java | 22 +++---- .../SpringDataJdbcStatementReaderTest.java | 10 ++-- 15 files changed, 117 insertions(+), 136 deletions(-) delete mode 100644 jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatementId.java rename jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/{MyBatisStatement.java => SqlStatement.java} (56%) create mode 100644 jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java rename jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/{MyBatisStatements.java => SqlStatements.java} (52%) diff --git a/jig-core/src/main/java/org/dddjava/jig/application/InfrastructureQueryService.java b/jig-core/src/main/java/org/dddjava/jig/application/InfrastructureQueryService.java index 809a5f96b..edaac8fd9 100644 --- a/jig-core/src/main/java/org/dddjava/jig/application/InfrastructureQueryService.java +++ b/jig-core/src/main/java/org/dddjava/jig/application/InfrastructureQueryService.java @@ -29,6 +29,6 @@ public OutputImplementations outputImplementations(JigRepository jigRepository) public DatasourceAngles datasourceAngles(JigRepository jigRepository) { var jigTypes = typesQueryService.jigTypes(jigRepository); var outputImplementations = outputImplementations(jigRepository); - return DatasourceAngles.from(outputImplementations, jigRepository.jigDataProvider().fetchMybatisStatements(), MethodRelations.from(jigTypes)); + return DatasourceAngles.from(outputImplementations, jigRepository.jigDataProvider().fetchSqlStatements(), MethodRelations.from(jigTypes)); } } diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/JigDataProvider.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/JigDataProvider.java index 92b0ab387..e93d3f0e9 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/JigDataProvider.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/JigDataProvider.java @@ -1,7 +1,7 @@ package org.dddjava.jig.domain.model.data; import org.dddjava.jig.domain.model.data.enums.EnumModels; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import java.util.List; @@ -10,8 +10,8 @@ public interface JigDataProvider { static JigDataProvider none() { return new JigDataProvider() { @Override - public MyBatisStatements fetchMybatisStatements() { - return MyBatisStatements.empty(); + public SqlStatements fetchSqlStatements() { + return SqlStatements.empty(); } @Override @@ -21,7 +21,7 @@ public EnumModels fetchEnumModels() { }; } - MyBatisStatements fetchMybatisStatements(); + SqlStatements fetchSqlStatements(); EnumModels fetchEnumModels(); } diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatementId.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatementId.java deleted file mode 100644 index 319d8a1a4..000000000 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatementId.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.dddjava.jig.domain.model.data.rdbaccess; - -import java.util.Objects; - -/** - * MyBatisのステートメントID - * - * namespaceとidを.で連結したもの。 - * - * 以下のMapperXMLとMapperインタフェースの場合、ステートメントIDは `com.example.mybatis.ExampleMapper.selectAll` となります。 - *
- * {@code
- * 
- *     
- * 
- * }
- * 
- *
- * {@code
- * package com.example.mybatis;
- * interface ExampleMapper {
- *     List selectAll();
- * }
- * }
- * 
- */ -public record MyBatisStatementId(String value, String namespace, String id) { - - public static MyBatisStatementId from(String value) { - var namespaceIdSeparateIndex = value.lastIndexOf('.'); - if (namespaceIdSeparateIndex != -1) { - return new MyBatisStatementId(value, value.substring(0, namespaceIdSeparateIndex), value.substring(namespaceIdSeparateIndex + 1)); - } else { - return new MyBatisStatementId(value, "", value); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MyBatisStatementId that = (MyBatisStatementId) o; - return Objects.equals(value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - public String namespace() { - return namespace; - } - - public String id() { - return id; - } -} diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatement.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatement.java similarity index 56% rename from jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatement.java rename to jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatement.java index 593ba85dc..e665768b6 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatement.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatement.java @@ -3,11 +3,11 @@ /** * SQL */ -public record MyBatisStatement(MyBatisStatementId myBatisStatementId, Query query, SqlType sqlType) { +public record SqlStatement(SqlStatementId sqlStatementId, Query query, SqlType sqlType) { public Tables tables() { if (query.supported()) { - Table table = sqlType.extractTable(query.text(), myBatisStatementId); + Table table = sqlType.extractTable(query.text(), sqlStatementId); return new Tables(table); } return new Tables(sqlType.unexpectedTable()); diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java new file mode 100644 index 000000000..71621663c --- /dev/null +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java @@ -0,0 +1,41 @@ +package org.dddjava.jig.domain.model.data.rdbaccess; + +import java.util.Objects; + +/** + * SQLステートメントID + * + * namespaceとidを.で連結したもの。 + */ +public record SqlStatementId(String value, String namespace, String id) { + + public static SqlStatementId from(String value) { + var namespaceIdSeparateIndex = value.lastIndexOf('.'); + if (namespaceIdSeparateIndex != -1) { + return new SqlStatementId(value, value.substring(0, namespaceIdSeparateIndex), value.substring(namespaceIdSeparateIndex + 1)); + } else { + return new SqlStatementId(value, "", value); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SqlStatementId that = (SqlStatementId) o; + return Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + public String namespace() { + return namespace; + } + + public String id() { + return id; + } +} diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatements.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatements.java similarity index 52% rename from jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatements.java rename to jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatements.java index 5e8cdaebd..183457017 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/MyBatisStatements.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatements.java @@ -8,34 +8,34 @@ /** * SQL一覧 */ -public record MyBatisStatements(List list) { +public record SqlStatements(List list) { - public static MyBatisStatements empty() { - return new MyBatisStatements(Collections.emptyList()); + public static SqlStatements empty() { + return new SqlStatements(Collections.emptyList()); } private Tables tables(SqlType sqlType) { return list.stream() - .filter(myBatisStatement -> myBatisStatement.sqlType() == sqlType) - .map(MyBatisStatement::tables) + .filter(sqlStatement -> sqlStatement.sqlType() == sqlType) + .map(SqlStatement::tables) .reduce(Tables::merge) .orElse(Tables.nothing()); } - public Optional findById(MyBatisStatementId myBatisStatementId) { + public Optional findById(SqlStatementId sqlStatementId) { return list.stream() - .filter(myBatisStatement -> myBatisStatement.myBatisStatementId().equals(myBatisStatementId)) + .filter(sqlStatement -> sqlStatement.sqlStatementId().equals(sqlStatementId)) .findFirst(); } /** * 引数のメソッドに関連するステートメントに絞り込む */ - public MyBatisStatements filterRelationOn(Predicate myBatisStatementPredicate) { - List myBatisStatements = list.stream() - .filter(myBatisStatementPredicate) + public SqlStatements filterRelationOn(Predicate sqlStatementPredicate) { + List sqlStatements = list.stream() + .filter(sqlStatementPredicate) .toList(); - return new MyBatisStatements(myBatisStatements); + return new SqlStatements(sqlStatements); } public boolean isEmpty() { diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java index 3fc7f4b09..0c3e61387 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java @@ -33,7 +33,7 @@ public enum SqlType { * 現在は1テーブルのみ対応 * 複問い合わせやWITHなどは未対応 */ - public Table extractTable(String sql, MyBatisStatementId myBatisStatementId) { + public Table extractTable(String sql, SqlStatementId sqlStatementId) { for (Pattern pattern : patterns) { Matcher matcher = pattern.matcher(sql.replaceAll("\n", " ")); if (matcher.matches()) { @@ -43,8 +43,8 @@ public Table extractTable(String sql, MyBatisStatementId myBatisStatementId) { logger.warn("{} {} を {} としてテーブル名が解析できませんでした。このMapper由来の解析結果はドキュメントに出力されません。" + "MyBatisの動的なSQLなどは完全に再現できません。JIGが認識しているSQL文=[{}]", - myBatisStatementId.namespace(), - myBatisStatementId.id(), + sqlStatementId.namespace(), + sqlStatementId.id(), this, sql); return unexpectedTable(); } diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java index 008f0c339..5ec626bc7 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java @@ -1,7 +1,7 @@ package org.dddjava.jig.domain.model.knowledge.datasource; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatementId; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import org.dddjava.jig.domain.model.information.members.CallerMethods; import org.dddjava.jig.domain.model.information.outputs.OutputImplementations; import org.dddjava.jig.domain.model.information.relation.methods.CallerMethodsFactory; @@ -14,19 +14,19 @@ */ public record DatasourceAngles(List list) { - public static DatasourceAngles from(OutputImplementations outputImplementations, MyBatisStatements myBatisStatements, CallerMethodsFactory callerMethodsFactory) { + public static DatasourceAngles from(OutputImplementations outputImplementations, SqlStatements sqlStatements, CallerMethodsFactory callerMethodsFactory) { return new DatasourceAngles(outputImplementations.stream() .map(outputImplementation -> { CallerMethods callerMethods = callerMethodsFactory.callerMethodsOf(outputImplementation.outputPortGateway().jigMethodId()); - var crudTables = myBatisStatements.filterRelationOn(myBatisStatement -> { - MyBatisStatementId myBatisStatementId = myBatisStatement.myBatisStatementId(); - boolean matchesSelf = outputImplementation.outputPortGateway().jigMethodId().namespace().equals(myBatisStatementId.namespace()) - && outputImplementation.outputPortGateway().name().equals(myBatisStatementId.id()); + var crudTables = sqlStatements.filterRelationOn(sqlStatement -> { + SqlStatementId sqlStatementId = sqlStatement.sqlStatementId(); + boolean matchesSelf = outputImplementation.outputPortGateway().jigMethodId().namespace().equals(sqlStatementId.namespace()) + && outputImplementation.outputPortGateway().name().equals(sqlStatementId.id()); // namespaceはメソッドの型のFQNに該当し、idはメソッド名に該当するので、それを比較する。 return matchesSelf || outputImplementation.usingMethods() - .containsAny(methodCall -> methodCall.methodOwner().fqn().equals(myBatisStatementId.namespace()) - && methodCall.methodName().equals(myBatisStatementId.id())); + .containsAny(methodCall -> methodCall.methodOwner().fqn().equals(sqlStatementId.namespace()) + && methodCall.methodName().equals(sqlStatementId.id())); }).crudTables(); return new DatasourceAngle(outputImplementation, crudTables, callerMethods); diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/mybatis/MyBatisReadResult.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/mybatis/MyBatisReadResult.java index acddbad8e..6693cdc06 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/mybatis/MyBatisReadResult.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/sources/mybatis/MyBatisReadResult.java @@ -1,15 +1,15 @@ package org.dddjava.jig.domain.model.sources.mybatis; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; -public record MyBatisReadResult(MyBatisStatements myBatisStatements, SqlReadStatus sqlReadStatus) { +public record MyBatisReadResult(SqlStatements sqlStatements, SqlReadStatus sqlReadStatus) { public MyBatisReadResult(SqlReadStatus sqlReadStatus) { - this(MyBatisStatements.empty(), sqlReadStatus); + this(SqlStatements.empty(), sqlReadStatus); } public SqlReadStatus status() { - if (sqlReadStatus == SqlReadStatus.成功 && myBatisStatements.isEmpty()) { + if (sqlReadStatus == SqlReadStatus.成功 && sqlStatements.isEmpty()) { return SqlReadStatus.SQLなし; } return sqlReadStatus; diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigDataProvider.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigDataProvider.java index 16aff3b6e..71423af86 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigDataProvider.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigDataProvider.java @@ -2,15 +2,15 @@ import org.dddjava.jig.domain.model.data.JigDataProvider; import org.dddjava.jig.domain.model.data.enums.EnumModels; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import org.dddjava.jig.domain.model.sources.javasources.JavaSourceModel; record DefaultJigDataProvider(JavaSourceModel javaSourceModel, - MyBatisStatements myBatisStatements) implements JigDataProvider { + SqlStatements sqlStatements) implements JigDataProvider { @Override - public MyBatisStatements fetchMybatisStatements() { - return myBatisStatements; + public SqlStatements fetchSqlStatements() { + return sqlStatements; } @Override diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java index db5a848f5..d8a6e8142 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java @@ -6,8 +6,8 @@ import org.dddjava.jig.application.GlossaryRepository; import org.dddjava.jig.application.JigEventRepository; import org.dddjava.jig.domain.model.data.JigDataProvider; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatement; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatement; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import org.dddjava.jig.domain.model.data.terms.Glossary; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; import org.dddjava.jig.domain.model.information.JigRepository; @@ -109,11 +109,11 @@ private JigRepository analyze(FilesystemSources sources) { Metrics.timer(metricName, "phase", "class_file_parsing").record(() -> asmClassSourceReader.readClasses(sources.classFilePaths()))); - MyBatisStatements myBatisStatements = Objects.requireNonNull(Metrics.timer(metricName, "phase", "mybatis_reading").record(() -> + SqlStatements sqlStatements = Objects.requireNonNull(Metrics.timer(metricName, "phase", "mybatis_reading").record(() -> readMyBatisStatements(sources, classDeclarations))); return Metrics.timer(metricName, "phase", "jig_repository_creation").record(() -> { - DefaultJigDataProvider defaultJigDataProvider = new DefaultJigDataProvider(javaSourceModel, myBatisStatements); + DefaultJigDataProvider defaultJigDataProvider = new DefaultJigDataProvider(javaSourceModel, sqlStatements); JigTypes jigTypes = JigTypeFactory.createJigTypes(classDeclarations, glossaryRepository.all()); return new JigRepository() { @Override @@ -146,7 +146,7 @@ public JigResult.JigSummary summary() { })); } - private MyBatisStatements readMyBatisStatements(FilesystemSources sources, Collection classDeclarations) { + private SqlStatements readMyBatisStatements(FilesystemSources sources, Collection classDeclarations) { // MyBatisの読み込み対象となるMapperインタフェース識別のためにJigTypeHeaderを抽出 Collection jigTypeHeaders = classDeclarations.stream() .map(ClassDeclaration::jigTypeHeader) @@ -157,7 +157,7 @@ private MyBatisStatements readMyBatisStatements(FilesystemSources sources, Colle var myBatisReadResult = myBatisStatementsReader.readFrom(jigTypeHeaders, classPaths); var springDataJdbcStatements = springDataJdbcStatementsReader.readFrom(jigTypeHeaders, classPaths); - MyBatisStatements mergedStatements = mergeStatements(myBatisReadResult.myBatisStatements(), springDataJdbcStatements); + SqlStatements mergedStatements = mergeStatements(myBatisReadResult.sqlStatements(), springDataJdbcStatements); SqlReadStatus sqlReadStatus = myBatisReadResult.status(); if (sqlReadStatus == SqlReadStatus.SQLなし && mergedStatements.isEmpty()) { @@ -168,8 +168,8 @@ private MyBatisStatements readMyBatisStatements(FilesystemSources sources, Colle return mergedStatements; } - private MyBatisStatements mergeStatements(MyBatisStatements myBatisStatements, List springDataJdbcStatements) { - return new MyBatisStatements(Stream.concat( + private SqlStatements mergeStatements(SqlStatements myBatisStatements, List springDataJdbcStatements) { + return new SqlStatements(Stream.concat( myBatisStatements.list().stream(), springDataJdbcStatements.stream()) .distinct() diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java index 1e465c24e..a9dcf40c7 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java @@ -50,7 +50,7 @@ public MyBatisReadResult readFrom(Collection jigTypeHeaders, List .toList(); // 該当なしの場合に余計なClassLoader生成やMyBatisの初期化を行わないための早期リターン - if (classNames.isEmpty()) return new MyBatisReadResult(MyBatisStatements.empty(), SqlReadStatus.成功); + if (classNames.isEmpty()) return new MyBatisReadResult(SqlStatements.empty(), SqlReadStatus.成功); URL[] classLocationUrls = classPaths.stream() .flatMap(path -> { @@ -99,14 +99,14 @@ private MyBatisReadResult extractSql(Collection classNames, ClassLoader } } - List list = new ArrayList<>(); + List list = new ArrayList<>(); Collection mappedStatements = config.getMappedStatements(); logger.debug("MappedStatements: {}件", mappedStatements.size()); for (Object obj : mappedStatements) { // config.getMappedStatementsにAmbiguityが入っていることがあったので型を確認する if (obj instanceof MappedStatement mappedStatement) { - MyBatisStatementId myBatisStatementId = MyBatisStatementId.from(mappedStatement.getId()); + SqlStatementId myBatisStatementId = SqlStatementId.from(mappedStatement.getId()); Query query; try { @@ -130,13 +130,13 @@ private MyBatisReadResult extractSql(Collection classNames, ClassLoader yield SqlType.SELECT; } }; - MyBatisStatement myBatisStatement = new MyBatisStatement(myBatisStatementId, query, sqlType); + SqlStatement myBatisStatement = new SqlStatement(myBatisStatementId, query, sqlType); list.add(myBatisStatement); } } logger.debug("取得したSQL: {}件", list.size()); - return new MyBatisReadResult(new MyBatisStatements(list), sqlReadStatus); + return new MyBatisReadResult(new SqlStatements(list), sqlReadStatus); } private Query getQuery(MappedStatement mappedStatement) throws NoSuchFieldException, IllegalAccessException { diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index c55165146..9cc5d7c3a 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -1,7 +1,7 @@ package org.dddjava.jig.infrastructure.springdatajdbc; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatement; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatementId; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatement; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; import org.dddjava.jig.domain.model.data.rdbaccess.Query; import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; @@ -28,7 +28,7 @@ public class SpringDataJdbcStatementsReader { private static final String SPRING_DATA_QUERY = "org.springframework.data.jdbc.repository.query.Query"; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; - public List readFrom(Collection jigTypeHeaders, List classPaths) { + public List readFrom(Collection jigTypeHeaders, List classPaths) { Collection classNames = jigTypeHeaders.stream() .filter(jigTypeHeader -> jigTypeHeader.jigTypeAttributes() .declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION))) @@ -52,7 +52,7 @@ public List readFrom(Collection jigTypeHeaders, Class repositoryInterface = loadClassIfPresent(classLoader, SPRING_DATA_REPOSITORY).orElse(null); if (repositoryInterface == null) return List.of(); - Map statements = new LinkedHashMap<>(); + Map statements = new LinkedHashMap<>(); for (String className : classNames) { Class repositoryType = Class.forName(className, false, classLoader); if (!repositoryType.isInterface()) continue; @@ -60,7 +60,7 @@ public List readFrom(Collection jigTypeHeaders, Optional tableName = resolveTableName(repositoryType); extractStatements(repositoryType, tableName).forEach(statement -> - statements.put(statement.myBatisStatementId(), statement)); + statements.put(statement.sqlStatementId(), statement)); } return List.copyOf(statements.values()); } catch (Exception e) { @@ -69,21 +69,21 @@ public List readFrom(Collection jigTypeHeaders, } } - private Stream extractStatements(Class repositoryType, Optional tableName) { + private Stream extractStatements(Class repositoryType, Optional tableName) { return Arrays.stream(repositoryType.getMethods()) .filter(method -> method.getDeclaringClass() != Object.class) .flatMap(method -> createStatement(repositoryType, method, tableName).stream()); } - private Optional createStatement(Class repositoryType, Method method, Optional tableName) { + private Optional createStatement(Class repositoryType, Method method, Optional tableName) { SqlType sqlType = inferSqlType(method).orElse(null); if (sqlType == null) return Optional.empty(); Query query = readQuery(method).orElseGet(() -> tableName.map(name -> Query.from(defaultQuery(sqlType, name))).orElse(Query.unsupported())); - MyBatisStatementId statementId = MyBatisStatementId.from(repositoryType.getCanonicalName() + "." + method.getName()); - return Optional.of(new MyBatisStatement(statementId, query, sqlType)); + SqlStatementId statementId = SqlStatementId.from(repositoryType.getCanonicalName() + "." + method.getName()); + return Optional.of(new SqlStatement(statementId, query, sqlType)); } private Optional inferSqlType(Method method) { diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java index fd41323bf..84cfd49e9 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java @@ -1,8 +1,8 @@ package org.dddjava.jig.infrastructure.mybatis; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatement; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatementId; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatements; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatement; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; import org.dddjava.jig.domain.model.information.JigRepository; import org.junit.jupiter.api.Test; @@ -22,17 +22,17 @@ class MyBatisStatementReaderTest { @Test void bindを使ってても解析できる(JigRepository jigRepository) { - MyBatisStatements myBatisStatements = jigRepository.jigDataProvider().fetchMybatisStatements(); + SqlStatements myBatisStatements = jigRepository.jigDataProvider().fetchSqlStatements(); - MyBatisStatement myBatisStatement = myBatisStatements.findById(MyBatisStatementId.from(SampleMapper.class.getCanonicalName() + ".binding")).orElseThrow(); + SqlStatement myBatisStatement = myBatisStatements.findById(SqlStatementId.from(SampleMapper.class.getCanonicalName() + ".binding")).orElseThrow(); assertEquals("[fuga]", myBatisStatement.tables().asText()); } @Test void OGNLを使ったSELECTが解析できない(JigRepository jigRepository) { - MyBatisStatements myBatisStatements = jigRepository.jigDataProvider().fetchMybatisStatements(); + SqlStatements myBatisStatements = jigRepository.jigDataProvider().fetchSqlStatements(); - MyBatisStatement myBatisStatement = myBatisStatements.findById(MyBatisStatementId.from(ComplexMapper.class.getCanonicalName() + ".select_ognl")).orElseThrow(); + SqlStatement myBatisStatement = myBatisStatements.findById(SqlStatementId.from(ComplexMapper.class.getCanonicalName() + ".select_ognl")).orElseThrow(); assertEquals("[(解析失敗)]", myBatisStatement.tables().asText()); // OGNLを使ったSQLは現時点では空になる assertEquals("", myBatisStatement.query().text()); @@ -40,9 +40,9 @@ class MyBatisStatementReaderTest { @Test void OGNLを使ったSELECTが解析できない2(JigRepository jigRepository) { - MyBatisStatements myBatisStatements = jigRepository.jigDataProvider().fetchMybatisStatements(); + SqlStatements myBatisStatements = jigRepository.jigDataProvider().fetchSqlStatements(); - MyBatisStatement myBatisStatement = myBatisStatements.findById(MyBatisStatementId.from(ComplexMapper.class.getCanonicalName() + ".select_ognl_where")).orElseThrow(); + SqlStatement myBatisStatement = myBatisStatements.findById(SqlStatementId.from(ComplexMapper.class.getCanonicalName() + ".select_ognl_where")).orElseThrow(); assertEquals("[(解析失敗)]", myBatisStatement.tables().asText()); // OGNLを使ったSQLは現時点では空になる @@ -53,9 +53,9 @@ class MyBatisStatementReaderTest { @ParameterizedTest @MethodSource void 標準的なパターン(String methodName, String tableName, SqlType sqlType, JigRepository jigRepository) { - MyBatisStatements myBatisStatements = jigRepository.jigDataProvider().fetchMybatisStatements(); + SqlStatements myBatisStatements = jigRepository.jigDataProvider().fetchSqlStatements(); - MyBatisStatement myBatisStatement = myBatisStatements.findById(MyBatisStatementId.from("stub.infrastructure.datasource.CanonicalMapper." + methodName)).orElseThrow(); + SqlStatement myBatisStatement = myBatisStatements.findById(SqlStatementId.from("stub.infrastructure.datasource.CanonicalMapper." + methodName)).orElseThrow(); assertEquals("[" + tableName + "]", myBatisStatement.tables().asText()); assertEquals(sqlType, myBatisStatement.sqlType()); } diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java index 007bf3b03..b2277c93b 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java @@ -1,7 +1,7 @@ package org.dddjava.jig.infrastructure.springdatajdbc; import org.dddjava.jig.application.JigService; -import org.dddjava.jig.domain.model.data.rdbaccess.MyBatisStatementId; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; import org.dddjava.jig.domain.model.information.JigRepository; import org.junit.jupiter.api.Test; @@ -15,18 +15,18 @@ class SpringDataJdbcStatementReaderTest { @Test void SpringDataJdbcのRepositoryメソッドをSQLとして取得できる(JigRepository jigRepository) { - var statements = jigRepository.jigDataProvider().fetchMybatisStatements(); + var statements = jigRepository.jigDataProvider().fetchSqlStatements(); var namespace = SpringDataJdbcOrderRepository.class.getCanonicalName(); - var save = statements.findById(MyBatisStatementId.from(namespace + ".save")).orElseThrow(); + var save = statements.findById(SqlStatementId.from(namespace + ".save")).orElseThrow(); assertEquals("[spring_data_jdbc_orders]", save.tables().asText()); assertEquals(SqlType.INSERT, save.sqlType()); - var findById = statements.findById(MyBatisStatementId.from(namespace + ".findById")).orElseThrow(); + var findById = statements.findById(SqlStatementId.from(namespace + ".findById")).orElseThrow(); assertEquals("[spring_data_jdbc_orders]", findById.tables().asText()); assertEquals(SqlType.SELECT, findById.sqlType()); - var deleteById = statements.findById(MyBatisStatementId.from(namespace + ".deleteById")).orElseThrow(); + var deleteById = statements.findById(SqlStatementId.from(namespace + ".deleteById")).orElseThrow(); assertEquals("[spring_data_jdbc_orders]", deleteById.tables().asText()); assertEquals(SqlType.DELETE, deleteById.sqlType()); } From 667a2230ab895d29d3952c152e1f857c2f5c2a6f Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 21 Feb 2026 23:40:23 +0900 Subject: [PATCH 06/22] =?UTF-8?q?refactor:=20SpringDataJdbcStatementsReade?= =?UTF-8?q?r=E3=81=AE=E6=88=BB=E3=82=8A=E5=80=A4=E3=82=92SqlStatements?= =?UTF-8?q?=E3=81=AB=E7=B5=B1=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../DefaultJigRepositoryFactory.java | 5 ++--- .../SpringDataJdbcStatementsReader.java | 11 ++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java index d8a6e8142..87c8a767d 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java @@ -6,7 +6,6 @@ import org.dddjava.jig.application.GlossaryRepository; import org.dddjava.jig.application.JigEventRepository; import org.dddjava.jig.domain.model.data.JigDataProvider; -import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatement; import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import org.dddjava.jig.domain.model.data.terms.Glossary; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; @@ -168,10 +167,10 @@ private SqlStatements readMyBatisStatements(FilesystemSources sources, Collectio return mergedStatements; } - private SqlStatements mergeStatements(SqlStatements myBatisStatements, List springDataJdbcStatements) { + private SqlStatements mergeStatements(SqlStatements myBatisStatements, SqlStatements springDataJdbcStatements) { return new SqlStatements(Stream.concat( myBatisStatements.list().stream(), - springDataJdbcStatements.stream()) + springDataJdbcStatements.list().stream()) .distinct() .toList()); } diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index 9cc5d7c3a..a829514d1 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -2,6 +2,7 @@ import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatement; import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; +import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import org.dddjava.jig.domain.model.data.rdbaccess.Query; import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; @@ -28,14 +29,14 @@ public class SpringDataJdbcStatementsReader { private static final String SPRING_DATA_QUERY = "org.springframework.data.jdbc.repository.query.Query"; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; - public List readFrom(Collection jigTypeHeaders, List classPaths) { + public SqlStatements readFrom(Collection jigTypeHeaders, List classPaths) { Collection classNames = jigTypeHeaders.stream() .filter(jigTypeHeader -> jigTypeHeader.jigTypeAttributes() .declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION))) .map(JigTypeHeader::fqn) .toList(); - if (classNames.isEmpty()) return List.of(); + if (classNames.isEmpty()) return SqlStatements.empty(); URL[] classLocationUrls = classPaths.stream() .flatMap(path -> { @@ -50,7 +51,7 @@ public List readFrom(Collection jigTypeHeaders, Lis try (URLClassLoader classLoader = new URLClassLoader(classLocationUrls, ClassLoader.getSystemClassLoader())) { Class repositoryInterface = loadClassIfPresent(classLoader, SPRING_DATA_REPOSITORY).orElse(null); - if (repositoryInterface == null) return List.of(); + if (repositoryInterface == null) return SqlStatements.empty(); Map statements = new LinkedHashMap<>(); for (String className : classNames) { @@ -62,10 +63,10 @@ public List readFrom(Collection jigTypeHeaders, Lis extractStatements(repositoryType, tableName).forEach(statement -> statements.put(statement.sqlStatementId(), statement)); } - return List.copyOf(statements.values()); + return new SqlStatements(List.copyOf(statements.values())); } catch (Exception e) { logger.warn("Spring Data JDBC の読み取りに失敗しました。", e); - return List.of(); + return SqlStatements.empty(); } } From 277bd4a8355497904e6c2c26000057c3e75f10af Mon Sep 17 00:00:00 2001 From: irof Date: Sat, 21 Feb 2026 23:49:15 +0900 Subject: [PATCH 07/22] =?UTF-8?q?refactor:=20SpringDataJDBC=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E3=82=92ASM=E3=83=A1=E3=82=BF=E6=83=85=E5=A0=B1?= =?UTF-8?q?=E3=83=99=E3=83=BC=E3=82=B9=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../DefaultJigRepositoryFactory.java | 2 +- .../SpringDataJdbcStatementsReader.java | 255 +++++++----------- 2 files changed, 102 insertions(+), 155 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java index 87c8a767d..193b9f24f 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/javaproductreader/DefaultJigRepositoryFactory.java @@ -154,7 +154,7 @@ private SqlStatements readMyBatisStatements(FilesystemSources sources, Collectio List classPaths = sources.sourceBasePaths().classSourceBasePaths(); var myBatisReadResult = myBatisStatementsReader.readFrom(jigTypeHeaders, classPaths); - var springDataJdbcStatements = springDataJdbcStatementsReader.readFrom(jigTypeHeaders, classPaths); + var springDataJdbcStatements = springDataJdbcStatementsReader.readFrom(classDeclarations); SqlStatements mergedStatements = mergeStatements(myBatisReadResult.sqlStatements(), springDataJdbcStatements); diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index a829514d1..e6f3e9f61 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -1,194 +1,141 @@ package org.dddjava.jig.infrastructure.springdatajdbc; +import org.dddjava.jig.domain.model.data.rdbaccess.Query; import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatement; import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; -import org.dddjava.jig.domain.model.data.rdbaccess.Query; import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; +import org.dddjava.jig.domain.model.data.types.JigAnnotationReference; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; +import org.dddjava.jig.domain.model.data.types.JigTypeReference; +import org.dddjava.jig.domain.model.data.types.JavaTypeDeclarationKind; import org.dddjava.jig.domain.model.data.types.TypeId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; +import org.dddjava.jig.infrastructure.asm.ClassDeclaration; + import java.util.*; -import java.util.stream.Stream; public class SpringDataJdbcStatementsReader { - private static final Logger logger = LoggerFactory.getLogger(SpringDataJdbcStatementsReader.class); - private static final String REPOSITORY_ANNOTATION = "org.springframework.stereotype.Repository"; - private static final String SPRING_DATA_REPOSITORY = "org.springframework.data.repository.Repository"; - private static final String SPRING_DATA_QUERY = "org.springframework.data.jdbc.repository.query.Query"; + private static final String SPRING_DATA_REPOSITORY_PREFIX = "org.springframework.data.repository."; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; - public SqlStatements readFrom(Collection jigTypeHeaders, List classPaths) { - Collection classNames = jigTypeHeaders.stream() - .filter(jigTypeHeader -> jigTypeHeader.jigTypeAttributes() - .declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION))) - .map(JigTypeHeader::fqn) - .toList(); - - if (classNames.isEmpty()) return SqlStatements.empty(); - - URL[] classLocationUrls = classPaths.stream() - .flatMap(path -> { - try { - return Stream.of(path.toUri().toURL()); - } catch (MalformedURLException e) { - logger.warn("classPath({})は読み飛ばします", path, e); - return Stream.empty(); - } - }) - .toArray(URL[]::new); - - try (URLClassLoader classLoader = new URLClassLoader(classLocationUrls, ClassLoader.getSystemClassLoader())) { - Class repositoryInterface = loadClassIfPresent(classLoader, SPRING_DATA_REPOSITORY).orElse(null); - if (repositoryInterface == null) return SqlStatements.empty(); - - Map statements = new LinkedHashMap<>(); - for (String className : classNames) { - Class repositoryType = Class.forName(className, false, classLoader); - if (!repositoryType.isInterface()) continue; - if (!repositoryInterface.isAssignableFrom(repositoryType)) continue; - - Optional tableName = resolveTableName(repositoryType); - extractStatements(repositoryType, tableName).forEach(statement -> - statements.put(statement.sqlStatementId(), statement)); - } - return new SqlStatements(List.copyOf(statements.values())); - } catch (Exception e) { - logger.warn("Spring Data JDBC の読み取りに失敗しました。", e); - return SqlStatements.empty(); - } + public SqlStatements readFrom(Collection classDeclarations) { + Map declarationMap = classDeclarations.stream() + .collect(java.util.stream.Collectors.toMap( + declaration -> declaration.jigTypeHeader().id(), + declaration -> declaration, + (left, right) -> left, + LinkedHashMap::new)); + + Map statements = new LinkedHashMap<>(); + + classDeclarations.stream() + .filter(this::isRepositoryInterface) + .filter(declaration -> extendsSpringDataRepository(declaration.jigTypeHeader(), declarationMap, new HashSet<>())) + .forEach(declaration -> { + Optional tableName = resolveTableName(declaration.jigTypeHeader(), declarationMap, new HashSet<>()); + declaration.jigMethodDeclarations().stream() + .map(jigMethodDeclaration -> jigMethodDeclaration.header().name()) + .distinct() + .forEach(methodName -> inferSqlType(methodName).ifPresent(sqlType -> { + Query query = tableName + .map(name -> Query.from(defaultQuery(sqlType, name))) + .orElse(Query.unsupported()); + String statementValue = declaration.jigTypeHeader().fqn() + "." + methodName; + SqlStatementId statementId = SqlStatementId.from(statementValue); + statements.put(statementId, new SqlStatement(statementId, query, sqlType)); + })); + }); + + return new SqlStatements(List.copyOf(statements.values())); } - private Stream extractStatements(Class repositoryType, Optional tableName) { - return Arrays.stream(repositoryType.getMethods()) - .filter(method -> method.getDeclaringClass() != Object.class) - .flatMap(method -> createStatement(repositoryType, method, tableName).stream()); + private boolean isRepositoryInterface(ClassDeclaration declaration) { + JigTypeHeader header = declaration.jigTypeHeader(); + return header.javaTypeDeclarationKind() == JavaTypeDeclarationKind.INTERFACE + && header.jigTypeAttributes().declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION)); } - private Optional createStatement(Class repositoryType, Method method, Optional tableName) { - SqlType sqlType = inferSqlType(method).orElse(null); - if (sqlType == null) return Optional.empty(); + private boolean extendsSpringDataRepository(JigTypeHeader header, Map declarationMap, Set visited) { + if (!visited.add(header.id())) return false; - Query query = readQuery(method).orElseGet(() -> - tableName.map(name -> Query.from(defaultQuery(sqlType, name))).orElse(Query.unsupported())); + for (JigTypeReference interfaceType : header.interfaceTypeList()) { + TypeId interfaceId = interfaceType.id(); + if (interfaceId.fqn().startsWith(SPRING_DATA_REPOSITORY_PREFIX)) return true; - SqlStatementId statementId = SqlStatementId.from(repositoryType.getCanonicalName() + "." + method.getName()); - return Optional.of(new SqlStatement(statementId, query, sqlType)); + ClassDeclaration declaration = declarationMap.get(interfaceId); + if (declaration != null && extendsSpringDataRepository(declaration.jigTypeHeader(), declarationMap, visited)) { + return true; + } + } + return false; } - private Optional inferSqlType(Method method) { - String methodName = method.getName().toLowerCase(Locale.ROOT); + private Optional resolveTableName(JigTypeHeader repositoryHeader, Map declarationMap, Set visited) { + Optional entityTypeId = resolveEntityTypeId(repositoryHeader, declarationMap, visited); + if (entityTypeId.isEmpty()) return Optional.empty(); - if (methodName.startsWith("find") - || methodName.startsWith("read") - || methodName.startsWith("get") - || methodName.startsWith("query") - || methodName.startsWith("count") - || methodName.startsWith("exists")) { - return Optional.of(SqlType.SELECT); - } - if (methodName.startsWith("save") - || methodName.startsWith("insert") - || methodName.startsWith("create") - || methodName.startsWith("add")) { - return Optional.of(SqlType.INSERT); - } - if (methodName.startsWith("update") - || methodName.startsWith("set")) { - return Optional.of(SqlType.UPDATE); + TypeId typeId = entityTypeId.orElseThrow(); + ClassDeclaration entityDeclaration = declarationMap.get(typeId); + if (entityDeclaration == null) { + return Optional.of(toSnakeCase(typeId.asSimpleText())); } - if (methodName.startsWith("delete") - || methodName.startsWith("remove")) { - return Optional.of(SqlType.DELETE); - } - return Optional.empty(); - } - private Optional readQuery(Method method) { - return Arrays.stream(method.getAnnotations()) - .filter(annotation -> annotation.annotationType().getName().equals(SPRING_DATA_QUERY)) + Optional tableName = entityDeclaration.jigTypeHeader().jigTypeAttributes().declarationAnnotationInstances().stream() + .filter(annotation -> annotation.id().fqn().equals(SPRING_DATA_TABLE)) .findFirst() - .flatMap(annotation -> { - try { - Method valueMethod = annotation.annotationType().getMethod("value"); - Object value = valueMethod.invoke(annotation); - if (!(value instanceof String query)) return Optional.empty(); - if (query.isBlank()) return Optional.empty(); - return Optional.of(Query.from(query)); - } catch (ReflectiveOperationException e) { - logger.debug("Queryアノテーションの読み取りに失敗しました。method={}", method, e); - return Optional.empty(); - } - }); - } + .flatMap(annotation -> annotation.elementTextOf("value")) + .filter(value -> !value.isBlank()); - private Optional resolveTableName(Class repositoryType) { - Optional> entityType = resolveEntityType(repositoryType, new HashSet<>()); - if (entityType.isEmpty()) return Optional.empty(); - - Annotation[] annotations = entityType.orElseThrow().getAnnotations(); - for (Annotation annotation : annotations) { - if (!annotation.annotationType().getName().equals(SPRING_DATA_TABLE)) continue; - try { - Method valueMethod = annotation.annotationType().getMethod("value"); - Object value = valueMethod.invoke(annotation); - if (value instanceof String tableName && !tableName.isBlank()) { - return Optional.of(tableName); - } - } catch (ReflectiveOperationException e) { - logger.debug("Tableアノテーションの読み取りに失敗しました。entity={}", entityType.orElseThrow(), e); - } - } - return Optional.of(toSnakeCase(entityType.orElseThrow().getSimpleName())); + if (tableName.isPresent()) return tableName; + return Optional.of(toSnakeCase(entityDeclaration.jigTypeHeader().simpleName())); } - private Optional> resolveEntityType(Class repositoryType, Set> visited) { - if (!visited.add(repositoryType)) return Optional.empty(); - - for (Type genericInterface : repositoryType.getGenericInterfaces()) { - Optional> entityType = resolveEntityType(genericInterface, visited); - if (entityType.isPresent()) return entityType; - } - return Optional.empty(); - } + private Optional resolveEntityTypeId(JigTypeHeader header, Map declarationMap, Set visited) { + if (!visited.add(header.id())) return Optional.empty(); - private Optional> resolveEntityType(Type type, Set> visited) { - if (type instanceof ParameterizedType parameterizedType) { - Type rawType = parameterizedType.getRawType(); - if (rawType instanceof Class rawClass - && rawClass.getName().startsWith("org.springframework.data.repository.")) { - Type[] arguments = parameterizedType.getActualTypeArguments(); - if (arguments.length > 0 && arguments[0] instanceof Class entityType) return Optional.of(entityType); + for (JigTypeReference interfaceType : header.interfaceTypeList()) { + TypeId interfaceId = interfaceType.id(); + if (interfaceId.fqn().startsWith(SPRING_DATA_REPOSITORY_PREFIX) + && !interfaceType.typeArgumentList().isEmpty()) { + return Optional.of(interfaceType.typeArgumentList().getFirst().typeId()); } - if (rawType instanceof Class rawClass) { - Optional> entityType = resolveEntityType(rawClass, visited); - if (entityType.isPresent()) return entityType; + + ClassDeclaration declaration = declarationMap.get(interfaceId); + if (declaration != null) { + Optional entityTypeId = resolveEntityTypeId(declaration.jigTypeHeader(), declarationMap, visited); + if (entityTypeId.isPresent()) return entityTypeId; } - return Optional.empty(); - } - if (type instanceof Class interfaceType) { - return resolveEntityType(interfaceType, visited); } return Optional.empty(); } - private static Optional> loadClassIfPresent(ClassLoader classLoader, String className) { - try { - return Optional.of(Class.forName(className, false, classLoader)); - } catch (ClassNotFoundException ignored) { - return Optional.empty(); + private Optional inferSqlType(String methodName) { + String normalizedMethodName = methodName.toLowerCase(Locale.ROOT); + + if (normalizedMethodName.startsWith("find") + || normalizedMethodName.startsWith("read") + || normalizedMethodName.startsWith("get") + || normalizedMethodName.startsWith("query") + || normalizedMethodName.startsWith("count") + || normalizedMethodName.startsWith("exists")) { + return Optional.of(SqlType.SELECT); } + if (normalizedMethodName.startsWith("save") + || normalizedMethodName.startsWith("insert") + || normalizedMethodName.startsWith("create") + || normalizedMethodName.startsWith("add")) { + return Optional.of(SqlType.INSERT); + } + if (normalizedMethodName.startsWith("update") + || normalizedMethodName.startsWith("set")) { + return Optional.of(SqlType.UPDATE); + } + if (normalizedMethodName.startsWith("delete") + || normalizedMethodName.startsWith("remove")) { + return Optional.of(SqlType.DELETE); + } + return Optional.empty(); } private String defaultQuery(SqlType sqlType, String tableName) { From af1be40d07f696355f2c6e7349c1639c5b0bae00 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 00:02:47 +0900 Subject: [PATCH 08/22] =?UTF-8?q?docs:=20=E6=8E=A8=E6=B8=AC=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=81=AB=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQLの種類を推測するメソッドに説明を記載し、公式ドキュメントへのリンクを追加しました。また、判別できない場合のログメッセージを追加し、ユーザーにissue提出を促す内容にしました。 --- .../SpringDataJdbcStatementsReader.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index e6f3e9f61..de071ccec 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -1,20 +1,19 @@ package org.dddjava.jig.infrastructure.springdatajdbc; -import org.dddjava.jig.domain.model.data.rdbaccess.Query; -import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatement; -import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; -import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; -import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; -import org.dddjava.jig.domain.model.data.types.JigAnnotationReference; +import org.dddjava.jig.domain.model.data.rdbaccess.*; +import org.dddjava.jig.domain.model.data.types.JavaTypeDeclarationKind; import org.dddjava.jig.domain.model.data.types.JigTypeHeader; import org.dddjava.jig.domain.model.data.types.JigTypeReference; -import org.dddjava.jig.domain.model.data.types.JavaTypeDeclarationKind; import org.dddjava.jig.domain.model.data.types.TypeId; import org.dddjava.jig.infrastructure.asm.ClassDeclaration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; public class SpringDataJdbcStatementsReader { + private static final Logger logger = LoggerFactory.getLogger(SpringDataJdbcStatementsReader.class); + private static final String REPOSITORY_ANNOTATION = "org.springframework.stereotype.Repository"; private static final String SPRING_DATA_REPOSITORY_PREFIX = "org.springframework.data.repository."; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; @@ -110,6 +109,11 @@ private Optional resolveEntityTypeId(JigTypeHeader header, MapDefining Query Methods + */ private Optional inferSqlType(String methodName) { String normalizedMethodName = methodName.toLowerCase(Locale.ROOT); @@ -135,6 +139,9 @@ private Optional inferSqlType(String methodName) { || normalizedMethodName.startsWith("remove")) { return Optional.of(SqlType.DELETE); } + + // 判別できないものは空にしておく + logger.info("SQLの種類がメソッド名 {} から判別できませんでした。CRUDのどれかに該当する場合は対象にしたいのでissueお願いします。", methodName); return Optional.empty(); } From f071a3503d8471e1d768349e2f11d83e714b95a6 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 15:54:35 +0900 Subject: [PATCH 09/22] =?UTF-8?q?docs:=20SpringDataJDBC=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E5=AF=BE=E8=B1=A1=E3=81=AE=E6=9D=A1=E4=BB=B6=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../SpringDataJdbcStatementsReader.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index de071ccec..a54e01299 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -18,6 +18,14 @@ public class SpringDataJdbcStatementsReader { private static final String SPRING_DATA_REPOSITORY_PREFIX = "org.springframework.data.repository."; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; + /** + * ASMで読み取ったクラス情報から、Spring Data JDBCのRepositoryメソッドをSQLステートメントとして抽出する。 + * + * 対象は次の条件をすべて満たす型: + * 1) interface である + * 2) {@code @org.springframework.stereotype.Repository} が付与されている + * 3) 継承先(再帰含む)に {@code org.springframework.data.repository.*} を持つ + */ public SqlStatements readFrom(Collection classDeclarations) { Map declarationMap = classDeclarations.stream() .collect(java.util.stream.Collectors.toMap( @@ -51,6 +59,7 @@ public SqlStatements readFrom(Collection classDeclarations) { private boolean isRepositoryInterface(ClassDeclaration declaration) { JigTypeHeader header = declaration.jigTypeHeader(); + // Spring DataのRepositoryとして扱う入口条件 return header.javaTypeDeclarationKind() == JavaTypeDeclarationKind.INTERFACE && header.jigTypeAttributes().declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION)); } @@ -60,6 +69,7 @@ private boolean extendsSpringDataRepository(JigTypeHeader header, Map resolveEntityTypeId(JigTypeHeader header, Map の先頭型引数Tをエンティティ型として扱う return Optional.of(interfaceType.typeArgumentList().getFirst().typeId()); } From 59b87f9dfcc41d71f5caa475e799a3d9e784d14f Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 17:26:07 +0900 Subject: [PATCH 10/22] =?UTF-8?q?fix:=20@Query=E3=83=A1=E3=82=BD=E3=83=83?= =?UTF-8?q?=E3=83=89=E3=82=92=E5=91=BD=E5=90=8D=E8=A6=8F=E7=B4=84=E6=8E=A8?= =?UTF-8?q?=E6=B8=AC=E3=81=AE=E5=AF=BE=E8=B1=A1=E5=A4=96=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../springdatajdbc/SpringDataJdbcStatementsReader.java | 7 +++++++ .../springdatajdbc/SpringDataJdbcStatementReaderTest.java | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index a54e01299..b45de4137 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -17,6 +17,7 @@ public class SpringDataJdbcStatementsReader { private static final String REPOSITORY_ANNOTATION = "org.springframework.stereotype.Repository"; private static final String SPRING_DATA_REPOSITORY_PREFIX = "org.springframework.data.repository."; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; + private static final String SPRING_DATA_QUERY = "org.springframework.data.jdbc.repository.query.Query"; /** * ASMで読み取ったクラス情報から、Spring Data JDBCのRepositoryメソッドをSQLステートメントとして抽出する。 @@ -41,9 +42,15 @@ public SqlStatements readFrom(Collection classDeclarations) { .filter(declaration -> extendsSpringDataRepository(declaration.jigTypeHeader(), declarationMap, new HashSet<>())) .forEach(declaration -> { Optional tableName = resolveTableName(declaration.jigTypeHeader(), declarationMap, new HashSet<>()); + Set queryAnnotatedMethodNames = declaration.jigMethodDeclarations().stream() + .filter(methodDeclaration -> methodDeclaration.header().declarationAnnotationStream() + .anyMatch(annotation -> annotation.id().fqn().equals(SPRING_DATA_QUERY))) + .map(methodDeclaration -> methodDeclaration.header().name()) + .collect(java.util.stream.Collectors.toSet()); declaration.jigMethodDeclarations().stream() .map(jigMethodDeclaration -> jigMethodDeclaration.header().name()) .distinct() + .filter(methodName -> !queryAnnotatedMethodNames.contains(methodName)) .forEach(methodName -> inferSqlType(methodName).ifPresent(sqlType -> { Query query = tableName .map(name -> Query.from(defaultQuery(sqlType, name))) diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java index b2277c93b..8fa071f85 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java @@ -9,6 +9,7 @@ import testing.JigTest; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @JigTest class SpringDataJdbcStatementReaderTest { @@ -29,6 +30,8 @@ class SpringDataJdbcStatementReaderTest { var deleteById = statements.findById(SqlStatementId.from(namespace + ".deleteById")).orElseThrow(); assertEquals("[spring_data_jdbc_orders]", deleteById.tables().asText()); assertEquals(SqlType.DELETE, deleteById.sqlType()); + + assertTrue(statements.findById(SqlStatementId.from(namespace + ".updateById")).isEmpty()); } @Test @@ -48,7 +51,7 @@ class SpringDataJdbcStatementReaderTest { .findFirst() .orElseThrow() .selectTables()); - assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() + assertEquals("[]", datasourceAngles.stream() .filter(angle -> angle.interfaceMethod().name().equals("updateById")) .findFirst() .orElseThrow() From e83ff0abc08b2d2b51f5db8232be950f1982ab6b Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 18:55:13 +0900 Subject: [PATCH 11/22] =?UTF-8?q?fix:=20SpringDataJDBC=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E3=81=AE@Repository=E6=9D=A1=E4=BB=B6=E3=82=92=E9=99=A4?= =?UTF-8?q?=E5=8E=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../SpringDataJdbcStatementsReader.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index b45de4137..e5229b686 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -14,7 +14,6 @@ public class SpringDataJdbcStatementsReader { private static final Logger logger = LoggerFactory.getLogger(SpringDataJdbcStatementsReader.class); - private static final String REPOSITORY_ANNOTATION = "org.springframework.stereotype.Repository"; private static final String SPRING_DATA_REPOSITORY_PREFIX = "org.springframework.data.repository."; private static final String SPRING_DATA_TABLE = "org.springframework.data.relational.core.mapping.Table"; private static final String SPRING_DATA_QUERY = "org.springframework.data.jdbc.repository.query.Query"; @@ -24,8 +23,7 @@ public class SpringDataJdbcStatementsReader { * * 対象は次の条件をすべて満たす型: * 1) interface である - * 2) {@code @org.springframework.stereotype.Repository} が付与されている - * 3) 継承先(再帰含む)に {@code org.springframework.data.repository.*} を持つ + * 2) 継承先(再帰含む)に {@code org.springframework.data.repository.*} を持つ */ public SqlStatements readFrom(Collection classDeclarations) { Map declarationMap = classDeclarations.stream() @@ -38,7 +36,7 @@ public SqlStatements readFrom(Collection classDeclarations) { Map statements = new LinkedHashMap<>(); classDeclarations.stream() - .filter(this::isRepositoryInterface) + .filter(this::isInterface) .filter(declaration -> extendsSpringDataRepository(declaration.jigTypeHeader(), declarationMap, new HashSet<>())) .forEach(declaration -> { Optional tableName = resolveTableName(declaration.jigTypeHeader(), declarationMap, new HashSet<>()); @@ -64,11 +62,9 @@ public SqlStatements readFrom(Collection classDeclarations) { return new SqlStatements(List.copyOf(statements.values())); } - private boolean isRepositoryInterface(ClassDeclaration declaration) { + private boolean isInterface(ClassDeclaration declaration) { JigTypeHeader header = declaration.jigTypeHeader(); - // Spring DataのRepositoryとして扱う入口条件 - return header.javaTypeDeclarationKind() == JavaTypeDeclarationKind.INTERFACE - && header.jigTypeAttributes().declaredAnnotation(TypeId.valueOf(REPOSITORY_ANNOTATION)); + return header.javaTypeDeclarationKind() == JavaTypeDeclarationKind.INTERFACE; } private boolean extendsSpringDataRepository(JigTypeHeader header, Map declarationMap, Set visited) { From 38ec9cb452fd0042007f6b642cc02e37a19bce7c Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 19:09:00 +0900 Subject: [PATCH 12/22] =?UTF-8?q?feat:=20SpringDataJDBC=E3=81=AE@Query?= =?UTF-8?q?=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E8=A7=A3=E6=9E=90=E3=81=AB?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../SpringDataJdbcStatementsReader.java | 55 +++++++++++++++---- .../SpringDataJdbcStatementReaderTest.java | 7 ++- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index e5229b686..c8bb3136e 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -40,23 +40,43 @@ public SqlStatements readFrom(Collection classDeclarations) { .filter(declaration -> extendsSpringDataRepository(declaration.jigTypeHeader(), declarationMap, new HashSet<>())) .forEach(declaration -> { Optional tableName = resolveTableName(declaration.jigTypeHeader(), declarationMap, new HashSet<>()); - Set queryAnnotatedMethodNames = declaration.jigMethodDeclarations().stream() - .filter(methodDeclaration -> methodDeclaration.header().declarationAnnotationStream() - .anyMatch(annotation -> annotation.id().fqn().equals(SPRING_DATA_QUERY))) - .map(methodDeclaration -> methodDeclaration.header().name()) - .collect(java.util.stream.Collectors.toSet()); + Map queryByMethodName = declaration.jigMethodDeclarations().stream() + .collect(java.util.stream.Collectors.toMap( + methodDeclaration -> methodDeclaration.header().name(), + methodDeclaration -> methodDeclaration.header().declarationAnnotationStream() + .filter(annotation -> annotation.id().fqn().equals(SPRING_DATA_QUERY)) + .findFirst() + .flatMap(annotation -> annotation.elementTextOf("value")) + .filter(value -> !value.isBlank()) + .map(Query::from) + .orElse(Query.unsupported()), + (left, right) -> left.supported() ? left : right.supported() ? right : left, + LinkedHashMap::new)) + .entrySet().stream() + .filter(entry -> entry.getValue().supported()) + .collect(java.util.stream.Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (left, right) -> left, + LinkedHashMap::new)); + declaration.jigMethodDeclarations().stream() .map(jigMethodDeclaration -> jigMethodDeclaration.header().name()) .distinct() - .filter(methodName -> !queryAnnotatedMethodNames.contains(methodName)) - .forEach(methodName -> inferSqlType(methodName).ifPresent(sqlType -> { - Query query = tableName - .map(name -> Query.from(defaultQuery(sqlType, name))) - .orElse(Query.unsupported()); + .forEach(methodName -> { + Query query = queryByMethodName.getOrDefault(methodName, Query.unsupported()); + Optional inferredSqlType = query.supported() + ? inferSqlTypeFromQuery(query.text()) + : inferSqlType(methodName); + inferredSqlType.ifPresent(sqlType -> { + Query resolvedQuery = query.supported() + ? query + : tableName.map(name -> Query.from(defaultQuery(sqlType, name))).orElse(Query.unsupported()); String statementValue = declaration.jigTypeHeader().fqn() + "." + methodName; SqlStatementId statementId = SqlStatementId.from(statementValue); - statements.put(statementId, new SqlStatement(statementId, query, sqlType)); - })); + statements.put(statementId, new SqlStatement(statementId, resolvedQuery, sqlType)); + }); + }); }); return new SqlStatements(List.copyOf(statements.values())); @@ -159,6 +179,17 @@ private Optional inferSqlType(String methodName) { return Optional.empty(); } + private Optional inferSqlTypeFromQuery(String query) { + String normalizedQuery = query.stripLeading().toLowerCase(Locale.ROOT); + if (normalizedQuery.startsWith("insert")) return Optional.of(SqlType.INSERT); + if (normalizedQuery.startsWith("select")) return Optional.of(SqlType.SELECT); + if (normalizedQuery.startsWith("update")) return Optional.of(SqlType.UPDATE); + if (normalizedQuery.startsWith("delete")) return Optional.of(SqlType.DELETE); + + logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query); + return Optional.empty(); + } + private String defaultQuery(SqlType sqlType, String tableName) { return switch (sqlType) { case INSERT -> "insert into " + tableName + " values (?)"; diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java index 8fa071f85..e45c66132 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java @@ -9,7 +9,6 @@ import testing.JigTest; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; @JigTest class SpringDataJdbcStatementReaderTest { @@ -31,7 +30,9 @@ class SpringDataJdbcStatementReaderTest { assertEquals("[spring_data_jdbc_orders]", deleteById.tables().asText()); assertEquals(SqlType.DELETE, deleteById.sqlType()); - assertTrue(statements.findById(SqlStatementId.from(namespace + ".updateById")).isEmpty()); + var updateById = statements.findById(SqlStatementId.from(namespace + ".updateById")).orElseThrow(); + assertEquals("[spring_data_jdbc_orders]", updateById.tables().asText()); + assertEquals(SqlType.UPDATE, updateById.sqlType()); } @Test @@ -51,7 +52,7 @@ class SpringDataJdbcStatementReaderTest { .findFirst() .orElseThrow() .selectTables()); - assertEquals("[]", datasourceAngles.stream() + assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() .filter(angle -> angle.interfaceMethod().name().equals("updateById")) .findFirst() .orElseThrow() From 1cf71394f0daff8ec475ecc3240368be364bb4ef Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 19:19:05 +0900 Subject: [PATCH 13/22] =?UTF-8?q?fix:=20@Query=E5=85=88=E9=A0=AD=E8=A3=85?= =?UTF-8?q?=E9=A3=BE=E3=82=92=E9=99=A4=E5=8E=BB=E3=81=97=E3=81=A6SQL?= =?UTF-8?q?=E7=A8=AE=E5=88=A5=E3=82=92=E5=88=A4=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../SpringDataJdbcStatementsReader.java | 35 ++++++++++++++++++- .../SpringDataJdbcStatementReaderTest.java | 11 +++++- .../SpringDataJdbcOrderRepository.java | 3 ++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index c8bb3136e..76d77d49a 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -47,6 +47,7 @@ public SqlStatements readFrom(Collection classDeclarations) { .filter(annotation -> annotation.id().fqn().equals(SPRING_DATA_QUERY)) .findFirst() .flatMap(annotation -> annotation.elementTextOf("value")) + .map(this::normalizeQuery) .filter(value -> !value.isBlank()) .map(Query::from) .orElse(Query.unsupported()), @@ -180,7 +181,7 @@ private Optional inferSqlType(String methodName) { } private Optional inferSqlTypeFromQuery(String query) { - String normalizedQuery = query.stripLeading().toLowerCase(Locale.ROOT); + String normalizedQuery = normalizeQuery(query).toLowerCase(Locale.ROOT); if (normalizedQuery.startsWith("insert")) return Optional.of(SqlType.INSERT); if (normalizedQuery.startsWith("select")) return Optional.of(SqlType.SELECT); if (normalizedQuery.startsWith("update")) return Optional.of(SqlType.UPDATE); @@ -190,6 +191,38 @@ private Optional inferSqlTypeFromQuery(String query) { return Optional.empty(); } + private String normalizeQuery(String query) { + return skipLeadingSqlDecorations(query); + } + + private String skipLeadingSqlDecorations(String query) { + String remaining = query; + while (true) { + String trimmed = remaining.stripLeading(); + if (trimmed.startsWith("\uFEFF")) { + remaining = trimmed.substring(1); + continue; + } + if (trimmed.startsWith("--")) { + int newlineIndex = trimmed.indexOf('\n'); + if (newlineIndex < 0) return ""; + remaining = trimmed.substring(newlineIndex + 1); + continue; + } + if (trimmed.startsWith("/*")) { + int commentEndIndex = trimmed.indexOf("*/"); + if (commentEndIndex < 0) return ""; + remaining = trimmed.substring(commentEndIndex + 2); + continue; + } + if (trimmed.startsWith("(")) { + remaining = trimmed.substring(1); + continue; + } + return trimmed; + } + } + private String defaultQuery(SqlType sqlType, String tableName) { return switch (sqlType) { case INSERT -> "insert into " + tableName + " values (?)"; diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java index e45c66132..a35af25e5 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java @@ -33,6 +33,10 @@ class SpringDataJdbcStatementReaderTest { var updateById = statements.findById(SqlStatementId.from(namespace + ".updateById")).orElseThrow(); assertEquals("[spring_data_jdbc_orders]", updateById.tables().asText()); assertEquals(SqlType.UPDATE, updateById.sqlType()); + + var updateByIdWithComment = statements.findById(SqlStatementId.from(namespace + ".updateByIdWithComment")).orElseThrow(); + assertEquals("[spring_data_jdbc_orders]", updateByIdWithComment.tables().asText()); + assertEquals(SqlType.UPDATE, updateByIdWithComment.sqlType()); } @Test @@ -41,7 +45,7 @@ class SpringDataJdbcStatementReaderTest { .filter(angle -> angle.declaringType().fqn().equals(SpringDataJdbcOrderRepository.class.getCanonicalName())) .toList(); - assertEquals(4, datasourceAngles.size()); + assertEquals(5, datasourceAngles.size()); assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() .filter(angle -> angle.interfaceMethod().name().equals("save")) .findFirst() @@ -57,6 +61,11 @@ class SpringDataJdbcStatementReaderTest { .findFirst() .orElseThrow() .updateTables()); + assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() + .filter(angle -> angle.interfaceMethod().name().equals("updateByIdWithComment")) + .findFirst() + .orElseThrow() + .updateTables()); assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() .filter(angle -> angle.interfaceMethod().name().equals("deleteById")) .findFirst() diff --git a/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java b/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java index d419aa7ca..c2861fbd3 100644 --- a/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java +++ b/jig-core/src/test/java/stub/infrastructure/datasource/springdata/SpringDataJdbcOrderRepository.java @@ -18,4 +18,7 @@ public interface SpringDataJdbcOrderRepository extends CrudRepository Date: Sun, 22 Feb 2026 23:19:01 +0900 Subject: [PATCH 14/22] =?UTF-8?q?docs:=20SqlStatementId=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=81=ABTODO=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java index 71621663c..e5159e926 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatementId.java @@ -6,6 +6,8 @@ * SQLステートメントID * * namespaceとidを.で連結したもの。 + * + * TODO namespaceやidはMyBatisの用語なのでこの形のままとするかは一考の余地がある */ public record SqlStatementId(String value, String namespace, String id) { From 183b72f66f59358914a0b0927e449d7c27d27b42 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 23:24:21 +0900 Subject: [PATCH 15/22] =?UTF-8?q?refactor:=20SQL=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=83=A1=E3=83=B3=E3=83=88ID=E3=81=AE?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=87=A6=E7=90=86=E3=82=92=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=81=AB=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/MyBatisStatementsReaderImpl.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java index a9dcf40c7..f34f461da 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementsReaderImpl.java @@ -106,7 +106,7 @@ private MyBatisReadResult extractSql(Collection classNames, ClassLoader // config.getMappedStatementsにAmbiguityが入っていることがあったので型を確認する if (obj instanceof MappedStatement mappedStatement) { - SqlStatementId myBatisStatementId = SqlStatementId.from(mappedStatement.getId()); + SqlStatementId sqlStatementId = resolveStatementId(mappedStatement); Query query; try { @@ -130,7 +130,7 @@ private MyBatisReadResult extractSql(Collection classNames, ClassLoader yield SqlType.SELECT; } }; - SqlStatement myBatisStatement = new SqlStatement(myBatisStatementId, query, sqlType); + SqlStatement myBatisStatement = new SqlStatement(sqlStatementId, query, sqlType); list.add(myBatisStatement); } } @@ -139,6 +139,33 @@ private MyBatisReadResult extractSql(Collection classNames, ClassLoader return new MyBatisReadResult(new SqlStatements(list), sqlReadStatus); } + /** + * MyBatisのステートメント情報からSQLステートメントIDを作成する + * + * 以下のMapperXMLとMapperインタフェースの場合、ステートメントIDは `com.example.mybatis.ExampleMapper.selectAll` となる。 + * + *
+     * {@code
+     * 
+     *     
+     * 
+     * }
+     * 
+ *
+     * {@code
+     * package com.example.mybatis;
+     * interface ExampleMapper {
+     *     List selectAll();
+     * }
+     * }
+     * 
+ */ + private static SqlStatementId resolveStatementId(MappedStatement mappedStatement) { + return SqlStatementId.from(mappedStatement.getId()); + } + private Query getQuery(MappedStatement mappedStatement) throws NoSuchFieldException, IllegalAccessException { SqlSource sqlSource = mappedStatement.getSqlSource(); From 298ebb00d9f8724c07aa0e10676a7a858af21d9b Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 23:27:04 +0900 Subject: [PATCH 16/22] =?UTF-8?q?fix:=20SQL=E8=A7=A3=E6=9E=90=E5=A4=B1?= =?UTF-8?q?=E6=95=97=E6=99=82=E3=81=AE=E3=83=AD=E3=82=B0=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit テーブル名解析失敗時のログメッセージを簡潔に改善し、「解析失敗」と明示しました。これにより、ログ内容がより分かりやすくなります。 --- .../org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java index 0c3e61387..80cb80214 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java @@ -41,8 +41,7 @@ public Table extractTable(String sql, SqlStatementId sqlStatementId) { } } - logger.warn("{} {} を {} としてテーブル名が解析できませんでした。このMapper由来の解析結果はドキュメントに出力されません。" + - "MyBatisの動的なSQLなどは完全に再現できません。JIGが認識しているSQL文=[{}]", + logger.warn("{} {} を {} としてテーブル名が解析できませんでした。テーブル名は「解析失敗」と表示されます。JIGが認識しているSQL文=[{}]", sqlStatementId.namespace(), sqlStatementId.id(), this, sql); From 27a4b0bf2dfd6edecd18f90b372fa23fa3955bd2 Mon Sep 17 00:00:00 2001 From: irof Date: Sun, 22 Feb 2026 23:54:16 +0900 Subject: [PATCH 17/22] =?UTF-8?q?refactor:=20DB=E3=82=A2=E3=82=AF=E3=82=BB?= =?UTF-8?q?=E3=82=B9=E5=88=A4=E5=AE=9A=E5=87=A6=E7=90=86=E3=82=92=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=81=AB=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../datasource/DatasourceAngles.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java index 5ec626bc7..36ba3b626 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/knowledge/datasource/DatasourceAngles.java @@ -3,6 +3,7 @@ import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatements; import org.dddjava.jig.domain.model.information.members.CallerMethods; +import org.dddjava.jig.domain.model.information.outputs.OutputImplementation; import org.dddjava.jig.domain.model.information.outputs.OutputImplementations; import org.dddjava.jig.domain.model.information.relation.methods.CallerMethodsFactory; @@ -21,12 +22,7 @@ public static DatasourceAngles from(OutputImplementations outputImplementations, var crudTables = sqlStatements.filterRelationOn(sqlStatement -> { SqlStatementId sqlStatementId = sqlStatement.sqlStatementId(); - boolean matchesSelf = outputImplementation.outputPortGateway().jigMethodId().namespace().equals(sqlStatementId.namespace()) - && outputImplementation.outputPortGateway().name().equals(sqlStatementId.id()); - // namespaceはメソッドの型のFQNに該当し、idはメソッド名に該当するので、それを比較する。 - return matchesSelf || outputImplementation.usingMethods() - .containsAny(methodCall -> methodCall.methodOwner().fqn().equals(sqlStatementId.namespace()) - && methodCall.methodName().equals(sqlStatementId.id())); + return gatewayUseSQL(outputImplementation, sqlStatementId) || invocationUseSQL(outputImplementation, sqlStatementId); }).crudTables(); return new DatasourceAngle(outputImplementation, crudTables, callerMethods); @@ -34,4 +30,30 @@ public static DatasourceAngles from(OutputImplementations outputImplementations, .sorted(Comparator.comparing(datasourceAngle -> datasourceAngle.interfaceMethod().jigMethodId().value())) .toList()); } + + /** + * InvocationがDBアクセスしているかを判定する + * + * 使用しているメソッドがSQLステートメントかで判断する + * TODO プライベートメソッドとか辿らないといけないような・・・ + */ + private static boolean invocationUseSQL(OutputImplementation outputImplementation, SqlStatementId sqlStatementId) { + return outputImplementation.usingMethods() + .containsAny(methodCall -> matchStatement(sqlStatementId, methodCall.methodOwner().fqn(), methodCall.methodName())); + } + + /** + * GatewayがDBアクセスするものかを判定する + * + * SpringDataJDBCを直接Serviceで使用している場合などにRepositoryインタフェースとSQLステートメントが一致する。 + */ + private static boolean gatewayUseSQL(OutputImplementation outputImplementation, SqlStatementId sqlStatementId) { + var gatewayMethodId = outputImplementation.outputPortGateway().jigMethodId(); + return matchStatement(sqlStatementId, gatewayMethodId.namespace(), gatewayMethodId.name()); + } + + private static boolean matchStatement(SqlStatementId sqlStatementId, String namespace, String name) { + // namespaceはメソッドの型のFQNに該当し、idはメソッド名に該当するので、それを比較する。 + return namespace.equals(sqlStatementId.namespace()) && name.equals(sqlStatementId.id()); + } } From 4092235b912a8b5db02112ae757848ea2f2ab5ab Mon Sep 17 00:00:00 2001 From: irof Date: Mon, 23 Feb 2026 00:01:54 +0900 Subject: [PATCH 18/22] =?UTF-8?q?test:=20SpringDataJdbcStatementReaderTest?= =?UTF-8?q?=E3=82=92=E3=83=91=E3=83=A9=E3=83=A1=E3=82=BF=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=82=BA=E3=83=89=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../SpringDataJdbcStatementReaderTest.java | 94 +++++++++---------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java index a35af25e5..71aa47d04 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementReaderTest.java @@ -4,72 +4,68 @@ import org.dddjava.jig.domain.model.data.rdbaccess.SqlStatementId; import org.dddjava.jig.domain.model.data.rdbaccess.SqlType; import org.dddjava.jig.domain.model.information.JigRepository; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import stub.infrastructure.datasource.springdata.SpringDataJdbcOrderRepository; import testing.JigTest; +import java.util.stream.Stream; + import static org.junit.jupiter.api.Assertions.assertEquals; @JigTest class SpringDataJdbcStatementReaderTest { - @Test - void SpringDataJdbcのRepositoryメソッドをSQLとして取得できる(JigRepository jigRepository) { + @ParameterizedTest + @MethodSource("repositoryMethodAndSqlType") + void SpringDataJdbcのRepositoryメソッドをSQLとして取得できる( + String methodName, + SqlType expectedSqlType, + JigRepository jigRepository + ) { var statements = jigRepository.jigDataProvider().fetchSqlStatements(); var namespace = SpringDataJdbcOrderRepository.class.getCanonicalName(); + var statement = statements.findById(SqlStatementId.from(namespace + "." + methodName)).orElseThrow(); - var save = statements.findById(SqlStatementId.from(namespace + ".save")).orElseThrow(); - assertEquals("[spring_data_jdbc_orders]", save.tables().asText()); - assertEquals(SqlType.INSERT, save.sqlType()); - - var findById = statements.findById(SqlStatementId.from(namespace + ".findById")).orElseThrow(); - assertEquals("[spring_data_jdbc_orders]", findById.tables().asText()); - assertEquals(SqlType.SELECT, findById.sqlType()); - - var deleteById = statements.findById(SqlStatementId.from(namespace + ".deleteById")).orElseThrow(); - assertEquals("[spring_data_jdbc_orders]", deleteById.tables().asText()); - assertEquals(SqlType.DELETE, deleteById.sqlType()); - - var updateById = statements.findById(SqlStatementId.from(namespace + ".updateById")).orElseThrow(); - assertEquals("[spring_data_jdbc_orders]", updateById.tables().asText()); - assertEquals(SqlType.UPDATE, updateById.sqlType()); - - var updateByIdWithComment = statements.findById(SqlStatementId.from(namespace + ".updateByIdWithComment")).orElseThrow(); - assertEquals("[spring_data_jdbc_orders]", updateByIdWithComment.tables().asText()); - assertEquals(SqlType.UPDATE, updateByIdWithComment.sqlType()); + assertEquals("[spring_data_jdbc_orders]", statement.tables().asText()); + assertEquals(expectedSqlType, statement.sqlType()); } - @Test - void DatasourceAnglesにSpringDataJdbcのCRUDが反映される(JigService jigService, JigRepository jigRepository) { + @ParameterizedTest + @MethodSource("repositoryMethodAndSqlType") + void DatasourceAnglesにSpringDataJdbcのCRUDが反映される( + String methodName, + SqlType expectedSqlType, + JigService jigService, + JigRepository jigRepository + ) { var datasourceAngles = jigService.datasourceAngles(jigRepository).list().stream() .filter(angle -> angle.declaringType().fqn().equals(SpringDataJdbcOrderRepository.class.getCanonicalName())) .toList(); + var angle = datasourceAngles.stream() + .filter(found -> found.interfaceMethod().name().equals(methodName)) + .findFirst() + .orElseThrow(); assertEquals(5, datasourceAngles.size()); - assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() - .filter(angle -> angle.interfaceMethod().name().equals("save")) - .findFirst() - .orElseThrow() - .insertTables()); - assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() - .filter(angle -> angle.interfaceMethod().name().equals("findById")) - .findFirst() - .orElseThrow() - .selectTables()); - assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() - .filter(angle -> angle.interfaceMethod().name().equals("updateById")) - .findFirst() - .orElseThrow() - .updateTables()); - assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() - .filter(angle -> angle.interfaceMethod().name().equals("updateByIdWithComment")) - .findFirst() - .orElseThrow() - .updateTables()); - assertEquals("[spring_data_jdbc_orders]", datasourceAngles.stream() - .filter(angle -> angle.interfaceMethod().name().equals("deleteById")) - .findFirst() - .orElseThrow() - .deleteTables()); + var expectedTables = "[spring_data_jdbc_orders]"; + switch (expectedSqlType) { + case INSERT -> assertEquals(expectedTables, angle.insertTables()); + case SELECT -> assertEquals(expectedTables, angle.selectTables()); + case UPDATE -> assertEquals(expectedTables, angle.updateTables()); + case DELETE -> assertEquals(expectedTables, angle.deleteTables()); + default -> throw new IllegalStateException("未対応のSQL種別: " + expectedSqlType); + } + } + + static Stream repositoryMethodAndSqlType() { + return Stream.of( + Arguments.of("save", SqlType.INSERT), + Arguments.of("findById", SqlType.SELECT), + Arguments.of("deleteById", SqlType.DELETE), + Arguments.of("updateById", SqlType.UPDATE), + Arguments.of("updateByIdWithComment", SqlType.UPDATE) + ); } } From dae8953e17b674faa3e191082893aa110f48d801 Mon Sep 17 00:00:00 2001 From: irof Date: Mon, 23 Feb 2026 00:06:57 +0900 Subject: [PATCH 19/22] =?UTF-8?q?refactor:=20Query=E3=81=8B=E3=82=89?= =?UTF-8?q?=E3=81=AESQL=E7=A8=AE=E5=88=A5=E6=8E=A8=E6=B8=AC=E3=82=92SqlTyp?= =?UTF-8?q?e=E3=81=AB=E9=9B=86=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../domain/model/data/rdbaccess/SqlType.java | 41 +++++++++++++++++++ .../SpringDataJdbcStatementsReader.java | 13 +----- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java index 80cb80214..9f7d47bb4 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java @@ -3,7 +3,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Locale; import java.util.List; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -51,4 +53,43 @@ public Table extractTable(String sql, SqlStatementId sqlStatementId) { public Table unexpectedTable() { return new Table("(解析失敗)"); } + + public static Optional inferSqlTypeFromQuery(String query) { + String normalizedQuery = skipLeadingSqlDecorations(query).toLowerCase(Locale.ROOT); + if (normalizedQuery.startsWith("insert")) return Optional.of(INSERT); + if (normalizedQuery.startsWith("select")) return Optional.of(SELECT); + if (normalizedQuery.startsWith("update")) return Optional.of(UPDATE); + if (normalizedQuery.startsWith("delete")) return Optional.of(DELETE); + + logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query); + return Optional.empty(); + } + + private static String skipLeadingSqlDecorations(String query) { + String remaining = query; + while (true) { + String trimmed = remaining.stripLeading(); + if (trimmed.startsWith("\uFEFF")) { + remaining = trimmed.substring(1); + continue; + } + if (trimmed.startsWith("--")) { + int newlineIndex = trimmed.indexOf('\n'); + if (newlineIndex < 0) return ""; + remaining = trimmed.substring(newlineIndex + 1); + continue; + } + if (trimmed.startsWith("/*")) { + int commentEndIndex = trimmed.indexOf("*/"); + if (commentEndIndex < 0) return ""; + remaining = trimmed.substring(commentEndIndex + 2); + continue; + } + if (trimmed.startsWith("(")) { + remaining = trimmed.substring(1); + continue; + } + return trimmed; + } + } } diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index 76d77d49a..07029b717 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -67,7 +67,7 @@ public SqlStatements readFrom(Collection classDeclarations) { .forEach(methodName -> { Query query = queryByMethodName.getOrDefault(methodName, Query.unsupported()); Optional inferredSqlType = query.supported() - ? inferSqlTypeFromQuery(query.text()) + ? SqlType.inferSqlTypeFromQuery(query.text()) : inferSqlType(methodName); inferredSqlType.ifPresent(sqlType -> { Query resolvedQuery = query.supported() @@ -180,17 +180,6 @@ private Optional inferSqlType(String methodName) { return Optional.empty(); } - private Optional inferSqlTypeFromQuery(String query) { - String normalizedQuery = normalizeQuery(query).toLowerCase(Locale.ROOT); - if (normalizedQuery.startsWith("insert")) return Optional.of(SqlType.INSERT); - if (normalizedQuery.startsWith("select")) return Optional.of(SqlType.SELECT); - if (normalizedQuery.startsWith("update")) return Optional.of(SqlType.UPDATE); - if (normalizedQuery.startsWith("delete")) return Optional.of(SqlType.DELETE); - - logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query); - return Optional.empty(); - } - private String normalizeQuery(String query) { return skipLeadingSqlDecorations(query); } From 0c86a8d9816dbf0ecc338fd1ae2962eafe7bb851 Mon Sep 17 00:00:00 2001 From: irof Date: Mon, 23 Feb 2026 00:14:56 +0900 Subject: [PATCH 20/22] =?UTF-8?q?refactor:=20SQL=E5=85=88=E9=A0=AD?= =?UTF-8?q?=E8=A3=85=E9=A3=BE=E3=81=AE=E9=99=A4=E5=8E=BB=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92Query=E3=81=AB=E9=9B=86=E7=B4=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../domain/model/data/rdbaccess/Query.java | 28 +++++++++++++++ .../domain/model/data/rdbaccess/SqlType.java | 30 +--------------- .../SpringDataJdbcStatementsReader.java | 34 +------------------ 3 files changed, 30 insertions(+), 62 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java index e3891c3cb..3c6e1132d 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java @@ -31,4 +31,32 @@ public String text() { public boolean supported() { return !UNSUPPORTED.equals(text); } + + public static String normalizeSql(String query) { + String remaining = query; + while (true) { + String trimmed = remaining.stripLeading(); + if (trimmed.startsWith("\uFEFF")) { + remaining = trimmed.substring(1); + continue; + } + if (trimmed.startsWith("--")) { + int newlineIndex = trimmed.indexOf('\n'); + if (newlineIndex < 0) return ""; + remaining = trimmed.substring(newlineIndex + 1); + continue; + } + if (trimmed.startsWith("/*")) { + int commentEndIndex = trimmed.indexOf("*/"); + if (commentEndIndex < 0) return ""; + remaining = trimmed.substring(commentEndIndex + 2); + continue; + } + if (trimmed.startsWith("(")) { + remaining = trimmed.substring(1); + continue; + } + return trimmed; + } + } } diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java index 9f7d47bb4..65a6ee38b 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java @@ -55,7 +55,7 @@ public Table unexpectedTable() { } public static Optional inferSqlTypeFromQuery(String query) { - String normalizedQuery = skipLeadingSqlDecorations(query).toLowerCase(Locale.ROOT); + String normalizedQuery = Query.normalizeSql(query).toLowerCase(Locale.ROOT); if (normalizedQuery.startsWith("insert")) return Optional.of(INSERT); if (normalizedQuery.startsWith("select")) return Optional.of(SELECT); if (normalizedQuery.startsWith("update")) return Optional.of(UPDATE); @@ -64,32 +64,4 @@ public static Optional inferSqlTypeFromQuery(String query) { logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query); return Optional.empty(); } - - private static String skipLeadingSqlDecorations(String query) { - String remaining = query; - while (true) { - String trimmed = remaining.stripLeading(); - if (trimmed.startsWith("\uFEFF")) { - remaining = trimmed.substring(1); - continue; - } - if (trimmed.startsWith("--")) { - int newlineIndex = trimmed.indexOf('\n'); - if (newlineIndex < 0) return ""; - remaining = trimmed.substring(newlineIndex + 1); - continue; - } - if (trimmed.startsWith("/*")) { - int commentEndIndex = trimmed.indexOf("*/"); - if (commentEndIndex < 0) return ""; - remaining = trimmed.substring(commentEndIndex + 2); - continue; - } - if (trimmed.startsWith("(")) { - remaining = trimmed.substring(1); - continue; - } - return trimmed; - } - } } diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index 07029b717..c6f920110 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -47,7 +47,7 @@ public SqlStatements readFrom(Collection classDeclarations) { .filter(annotation -> annotation.id().fqn().equals(SPRING_DATA_QUERY)) .findFirst() .flatMap(annotation -> annotation.elementTextOf("value")) - .map(this::normalizeQuery) + .map(Query::normalizeSql) .filter(value -> !value.isBlank()) .map(Query::from) .orElse(Query.unsupported()), @@ -180,38 +180,6 @@ private Optional inferSqlType(String methodName) { return Optional.empty(); } - private String normalizeQuery(String query) { - return skipLeadingSqlDecorations(query); - } - - private String skipLeadingSqlDecorations(String query) { - String remaining = query; - while (true) { - String trimmed = remaining.stripLeading(); - if (trimmed.startsWith("\uFEFF")) { - remaining = trimmed.substring(1); - continue; - } - if (trimmed.startsWith("--")) { - int newlineIndex = trimmed.indexOf('\n'); - if (newlineIndex < 0) return ""; - remaining = trimmed.substring(newlineIndex + 1); - continue; - } - if (trimmed.startsWith("/*")) { - int commentEndIndex = trimmed.indexOf("*/"); - if (commentEndIndex < 0) return ""; - remaining = trimmed.substring(commentEndIndex + 2); - continue; - } - if (trimmed.startsWith("(")) { - remaining = trimmed.substring(1); - continue; - } - return trimmed; - } - } - private String defaultQuery(SqlType sqlType, String tableName) { return switch (sqlType) { case INSERT -> "insert into " + tableName + " values (?)"; From 7c31ec456dd80b2ecf3db23ecd2738780c7f4b32 Mon Sep 17 00:00:00 2001 From: irof Date: Mon, 23 Feb 2026 00:21:20 +0900 Subject: [PATCH 21/22] =?UTF-8?q?refactor:=20inferSqlTypeFromQuery?= =?UTF-8?q?=E3=81=8CQuery=E3=82=92=E5=8F=97=E3=81=91=E5=8F=96=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENT: Codex --- .../org/dddjava/jig/domain/model/data/rdbaccess/Query.java | 6 +++++- .../dddjava/jig/domain/model/data/rdbaccess/SqlType.java | 6 +++--- .../springdatajdbc/SpringDataJdbcStatementsReader.java | 5 +++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java index 3c6e1132d..f192a31ee 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java @@ -32,7 +32,11 @@ public boolean supported() { return !UNSUPPORTED.equals(text); } - public static String normalizeSql(String query) { + public String normalizedQuery() { + return normalizeSql(text()); + } + + private static String normalizeSql(String query) { String remaining = query; while (true) { String trimmed = remaining.stripLeading(); diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java index 65a6ee38b..1ccd6e37a 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java @@ -54,14 +54,14 @@ public Table unexpectedTable() { return new Table("(解析失敗)"); } - public static Optional inferSqlTypeFromQuery(String query) { - String normalizedQuery = Query.normalizeSql(query).toLowerCase(Locale.ROOT); + public static Optional inferSqlTypeFromQuery(Query query) { + String normalizedQuery = query.normalizedQuery().toLowerCase(Locale.ROOT); if (normalizedQuery.startsWith("insert")) return Optional.of(INSERT); if (normalizedQuery.startsWith("select")) return Optional.of(SELECT); if (normalizedQuery.startsWith("update")) return Optional.of(UPDATE); if (normalizedQuery.startsWith("delete")) return Optional.of(DELETE); - logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query); + logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query.text()); return Optional.empty(); } } diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index c6f920110..ce6ac989b 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -47,7 +47,8 @@ public SqlStatements readFrom(Collection classDeclarations) { .filter(annotation -> annotation.id().fqn().equals(SPRING_DATA_QUERY)) .findFirst() .flatMap(annotation -> annotation.elementTextOf("value")) - .map(Query::normalizeSql) + .map(Query::from) + .map(Query::normalizedQuery) .filter(value -> !value.isBlank()) .map(Query::from) .orElse(Query.unsupported()), @@ -67,7 +68,7 @@ public SqlStatements readFrom(Collection classDeclarations) { .forEach(methodName -> { Query query = queryByMethodName.getOrDefault(methodName, Query.unsupported()); Optional inferredSqlType = query.supported() - ? SqlType.inferSqlTypeFromQuery(query.text()) + ? SqlType.inferSqlTypeFromQuery(query) : inferSqlType(methodName); inferredSqlType.ifPresent(sqlType -> { Query resolvedQuery = query.supported() From f4472e5eb87e23c861d91acf2814c11e46415ba9 Mon Sep 17 00:00:00 2001 From: irof Date: Mon, 23 Feb 2026 00:37:45 +0900 Subject: [PATCH 22/22] =?UTF-8?q?refactor:=20Query=E3=81=ABnormalizedQuery?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rawTextとnormalizedQueryを分離し、SQLの正規化処理をQuery内に集約しました。これにより、SQL解析ロジックの一貫性が向上し、コードの可読性とメンテナンス性が改善されました。 --- .../domain/model/data/rdbaccess/Query.java | 24 ++++++++++++------- .../model/data/rdbaccess/SqlStatement.java | 2 +- .../domain/model/data/rdbaccess/SqlType.java | 4 ++-- .../SpringDataJdbcStatementsReader.java | 3 --- .../mybatis/MyBatisStatementReaderTest.java | 7 +++--- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java index f192a31ee..e9be45fa9 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/Query.java @@ -5,35 +5,43 @@ /** * クエリ */ -public record Query(String text) { +public record Query(String rawText, String normalizedQuery) { public static final String UNSUPPORTED = "<>"; public static Query from(@Nullable String text) { if (text == null) return unsupported(); - return new Query(text); + var normalizedQuery = normalizeSql(text); + if (normalizedQuery.isEmpty()) return unsupported(); + return new Query(text, normalizedQuery); } public static Query unsupported() { - return new Query(UNSUPPORTED); + return new Query(UNSUPPORTED, UNSUPPORTED); } @Override - public String text() { - if (UNSUPPORTED.equals(text)) { + public String rawText() { + if (UNSUPPORTED.equals(rawText)) { // 特殊値を返さないようにする // Queryのtextは外部から使用しないので例外でよい。これが発生したらバグ。 throw new IllegalArgumentException("BUG!!"); } - return text; + return rawText; } public boolean supported() { - return !UNSUPPORTED.equals(text); + return !UNSUPPORTED.equals(rawText); } + @Override public String normalizedQuery() { - return normalizeSql(text()); + if (UNSUPPORTED.equals(rawText)) { + // 特殊値を返さないようにする + // Queryのtextは外部から使用しないので例外でよい。これが発生したらバグ。 + throw new IllegalArgumentException("BUG!!"); + } + return normalizedQuery; } private static String normalizeSql(String query) { diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatement.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatement.java index e665768b6..0070a1f52 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatement.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlStatement.java @@ -7,7 +7,7 @@ public record SqlStatement(SqlStatementId sqlStatementId, Query query, SqlType s public Tables tables() { if (query.supported()) { - Table table = sqlType.extractTable(query.text(), sqlStatementId); + Table table = sqlType.extractTable(query.normalizedQuery(), sqlStatementId); return new Tables(table); } return new Tables(sqlType.unexpectedTable()); diff --git a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java index 1ccd6e37a..eaceb30c6 100644 --- a/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java +++ b/jig-core/src/main/java/org/dddjava/jig/domain/model/data/rdbaccess/SqlType.java @@ -3,8 +3,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Locale; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -61,7 +61,7 @@ public static Optional inferSqlTypeFromQuery(Query query) { if (normalizedQuery.startsWith("update")) return Optional.of(UPDATE); if (normalizedQuery.startsWith("delete")) return Optional.of(DELETE); - logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query.text()); + logger.info("SQLの種類がQuery文字列 [{}] から判別できませんでした。", query.rawText()); return Optional.empty(); } } diff --git a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java index ce6ac989b..c38360837 100644 --- a/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java +++ b/jig-core/src/main/java/org/dddjava/jig/infrastructure/springdatajdbc/SpringDataJdbcStatementsReader.java @@ -48,9 +48,6 @@ public SqlStatements readFrom(Collection classDeclarations) { .findFirst() .flatMap(annotation -> annotation.elementTextOf("value")) .map(Query::from) - .map(Query::normalizedQuery) - .filter(value -> !value.isBlank()) - .map(Query::from) .orElse(Query.unsupported()), (left, right) -> left.supported() ? left : right.supported() ? right : left, LinkedHashMap::new)) diff --git a/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java b/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java index 84cfd49e9..19ac3dfe2 100644 --- a/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java +++ b/jig-core/src/test/java/org/dddjava/jig/infrastructure/mybatis/MyBatisStatementReaderTest.java @@ -16,6 +16,7 @@ import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; @JigTest class MyBatisStatementReaderTest { @@ -34,8 +35,8 @@ class MyBatisStatementReaderTest { SqlStatement myBatisStatement = myBatisStatements.findById(SqlStatementId.from(ComplexMapper.class.getCanonicalName() + ".select_ognl")).orElseThrow(); assertEquals("[(解析失敗)]", myBatisStatement.tables().asText()); - // OGNLを使ったSQLは現時点では空になる - assertEquals("", myBatisStatement.query().text()); + // OGNLを使ったSQLは現時点では空になりunsupportedになる + assertFalse(myBatisStatement.query().supported()); } @Test @@ -47,7 +48,7 @@ class MyBatisStatementReaderTest { assertEquals("[(解析失敗)]", myBatisStatement.tables().asText()); // OGNLを使ったSQLは現時点では空になる // ・・・のだが、 タグなどで分割されているとOGNLを使用していない部分だけクエリが出てくる - assertEquals("order by 1", myBatisStatement.query().text()); + assertEquals("order by 1", myBatisStatement.query().rawText()); } @ParameterizedTest