From bf4615ca3c76b315df4d3a4ffcf7fb766e577d81 Mon Sep 17 00:00:00 2001
From: lanyuanxiaoyao
Date: Mon, 22 Sep 2025 18:04:57 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=94=9F=E6=88=90?=
=?UTF-8?q?=E5=9F=BA=E6=9C=AC=E6=96=87=E4=BB=B6=E7=9A=84=E5=B7=A5=E5=85=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../service/template/Helper.java | 262 ++++++++++++++++++
.../service/template/util/DDLGenerator.java | 116 --------
2 files changed, 262 insertions(+), 116 deletions(-)
create mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/Helper.java
delete mode 100644 src/main/java/com/lanyuanxiaoyao/service/template/util/DDLGenerator.java
diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/Helper.java b/src/main/java/com/lanyuanxiaoyao/service/template/Helper.java
new file mode 100644
index 0000000..1c5f10d
--- /dev/null
+++ b/src/main/java/com/lanyuanxiaoyao/service/template/Helper.java
@@ -0,0 +1,262 @@
+package com.lanyuanxiaoyao.service.template;
+
+import jakarta.persistence.Entity;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+import org.hibernate.boot.MetadataSources;
+import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
+import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
+import org.hibernate.tool.hbm2ddl.SchemaExport;
+import org.hibernate.tool.schema.TargetType;
+import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.type.filter.AnnotationTypeFilter;
+import org.springframework.util.ClassUtils;
+
+/**
+ * 构造DDL建表语句
+ *
+ * 该工具类用于生成数据库表结构的DDL语句,通过扫描指定包下的实体类,
+ * 利用Hibernate的SchemaExport工具生成建表SQL脚本。
+ *
+ *
+ *
+ * 使用示例:
+ *
+ * DDLGenerator.generateDDL(
+ * List.of("com.example.entity", "com.another.package"),
+ * "./sql",
+ * MySQL8Dialect.class,
+ * "jdbc:mysql://localhost:3306/test",
+ * "username",
+ * "password",
+ * com.mysql.cj.jdbc.Driver.class
+ * );
+ *
+ *
+ *
+ * @author lanyuanxiaoyao
+ */
+public class Helper {
+ public static void generateDDL(
+ Set entityPackages,
+ String ddlFilePath,
+ Class> dialect,
+ String jdbc,
+ String username,
+ String password,
+ Class> driver
+ ) {
+ var metadataSources = new MetadataSources(
+ new StandardServiceRegistryBuilder()
+ .applySetting("hibernate.dialect", dialect.getName())
+ .applySetting("hibernate.physical_naming_strategy", CamelCaseToUnderscoresNamingStrategy.class.getName())
+ .applySetting("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName())
+ .applySetting("hibernate.connection.url", jdbc)
+ .applySetting("hibernate.connection.username", username)
+ .applySetting("hibernate.connection.password", password)
+ .applySetting("hibernate.connection.driver_class", driver.getName())
+ .build()
+ );
+
+ for (String className : scanEntityPackage(entityPackages)) {
+ try {
+ var entityClass = ClassUtils.forName(className, Helper.class.getClassLoader());
+ metadataSources.addAnnotatedClass(entityClass);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Failed to load entity class: " + className, e);
+ }
+ }
+
+ var export = new SchemaExport();
+ export.setFormat(true);
+ export.setDelimiter(";");
+ export.setOutputFile(ddlFilePath + "/" + dialect.getSimpleName() + ".sql");
+ export.setOverrideOutputFileContent();
+ export.execute(EnumSet.of(TargetType.SCRIPT), SchemaExport.Action.CREATE, metadataSources.buildMetadata());
+ }
+
+ /**
+ * 兼容旧版本的方法签名
+ *
+ * @param entityPackage 实体类包路径
+ * @param ddlFilePath DDL文件输出路径
+ * @param dialect 方言类
+ * @param jdbc JDBC连接URL
+ * @param username 数据库用户名
+ * @param password 数据库密码
+ * @param driver JDBC驱动类
+ */
+ public static void generateDDL(
+ String entityPackage,
+ String ddlFilePath,
+ Class> dialect,
+ String jdbc,
+ String username,
+ String password,
+ Class> driver
+ ) {
+ generateDDL(Set.of(entityPackage), ddlFilePath, dialect, jdbc, username, password, driver);
+ }
+
+ public static void generateBasicFiles(Set entityPackages, String projectRootPackage, String projectRootPath, boolean override) throws IOException {
+ for (String className : scanEntityPackage(entityPackages)) {
+ try {
+ var entityClass = ClassUtils.forName(className, Helper.class.getClassLoader());
+ var name = entityClass.getSimpleName();
+
+ // Repository
+ Files.createDirectories(Path.of(projectRootPath, "repository"));
+ var repositoryFilePath = Path.of(projectRootPath, "repository", name + "Repository.java");
+ if (Files.notExists(repositoryFilePath) || override) {
+ Files.writeString(repositoryFilePath, """
+ package %s.repository;
+
+ import %s;
+ import com.lanyuanxiaoyao.service.template.repository.SimpleRepository;
+ import org.springframework.stereotype.Repository;
+
+ @Repository
+ public interface %sRepository extends SimpleRepository<%s> {
+ }
+
+ """.formatted(projectRootPackage, className, name, name));
+ }
+
+ // Service
+ Files.createDirectories(Path.of(projectRootPath, "service"));
+ var serviceFilePath = Path.of(projectRootPath, "service", name + "Service.java");
+ if (Files.notExists(serviceFilePath) || override) {
+ Files.writeString(serviceFilePath, """
+ package %s.service;
+
+ import %s;
+ import %s.repository.%sRepository;
+ import com.lanyuanxiaoyao.service.template.service.SimpleServiceSupport;
+ import lombok.extern.slf4j.Slf4j;
+ import org.springframework.stereotype.Service;
+
+ @Slf4j
+ @Service
+ public class %sService extends SimpleServiceSupport<%s> {
+ public %sService(%sRepository repository) {
+ super(repository);
+ }
+ }
+
+ """.formatted(projectRootPackage, className, projectRootPackage, name, name, name, name, name));
+ }
+
+ // Controller
+ Files.createDirectories(Path.of(projectRootPath, "controller"));
+ var controllerFilePath = Path.of(projectRootPath, "controller", name + "Controller.java");
+ if (Files.notExists(controllerFilePath) || override) {
+ Files.writeString(controllerFilePath, """
+ package %s.controller;
+
+ import %s;
+ import %s.service.%sService;
+ import com.lanyuanxiaoyao.service.template.controller.SimpleControllerSupport;
+ import java.util.function.Function;
+ import lombok.extern.slf4j.Slf4j;
+ import org.springframework.web.bind.annotation.RequestMapping;
+ import org.springframework.web.bind.annotation.RestController;
+
+ @Slf4j
+ @RestController
+ @RequestMapping("%s")
+ public class %sController extends SimpleControllerSupport<%s, %sController.SaveItem, %sController.ListItem, %sController.DetailItem> {
+ public %sController(%sService service) {
+ super(service);
+ }
+
+ @Override
+ protected Function saveItemMapper() {
+ return null;
+ }
+
+ @Override
+ protected Function<%s, ListItem> listItemMapper() {
+ return null;
+ }
+
+ @Override
+ protected Function<%s, DetailItem> detailItemMapper() {
+ return null;
+ }
+
+ public record SaveItem() {
+ }
+
+ public record ListItem() {
+ }
+
+ public record DetailItem() {
+ }
+ }
+
+ """.formatted(projectRootPackage, className, projectRootPackage, name, camelConvert(name), name, name, name, name, name, name, name, name, name, name));
+ }
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Failed to load entity class: " + className, e);
+ }
+ }
+ }
+
+ public static void generateBasicFiles(String entityPackage, String projectRootPackage, String projectRootPath, boolean override) throws IOException {
+ generateBasicFiles(Set.of(entityPackage), projectRootPackage, projectRootPath, override);
+ }
+
+ private static String camelConvert(String camelCase) {
+ if (camelCase == null || camelCase.isEmpty()) {
+ return camelCase;
+ }
+
+ StringBuilder result = new StringBuilder();
+ // 处理第一个字符,直接转为小写
+ result.append(Character.toLowerCase(camelCase.charAt(0)));
+
+ // 处理剩余字符
+ for (int i = 1; i < camelCase.length(); i++) {
+ char currentChar = camelCase.charAt(i);
+
+ // 如果是大写字母,且前一个字符不是下划线,并且不是第一个字符
+ if (Character.isUpperCase(currentChar)) {
+ // 检查前一个字符是否为大写,如果是连续大写,可能是缩写(如HTTP)
+ // 这种情况只在遇到小写字母前才添加下划线
+ char previousChar = camelCase.charAt(i - 1);
+ if (Character.isLowerCase(previousChar) || (i < camelCase.length() - 1 && Character.isLowerCase(camelCase.charAt(i + 1)))) {
+ result.append('_');
+ }
+ result.append(Character.toLowerCase(currentChar));
+ } else {
+ result.append(currentChar);
+ }
+ }
+
+ return result.toString();
+ }
+
+ private static Set scanEntityPackage(Set entityPackages) {
+ var scanner = new ClassPathScanningCandidateComponentProvider(false);
+ scanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
+ var classNames = new HashSet();
+ for (String entityPackage : entityPackages) {
+ var candidates = scanner.findCandidateComponents(entityPackage);
+
+ // 将找到的实体类添加到metadataSources中
+ for (var candidate : candidates) {
+ // 处理candidate或getBeanClassName可能为null的情况
+ if (candidate == null || candidate.getBeanClassName() == null) {
+ continue;
+ }
+ classNames.add(candidate.getBeanClassName());
+ }
+ }
+ return classNames;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/lanyuanxiaoyao/service/template/util/DDLGenerator.java b/src/main/java/com/lanyuanxiaoyao/service/template/util/DDLGenerator.java
deleted file mode 100644
index a430142..0000000
--- a/src/main/java/com/lanyuanxiaoyao/service/template/util/DDLGenerator.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.lanyuanxiaoyao.service.template.util;
-
-import jakarta.persistence.Entity;
-import java.util.EnumSet;
-import java.util.List;
-import org.hibernate.boot.MetadataSources;
-import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
-import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
-import org.hibernate.tool.hbm2ddl.SchemaExport;
-import org.hibernate.tool.schema.TargetType;
-import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
-import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
-import org.springframework.core.type.filter.AnnotationTypeFilter;
-import org.springframework.util.ClassUtils;
-
-/**
- * 构造DDL建表语句
- *
- * 该工具类用于生成数据库表结构的DDL语句,通过扫描指定包下的实体类,
- * 利用Hibernate的SchemaExport工具生成建表SQL脚本。
- *
- *
- *
- * 使用示例:
- *
- * DDLGenerator.generateDDL(
- * List.of("com.example.entity", "com.another.package"),
- * "./sql",
- * MySQL8Dialect.class,
- * "jdbc:mysql://localhost:3306/test",
- * "username",
- * "password",
- * com.mysql.cj.jdbc.Driver.class
- * );
- *
- *
- *
- * @author lanyuanxiaoyao
- */
-public class DDLGenerator {
- public static void generateDDL(
- List entityPackages,
- String ddlFilePath,
- Class> dialect,
- String jdbc,
- String username,
- String password,
- Class> driver
- ) {
- var metadataSources = new MetadataSources(
- new StandardServiceRegistryBuilder()
- .applySetting("hibernate.dialect", dialect.getName())
- .applySetting("hibernate.physical_naming_strategy", CamelCaseToUnderscoresNamingStrategy.class.getName())
- .applySetting("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName())
- .applySetting("hibernate.connection.url", jdbc)
- .applySetting("hibernate.connection.username", username)
- .applySetting("hibernate.connection.password", password)
- .applySetting("hibernate.connection.driver_class", driver.getName())
- .build()
- );
-
- // 使用Spring类路径扫描方式查找所有@Entity注解的类
- var scanner = new ClassPathScanningCandidateComponentProvider(false);
- scanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
-
- // 遍历所有包路径,扫描@Entity注解的类
- for (String entityPackage : entityPackages) {
- var candidates = scanner.findCandidateComponents(entityPackage);
-
- // 将找到的实体类添加到metadataSources中
- for (var candidate : candidates) {
- // 处理candidate或getBeanClassName可能为null的情况
- if (candidate == null || candidate.getBeanClassName() == null) {
- continue;
- }
-
- try {
- var entityClass = ClassUtils.forName(candidate.getBeanClassName(), DDLGenerator.class.getClassLoader());
- metadataSources.addAnnotatedClass(entityClass);
- } catch (ClassNotFoundException e) {
- throw new RuntimeException("Failed to load entity class: " + candidate.getBeanClassName(), e);
- }
- }
- }
-
- var export = new SchemaExport();
- export.setFormat(true);
- export.setDelimiter(";");
- export.setOutputFile(ddlFilePath + "/" + dialect.getSimpleName() + ".sql");
- export.setOverrideOutputFileContent();
- export.execute(EnumSet.of(TargetType.SCRIPT), SchemaExport.Action.CREATE, metadataSources.buildMetadata());
- }
-
- /**
- * 兼容旧版本的方法签名
- *
- * @param entityPackage 实体类包路径
- * @param ddlFilePath DDL文件输出路径
- * @param dialect 方言类
- * @param jdbc JDBC连接URL
- * @param username 数据库用户名
- * @param password 数据库密码
- * @param driver JDBC驱动类
- */
- public static void generateDDL(
- String entityPackage,
- String ddlFilePath,
- Class> dialect,
- String jdbc,
- String username,
- String password,
- Class> driver
- ) {
- generateDDL(List.of(entityPackage), ddlFilePath, dialect, jdbc, username, password, driver);
- }
-}
\ No newline at end of file