diff --git a/example/example-app/example-app-cmd-domain/pom.xml b/example/example-app/example-app-cmd-domain/pom.xml index b8e909ae..86a1c254 100644 --- a/example/example-app/example-app-cmd-domain/pom.xml +++ b/example/example-app/example-app-cmd-domain/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-app - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-app/example-app-cmd-meta/pom.xml b/example/example-app/example-app-cmd-meta/pom.xml index 3a87683c..c0ed3c91 100644 --- a/example/example-app/example-app-cmd-meta/pom.xml +++ b/example/example-app/example-app-cmd-meta/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-app - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-app/example-app-query/pom.xml b/example/example-app/example-app-query/pom.xml index 8b1d030e..f9d5cd14 100644 --- a/example/example-app/example-app-query/pom.xml +++ b/example/example-app/example-app-query/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-app - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-app/pom.xml b/example/example-app/pom.xml index 3c72466b..735483bf 100644 --- a/example/example-app/pom.xml +++ b/example/example-app/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-example - 3.4.23 + 3.4.24 ../pom.xml pom diff --git a/example/example-domain/example-domain-leave/pom.xml b/example/example-domain/example-domain-leave/pom.xml index 29de615f..d369d840 100644 --- a/example/example-domain/example-domain-leave/pom.xml +++ b/example/example-domain/example-domain-leave/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-domain - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-domain/example-domain-user/pom.xml b/example/example-domain/example-domain-user/pom.xml index d89473f8..010e9712 100644 --- a/example/example-domain/example-domain-user/pom.xml +++ b/example/example-domain/example-domain-user/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-domain - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-domain/pom.xml b/example/example-domain/pom.xml index 8bde6ca9..d0b4bf53 100644 --- a/example/example-domain/pom.xml +++ b/example/example-domain/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot springboot-example - 3.4.23 + 3.4.24 ../pom.xml 4.0.0 diff --git a/example/example-infra/example-infra-flow/pom.xml b/example/example-infra/example-infra-flow/pom.xml index 2b5e82f3..12e565f6 100644 --- a/example/example-infra/example-infra-flow/pom.xml +++ b/example/example-infra/example-infra-flow/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot example-infra - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-infra/example-infra-jpa/pom.xml b/example/example-infra/example-infra-jpa/pom.xml index cc1d6e0a..07fa83dc 100644 --- a/example/example-infra/example-infra-jpa/pom.xml +++ b/example/example-infra/example-infra-jpa/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot example-infra - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-infra/example-infra-security/pom.xml b/example/example-infra/example-infra-security/pom.xml index 1a05b954..d905d92a 100644 --- a/example/example-infra/example-infra-security/pom.xml +++ b/example/example-infra/example-infra-security/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot example-infra - 3.4.23 + 3.4.24 ../pom.xml diff --git a/example/example-infra/pom.xml b/example/example-infra/pom.xml index bc3d7b13..a1d0acfc 100644 --- a/example/example-infra/pom.xml +++ b/example/example-infra/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-example - 3.4.23 + 3.4.24 ../pom.xml pom diff --git a/example/example-interface/pom.xml b/example/example-interface/pom.xml index 6c508ee9..f6d78dd0 100644 --- a/example/example-interface/pom.xml +++ b/example/example-interface/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-example - 3.4.23 + 3.4.24 example-interface diff --git a/example/example-server/pom.xml b/example/example-server/pom.xml index 073c8ee2..be3ef03c 100644 --- a/example/example-server/pom.xml +++ b/example/example-server/pom.xml @@ -5,7 +5,7 @@ springboot-example com.codingapi.springboot - 3.4.23 + 3.4.24 4.0.0 diff --git a/example/pom.xml b/example/pom.xml index 05807946..24b4ebfb 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -19,7 +19,7 @@ springboot-example - 3.4.23 + 3.4.24 springboot-example springboot-example project for Spring Boot diff --git a/pom.xml b/pom.xml index 776f897e..861192da 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ com.codingapi.springboot springboot-parent - 3.4.23 + 3.4.24 https://github.com/codingapi/springboot-framewrok springboot-parent diff --git a/springboot-starter-data-authorization/pom.xml b/springboot-starter-data-authorization/pom.xml index a5282ee4..e6116bf4 100644 --- a/springboot-starter-data-authorization/pom.xml +++ b/springboot-starter-data-authorization/pom.xml @@ -6,7 +6,7 @@ com.codingapi.springboot springboot-parent - 3.4.23 + 3.4.24 springboot-starter-data-authorization diff --git a/springboot-starter-data-fast/pom.xml b/springboot-starter-data-fast/pom.xml index 69b340a6..82e10ab5 100644 --- a/springboot-starter-data-fast/pom.xml +++ b/springboot-starter-data-fast/pom.xml @@ -5,7 +5,7 @@ springboot-parent com.codingapi.springboot - 3.4.23 + 3.4.24 4.0.0 @@ -20,6 +20,11 @@ + + net.bytebuddy + byte-buddy + + org.springframework.boot spring-boot-starter-web diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassBuilder.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassBuilder.java new file mode 100644 index 00000000..94ed83f2 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassBuilder.java @@ -0,0 +1,302 @@ +package com.codingapi.springboot.fast.classloader; + +import com.codingapi.springboot.fast.metadata.EntityMetaData; +import jakarta.persistence.*; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.FieldAccessor; +import org.hibernate.annotations.Comment; +import org.springframework.util.StringUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 动态实体构建器 - 基于 EntityClass 元数据 + */ +public class DynamicEntityClassBuilder { + + /** + * 根据 EntityClass 构建动态实体 + */ + public static Class buildDynamicEntity(EntityMetaData entityMetaData) { + if (entityMetaData == null || entityMetaData.getClassName() == null) { + throw new IllegalArgumentException("Entity metadata cannot be null"); + } + + try { + DynamicType.Builder builder = new ByteBuddy() + .subclass(Object.class) + .name(entityMetaData.getClassName()) + .implement(Serializable.class) + .annotateType(buildEntityAnnotations(entityMetaData)); + + // 添加字段 + boolean hasPrimaryKey = false; + for (EntityMetaData.ColumnMeta column : entityMetaData.getColumns()) { + builder = addColumnField(builder, column); + if (column.isPrimaryKey()) { + hasPrimaryKey = true; + } + } + + // 如果没有主键,添加默认ID字段 + if (!hasPrimaryKey) { + builder = addDefaultIdField(builder); + } + + + Class clazz = builder.make() + .load(DynamicEntityClassBuilder.class.getClassLoader(), + ClassLoadingStrategy.Default.WRAPPER) + .getLoaded(); + + DynamicEntityClassLoaderContext.getInstance().registerClass(clazz); + + return clazz; + + } catch (Exception e) { + throw new RuntimeException("Failed to build dynamic entity: " + + entityMetaData.getClassName(), e); + } + } + + /** + * 构建实体类注解 + */ + private static AnnotationDescription[] buildEntityAnnotations(EntityMetaData entityMetaData) { + List annotations = new ArrayList<>(); + + // @Entity 注解 + annotations.add(AnnotationDescription.Builder.ofType(Entity.class).build()); + + // @Table 注解 + if (entityMetaData.getTable() != null) { + AnnotationDescription.Builder tableBuilder = + AnnotationDescription.Builder.ofType(Table.class); + + EntityMetaData.TableMeta tableMeta = entityMetaData.getTable(); + if (tableMeta.getName() != null && !tableMeta.getName().isEmpty()) { + tableBuilder = tableBuilder.define("name", tableMeta.getName()); + } + if (tableMeta.getCatalog() != null && !tableMeta.getCatalog().isEmpty()) { + tableBuilder = tableBuilder.define("catalog", tableMeta.getCatalog()); + } + if (tableMeta.getSchema() != null && !tableMeta.getSchema().isEmpty()) { + tableBuilder = tableBuilder.define("schema", tableMeta.getSchema()); + } + + annotations.add(tableBuilder.build()); + } + + // @Comment 注解 - 注释应该放在类上,而不是表注解上 + if (entityMetaData.getTable() != null && StringUtils.hasText(entityMetaData.getTable().getComment())) { + AnnotationDescription.Builder commentBuilder = + AnnotationDescription.Builder.ofType(Comment.class); + + EntityMetaData.TableMeta tableMeta = entityMetaData.getTable(); + commentBuilder = commentBuilder.define("value", tableMeta.getComment()); + annotations.add(commentBuilder.build()); + } + + return annotations.toArray(new AnnotationDescription[0]); + } + + /** + * 添加字段 + */ + private static DynamicType.Builder addColumnField(DynamicType.Builder builder, + EntityMetaData.ColumnMeta columnMeta) { + // 确定字段类型 + Class fieldType = columnMeta.getType(); + + // 构建字段名(转换驼峰命名) + String fieldName = convertToCamelCase(columnMeta.getName()); + + + + // 构建字段注解 + List fieldAnnotations = + buildFieldAnnotations(columnMeta, fieldName); + + // 开始定义字段 + DynamicType.Builder fieldBuilder = builder; + + + if (fieldAnnotations.isEmpty()) { + fieldBuilder = fieldBuilder.defineField(fieldName, fieldType, Visibility.PRIVATE); + }else { + fieldBuilder = fieldBuilder.defineField(fieldName, fieldType, Visibility.PRIVATE) + .annotateField(fieldAnnotations.toArray(new AnnotationDescription[0])); + } + + // 添加 getter 和 setter + String capitalizedFieldName = capitalize(fieldName); + String getterName = "get" + capitalizedFieldName; + String setterName = "set" + capitalizedFieldName; + + // 布尔类型特殊处理 + if (fieldType == Boolean.class || fieldType == Boolean.TYPE) { + getterName = "is" + capitalizedFieldName; + } + + fieldBuilder = fieldBuilder + .defineMethod(getterName, fieldType, Visibility.PUBLIC) + .intercept(FieldAccessor.ofField(fieldName)); + + fieldBuilder = fieldBuilder + .defineMethod(setterName, void.class, Visibility.PUBLIC) + .withParameter(fieldType) + .intercept(FieldAccessor.ofField(fieldName)); + + return fieldBuilder; + } + + /** + * 构建字段注解 + */ + private static List buildFieldAnnotations( + EntityMetaData.ColumnMeta columnMeta, String fieldName) { + + List annotations = new ArrayList<>(); + + // @Id 注解 + if (columnMeta.isPrimaryKey()) { + annotations.add(AnnotationDescription.Builder.ofType(Id.class).build()); + + // @GeneratedValue 注解 + if (columnMeta.getGeneratedValue() != null) { + EntityMetaData.GeneratedValueMeta genMeta = columnMeta.getGeneratedValue(); + AnnotationDescription.Builder genBuilder = + AnnotationDescription.Builder.ofType(GeneratedValue.class); + + if (genMeta.getStrategy() != null) { + genBuilder = genBuilder.define("strategy", genMeta.getStrategy()); + } + + if (genMeta.getGenerator() != null && !genMeta.getGenerator().isEmpty()) { + genBuilder = genBuilder.define("generator", genMeta.getGenerator()); + } + + annotations.add(genBuilder.build()); + } + } + + // @Column 注解 + AnnotationDescription.Builder columnBuilder = + AnnotationDescription.Builder.ofType(Column.class); + + columnBuilder = columnBuilder + .define("name", columnMeta.getName()) + .define("nullable", columnMeta.isNullable()) + .define("unique", columnMeta.isUnique()) + .define("insertable", columnMeta.isInsertable()) + .define("updatable", columnMeta.isUpdatable()); + + if (columnMeta.getLength() > 0) { + columnBuilder = columnBuilder.define("length", columnMeta.getLength()); + } + if (columnMeta.getPrecision() > 0) { + columnBuilder = columnBuilder.define("precision", columnMeta.getPrecision()); + } + if (columnMeta.getScale() > 0) { + columnBuilder = columnBuilder.define("scale", columnMeta.getScale()); + } + if (columnMeta.getColumnDefinition() != null && + !columnMeta.getColumnDefinition().isEmpty()) { + columnBuilder = columnBuilder.define("columnDefinition", + columnMeta.getColumnDefinition()); + } + + annotations.add(columnBuilder.build()); + + // @Comment 注解 + if (StringUtils.hasText(columnMeta.getComment())) { + annotations.add(AnnotationDescription.Builder.ofType(Comment.class) + .define("value", columnMeta.getComment()) + .build()); + } + + return annotations; + } + + /** + * 添加默认ID字段 - 修正版本 + */ + private static DynamicType.Builder addDefaultIdField(DynamicType.Builder builder) { + + + // 创建字段注解 + List fieldAnnotations = new ArrayList<>(); + fieldAnnotations.add(AnnotationDescription.Builder.ofType(Id.class).build()); + + // @GeneratedValue 注解 + AnnotationDescription.Builder genBuilder = + AnnotationDescription.Builder.ofType(GeneratedValue.class); + genBuilder = genBuilder.define("strategy", GenerationType.IDENTITY); + fieldAnnotations.add(genBuilder.build()); + + // @Column 注解 + AnnotationDescription.Builder columnBuilder = + AnnotationDescription.Builder.ofType(Column.class); + columnBuilder = columnBuilder + .define("name", "id") + .define("nullable", false); + fieldAnnotations.add(columnBuilder.build()); + + // 应用字段注解 + builder = builder.defineField("id", Long.class, Visibility.PRIVATE).annotateField(fieldAnnotations.toArray(new AnnotationDescription[0])); + + // 添加 getter 和 setter + builder = builder + .defineMethod("getId", Long.class, Visibility.PUBLIC) + .intercept(FieldAccessor.ofField("id")) + .defineMethod("setId", void.class, Visibility.PUBLIC) + .withParameter(Long.class) + .intercept(FieldAccessor.ofField("id")); + + return builder; + } + + /** + * 转换为驼峰命名 + */ + private static String convertToCamelCase(String name) { + if (name == null || name.isEmpty()) { + return name; + } + + // 处理下划线命名 + if (name.contains("_")) { + StringBuilder result = new StringBuilder(); + String[] parts = name.split("_"); + if (parts.length > 0) { + result.append(parts[0].toLowerCase()); + + for (int i = 1; i < parts.length; i++) { + if (!parts[i].isEmpty()) { + result.append(Character.toUpperCase(parts[i].charAt(0))); + if (parts[i].length() > 1) { + result.append(parts[i].substring(1).toLowerCase()); + } + } + } + } + return result.toString(); + } + + // 已经是驼峰命名,首字母小写 + return Character.toLowerCase(name.charAt(0)) + name.substring(1); + } + + private static String capitalize(String str) { + if (str == null || str.isEmpty()) { + return str; + } + return Character.toUpperCase(str.charAt(0)) + str.substring(1); + } +} \ No newline at end of file diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassLoader.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassLoader.java new file mode 100644 index 00000000..202a80dc --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassLoader.java @@ -0,0 +1,60 @@ +package com.codingapi.springboot.fast.classloader; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +class DynamicEntityClassLoader extends ClassLoader { + + private final Map> dynamicClasses = new ConcurrentHashMap<>(); + + public DynamicEntityClassLoader(ClassLoader parent) { + super(parent); + } + + public void registerClass(String className, Class clazz) { + dynamicClasses.put(className, clazz); + } + + public void registerClass(Class clazz) { + this.registerClass(clazz.getName(), clazz); + } + + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + // 首先检查已加载的动态类 + if (dynamicClasses.containsKey(name)) { + return dynamicClasses.get(name); + } + // 委托给父类 + return super.findClass(name); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return loadClass(name, false); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + // 首先检查是否已经加载 + Class c = findLoadedClass(name); + if (c == null) { + // 检查是否是动态类 + if (dynamicClasses.containsKey(name)) { + c = dynamicClasses.get(name); + } else { + // 委托给父类 + c = super.loadClass(name, resolve); + } + } + + if (resolve) { + resolveClass(c); + } + return c; + } + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassLoaderContext.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassLoaderContext.java new file mode 100644 index 00000000..555e4917 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/classloader/DynamicEntityClassLoaderContext.java @@ -0,0 +1,27 @@ +package com.codingapi.springboot.fast.classloader; + +import lombok.Getter; + +public class DynamicEntityClassLoaderContext { + + @Getter + private final static DynamicEntityClassLoaderContext instance = new DynamicEntityClassLoaderContext(); + + private final DynamicEntityClassLoader dynamicEntityClassLoader; + + private DynamicEntityClassLoaderContext(){ + dynamicEntityClassLoader = new DynamicEntityClassLoader(Thread.currentThread().getContextClassLoader() != null + ? Thread.currentThread().getContextClassLoader() + : getClass().getClassLoader()); + Thread.currentThread().setContextClassLoader(dynamicEntityClassLoader); + } + + public void registerClass(String className, Class clazz) { + dynamicEntityClassLoader.registerClass(className, clazz); + } + + public void registerClass(Class clazz) { + dynamicEntityClassLoader.registerClass(clazz); + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/dynamic/DynamicEntityBuilder.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/dynamic/DynamicEntityBuilder.java new file mode 100644 index 00000000..5a153b80 --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/dynamic/DynamicEntityBuilder.java @@ -0,0 +1,187 @@ +package com.codingapi.springboot.fast.dynamic; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.relational.ContributableDatabaseObject; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Dialect; +import org.hibernate.tool.schema.SourceType; +import org.hibernate.tool.schema.TargetType; +import org.hibernate.tool.schema.internal.ExceptionHandlerCollectingImpl; +import org.hibernate.tool.schema.spi.*; +import org.springframework.util.StringUtils; + +import java.util.*; + +public class DynamicEntityBuilder { + + private final Dialect dialect; + private final StandardServiceRegistry serviceRegistry; + private final SchemaManagementTool managementTool; + + public DynamicEntityBuilder(Class dialectClass, String jdbcUrl) { + this(dialectClass,jdbcUrl,null,null); + } + + public DynamicEntityBuilder(Class dialectClass, String jdbcUrl, String username, String password) { + try { + this.dialect = (Dialect) dialectClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException("Failed to instantiate dialect", e); + } + + StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder() + .applySetting(AvailableSettings.DIALECT, dialect.getClass().getName()) + .applySetting("hibernate.connection.url", jdbcUrl); + if (StringUtils.hasText(username)) { + builder.applySetting("hibernate.connection.username", username); + } + if (StringUtils.hasText(password)) { + builder.applySetting("hibernate.connection.password", password); + } + this.serviceRegistry = builder.build(); + this.managementTool = serviceRegistry.getService(SchemaManagementTool.class); + } + + private class ExecutionOptionsImpl implements ExecutionOptions { + + @Override + public Map getConfigurationValues() { + Map config = new HashMap<>(); + config.put(AvailableSettings.DIALECT, dialect.getClass().getName()); + return config; + } + + @Override + public boolean shouldManageNamespaces() { + return false; + } + + @Override + public ExceptionHandler getExceptionHandler() { + return new ExceptionHandlerCollectingImpl(); + } + } + + private static class ContributableMatcherImpl implements ContributableMatcher { + @Override + public boolean matches(ContributableDatabaseObject contributed) { + return true; + } + } + + private static class SourceDescriptorImpl implements SourceDescriptor { + + @Override + public SourceType getSourceType() { + return SourceType.METADATA; + } + + @Override + public ScriptSourceInput getScriptSourceInput() { + return extractor -> List.of(); + } + } + + private static class TargetDescriptorImpl implements TargetDescriptor { + private final List sqlCommands = new ArrayList<>(); + + @Override + public EnumSet getTargetTypes() { + return EnumSet.of(TargetType.SCRIPT); + } + + @Override + public ScriptTargetOutput getScriptTargetOutput() { + return new ScriptTargetOutput() { + @Override + public void prepare() { + + } + + @Override + public void accept(String command) { + sqlCommands.add(command); + } + + @Override + public void release() { + + } + }; + } + + public String getDDL() { + return String.join("\n", sqlCommands); + } + } + + public String generateDropTableDDL(Class entityClass) { + MetadataSources metadataSources = new MetadataSources(serviceRegistry); + metadataSources.addAnnotatedClass(entityClass); + Metadata metadata = metadataSources.buildMetadata(); + TargetDescriptorImpl targetDescriptor = new TargetDescriptorImpl(); + + managementTool.getSchemaDropper(Collections.emptyMap()).doDrop(metadata, + new ExecutionOptionsImpl(), + new ContributableMatcherImpl(), + new SourceDescriptorImpl(), + targetDescriptor); + + return targetDescriptor.getDDL(); + } + + public String generateCreateTableDDL(Class entityClass) { + MetadataSources metadataSources = new MetadataSources(serviceRegistry); + metadataSources.addAnnotatedClass(entityClass); + Metadata metadata = metadataSources.buildMetadata(); + + TargetDescriptorImpl targetDescriptor = new TargetDescriptorImpl(); + + managementTool.getSchemaCreator(Collections.emptyMap()).doCreation(metadata, + new ExecutionOptionsImpl(), + new ContributableMatcherImpl(), + new SourceDescriptorImpl(), + targetDescriptor); + return targetDescriptor.getDDL(); + } + + public String generateMigratorTableDDL(Class entityClass) { + MetadataSources metadataSources = new MetadataSources(serviceRegistry); + metadataSources.addAnnotatedClass(entityClass); + Metadata metadata = metadataSources.buildMetadata(); + + TargetDescriptorImpl targetDescriptor = new TargetDescriptorImpl(); + + managementTool.getSchemaMigrator(Collections.emptyMap()).doMigration(metadata, + new ExecutionOptionsImpl(), + new ContributableMatcherImpl(), + targetDescriptor); + return targetDescriptor.getDDL(); + } + + public List validatorTable(Class entityClass) { + ExceptionHandlerCollectingImpl exceptionHandler = new ExceptionHandlerCollectingImpl(); + MetadataSources metadataSources = new MetadataSources(serviceRegistry); + metadataSources.addAnnotatedClass(entityClass); + Metadata metadata = metadataSources.buildMetadata(); + List exceptionList = new ArrayList<>(); + try { + managementTool.getSchemaValidator(Collections.emptyMap()).doValidation(metadata, + new ExecutionOptionsImpl() { + @Override + public ExceptionHandler getExceptionHandler() { + return exceptionHandler; + } + }, + new ContributableMatcherImpl()); + } catch (Exception e) { + exceptionList.add(e); + } + exceptionList.addAll(exceptionHandler.getExceptions()); + return exceptionList; + } + +} diff --git a/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/metadata/EntityMetaData.java b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/metadata/EntityMetaData.java new file mode 100644 index 00000000..171f405c --- /dev/null +++ b/springboot-starter-data-fast/src/main/java/com/codingapi/springboot/fast/metadata/EntityMetaData.java @@ -0,0 +1,133 @@ +package com.codingapi.springboot.fast.metadata; + +import com.codingapi.springboot.fast.classloader.DynamicEntityClassBuilder; +import jakarta.persistence.GenerationType; +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class EntityMetaData { + + private final String className; + private final TableMeta table; + private final List columns; + + public EntityMetaData(String className) { + this.className = className; + this.table = new TableMeta(); + this.columns = new ArrayList<>(); + } + + public void setTable(String name,String catalog,String schema,String comment){ + this.table.setName(name); + this.table.setCatalog(catalog); + this.table.setSchema(schema); + this.table.setComment(comment); + } + + public void setTable(String name,String comment){ + this.setTable(name,null,null,comment); + } + + public void setTable(String name){ + this.setTable(name,null,null,null); + } + + public void addPrimaryKeyColumn(Class type,String name,GenerationType strategy,String generator,String comment,boolean unique,boolean nullable, boolean insertable, + boolean updatable,String columnDefinition, int length,int precision,int scale){ + ColumnMeta column = new ColumnMeta(); + column.setType(type); + column.setName(name); + column.setPrimaryKey(true); + GeneratedValueMeta generatedValueMeta = new GeneratedValueMeta(); + generatedValueMeta.setGenerator(generator); + generatedValueMeta.setStrategy(strategy); + column.setGeneratedValue(generatedValueMeta); + column.setComment(comment); + column.setUnique(unique); + column.setNullable(nullable); + column.setInsertable(insertable); + column.setUpdatable(updatable); + column.setColumnDefinition(columnDefinition); + column.setLength(length); + column.setPrecision(precision); + column.setScale(scale); + this.columns.add(column); + } + + public void addColumn(Class type,String name,String comment,boolean unique,boolean nullable, boolean insertable, + boolean updatable,String columnDefinition, int length,int precision,int scale){ + ColumnMeta column = new ColumnMeta(); + column.setPrimaryKey(false); + column.setType(type); + column.setName(name); + column.setComment(comment); + column.setUnique(unique); + column.setNullable(nullable); + column.setInsertable(insertable); + column.setUpdatable(updatable); + column.setColumnDefinition(columnDefinition); + column.setLength(length); + column.setPrecision(precision); + column.setScale(scale); + this.columns.add(column); + } + + public void addColumn(Class type,String name,String comment){ + this.addColumn(type,name,comment,false,false,false,false,null,255,0,0); + } + + public void addColumn(Class type,String name){ + this.addColumn(type,name,null,false,false,false,false,null,255,0,0); + } + + public void addPrimaryKeyColumn(Class type,String name,GenerationType strategy){ + this.addPrimaryKeyColumn(type,name,strategy,null,null,false,false,false,false,null,255,0,0); + } + + public void addPrimaryKeyColumn(Class type,String name,GenerationType strategy,String comment){ + this.addPrimaryKeyColumn(type,name,strategy,null,comment,false,false,false,false,null,255,0,0); + } + + public Class buildClass(){ + return DynamicEntityClassBuilder.buildDynamicEntity(this); + } + + @Setter + @Getter + public static class TableMeta{ + private String name; + private String catalog; + private String schema; + private String comment; + } + + @Setter + @Getter + public static class ColumnMeta{ + private Class type; + private String name; + private boolean isPrimaryKey; + private GeneratedValueMeta generatedValue; + private String comment; + private boolean unique; + private boolean nullable; + private boolean insertable; + private boolean updatable; + private String columnDefinition; + private int length; + private int precision; + private int scale; + } + + @Setter + @Getter + public static class GeneratedValueMeta{ + private GenerationType strategy; + private String generator; + } + +} diff --git a/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/dynamic/DynamicEntityBuilderTest.java b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/dynamic/DynamicEntityBuilderTest.java new file mode 100644 index 00000000..16aa4ded --- /dev/null +++ b/springboot-starter-data-fast/src/test/java/com/codingapi/springboot/fast/dynamic/DynamicEntityBuilderTest.java @@ -0,0 +1,62 @@ +package com.codingapi.springboot.fast.dynamic; + +import com.codingapi.springboot.fast.entity.Demo; +import com.codingapi.springboot.fast.metadata.EntityMetaData; +import jakarta.persistence.GenerationType; +import org.hibernate.dialect.H2Dialect; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class DynamicEntityBuilderTest { + + @Test + void generateTableDDL() { + DynamicEntityBuilder dynamicEntityBuilder = new DynamicEntityBuilder(H2Dialect.class,"jdbc:h2:file:./test.db"); + List exceptions = dynamicEntityBuilder.validatorTable(Demo.class); + System.out.println(exceptions); + + String createDDL = dynamicEntityBuilder.generateCreateTableDDL(Demo.class); + System.out.println("createDDL:\n" + createDDL); + assertNotNull(createDDL); + + String dropDDL = dynamicEntityBuilder.generateDropTableDDL(Demo.class); + System.out.println("dropDDL:\n" + dropDDL); + assertNotNull(dropDDL); + + String migrateDDL = dynamicEntityBuilder.generateMigratorTableDDL(Demo.class); + System.out.println("migrateDDL:\n" + migrateDDL); + assertNotNull(migrateDDL); + } + + @Test + void dynamicGenerateTableDDL() { + DynamicEntityBuilder dynamicEntityBuilder = new DynamicEntityBuilder(H2Dialect.class,"jdbc:h2:file:./test.db"); + + EntityMetaData entityMetaData = new EntityMetaData("com.codingapi.entity.Test"); + entityMetaData.setTable("test"); + entityMetaData.addPrimaryKeyColumn(Long.class,"id", GenerationType.IDENTITY,"主键"); + entityMetaData.addColumn(String.class,"name","姓名"); + + Class entityClass = entityMetaData.buildClass(); + + List exceptions = dynamicEntityBuilder.validatorTable(entityClass); + System.out.println(exceptions); + assertFalse(exceptions.isEmpty()); + + String createDDL = dynamicEntityBuilder.generateCreateTableDDL(entityClass); + System.out.println("createDDL:\n" + createDDL); + assertNotNull(createDDL); + + String dropDDL = dynamicEntityBuilder.generateDropTableDDL(entityClass); + System.out.println("dropDDL:\n" + dropDDL); + assertNotNull(dropDDL); + + String migrateDDL = dynamicEntityBuilder.generateMigratorTableDDL(entityClass); + System.out.println("migrateDDL:\n" + migrateDDL); + assertNotNull(migrateDDL); + + } +} \ No newline at end of file diff --git a/springboot-starter-flow/pom.xml b/springboot-starter-flow/pom.xml index 63c74096..4d13e158 100644 --- a/springboot-starter-flow/pom.xml +++ b/springboot-starter-flow/pom.xml @@ -6,7 +6,7 @@ springboot-parent com.codingapi.springboot - 3.4.23 + 3.4.24 springboot-starter-flow diff --git a/springboot-starter-security/pom.xml b/springboot-starter-security/pom.xml index d4598136..6dd3c797 100644 --- a/springboot-starter-security/pom.xml +++ b/springboot-starter-security/pom.xml @@ -6,7 +6,7 @@ springboot-parent com.codingapi.springboot - 3.4.23 + 3.4.24 springboot-starter-security diff --git a/springboot-starter/pom.xml b/springboot-starter/pom.xml index 78d0381d..d6b970d1 100644 --- a/springboot-starter/pom.xml +++ b/springboot-starter/pom.xml @@ -5,7 +5,7 @@ com.codingapi.springboot springboot-parent - 3.4.23 + 3.4.24 springboot-starter