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