feat: 增加生成基本文件的工具
This commit is contained in:
262
src/main/java/com/lanyuanxiaoyao/service/template/Helper.java
Normal file
262
src/main/java/com/lanyuanxiaoyao/service/template/Helper.java
Normal file
@@ -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建表语句
|
||||||
|
* <p>
|
||||||
|
* 该工具类用于生成数据库表结构的DDL语句,通过扫描指定包下的实体类,
|
||||||
|
* 利用Hibernate的SchemaExport工具生成建表SQL脚本。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 使用示例:
|
||||||
|
* <pre>
|
||||||
|
* 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
|
||||||
|
* );
|
||||||
|
* </pre>
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
*/
|
||||||
|
public class Helper {
|
||||||
|
public static void generateDDL(
|
||||||
|
Set<String> 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<String> 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<SaveItem, %s> 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<String> scanEntityPackage(Set<String> entityPackages) {
|
||||||
|
var scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||||
|
scanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
|
||||||
|
var classNames = new HashSet<String>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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建表语句
|
|
||||||
* <p>
|
|
||||||
* 该工具类用于生成数据库表结构的DDL语句,通过扫描指定包下的实体类,
|
|
||||||
* 利用Hibernate的SchemaExport工具生成建表SQL脚本。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 使用示例:
|
|
||||||
* <pre>
|
|
||||||
* 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
|
|
||||||
* );
|
|
||||||
* </pre>
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author lanyuanxiaoyao
|
|
||||||
*/
|
|
||||||
public class DDLGenerator {
|
|
||||||
public static void generateDDL(
|
|
||||||
List<String> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user