LowCode 低代码建表工具
需求描述
- 将数据库的表映射为实体类,服务启动时,扫描表相关的实体类,根据实体类模型在数据库创建相关的表
依赖
- 主要依赖:使用
Sprintboot、druid、spring-jdbc、mybatis
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
<scope>compile</scope>
</dependency>
<!-- Mybatis 的依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
设计思路
- 使用
Springboot CommandLineRunner
机制 - 开发自定义数据库模型表注解,扫描所有的表注解
- 查询数据库库、表、字段等元数据信息
- 比对元数据和扫描到的表模型信息,确定新增和更新的表模型
- 执行新增和更新操作
代码工程
annotation
:自定义数据库模型表、字段、索引等注解init
:主要扫描表信息,比对元数据建表的代码constants
:常量类utils
:工具类
业务流程图
lowcode-database
业务流程图大致如下
类图
lowcode-database建表接口
- 调用此接口中的方法进行建表的操作
lowcode-database主要类图
-
建表管理器,主要负责获取不同数据库的元数据信息,针对不同的数据库进行建表、更新表的操作
-
总类图
-
放大部分图
工厂类
自定义表注解定义
Table.java
package com.lidong.lowcode.database.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.lidong.lowcode.database.constants.database.CharacterSetEnum;
import com.lidong.lowcode.database.constants.database.EngineEnum;
import com.lidong.lowcode.database.constants.enumconst.StrCaseEnum;
import com.lidong.lowcode.database.constants.database.TableOrderEnum;
/**
* 实体类表注解
*
* @author LiDong
* @version 1.0.0
* @createTime 9/7/2022 8:39 AM
*/
// This annotation is used to Class
@Target(ElementType.TYPE)
// When run is effective
@Retention(RetentionPolicy.RUNTIME)
// Java document annotation
@Documented
public @interface Table {
/**
* 表名
*
* @return String
*/
String name() default "";
/**
* 表名大小写
*
* @return StrCaseEnum
*/
StrCaseEnum tableNameCase() default StrCaseEnum.LOWER;
/**
* 引擎
*
* @return EngineEnum
*/
EngineEnum engine() default EngineEnum.INNODB;
/**
* 自增开始数值
*
* @return int
*/
int autoIncrementNum() default 0;
/**
* 字符集
*
* @return CharacterSetEnum
*/
CharacterSetEnum characterSet() default CharacterSetEnum.UTF_8;
/**
* 表排序类型
*
* @return TableOrderEnum
*/
TableOrderEnum orderType() default TableOrderEnum.UTF8_GENERAL_CI;
/**
* 表备注
*
* @return String
*/
String comment() default "";
}
扫描自定义注解标志的类
Spring ResourceLoader
为我们通过资源路径getResource()
获取外部资源提供了统一的方法- 要获取
ResourceLoader
的引用,需要实现ResourceLoaderAware
接口。
public class LowCodeDataBaseResourceLoader implements ResourceLoaderAware {
private static final Logger logger = LoggerFactory.getLogger(LowCodeDataBaseResourceLoader.class);
public static final String PATTERN_CLASS = "/**/*.class";
/**
* 需要扫描的
*/
private final List<TypeFilter> includeTypeFilterList = new LinkedList<>();
/**
* 不需要扫描的
*/
private final List<TypeFilter> excludeTypeFilterList = new LinkedList<>();
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
}
/**
* 获取包下面注解标注的类
*
* @param basePackages 包路径 com.lidong.lowcode
* @param annotations 需要扫描的注解
* @return List
*/
public static <T> List<Class<T>> getAllClassByAnnotation(String[] basePackages, Class<? extends Annotation>... annotations) {
LowCodeDataBaseResourceLoader lowCodeDataBaseResourceLoader = new LowCodeDataBaseResourceLoader();
if (!ObjectUtils.isEmpty(annotations)) {
for (Class<? extends Annotation> anooationClass : annotations) {
lowCodeDataBaseResourceLoader.addIncludeFilter(new AnnotationTypeFilter(anooationClass));
}
}
List<Class<T>> tableClassList = new ArrayList<>();
for (String pack : basePackages) {
tableClassList.addAll(lowCodeDataBaseResourceLoader.scanPackageGetClassList(pack));
}
return tableClassList;
}
/**
* 扫描包获取类
*
* @param basePackage 包路径
* @param <T> T
* @return List
*/
public <T> List<Class<T>> scanPackageGetClassList(String basePackage) {
List<Class<T>> classes = new ArrayList<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + org.springframework.util.ClassUtils
.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)) + PATTERN_CLASS;
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
if (resource.isReadable()) {
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
if (matchTypeFilter(metadataReader)) {
try {
ClassMetadata classMetadata = metadataReader.getClassMetadata();
Class<?> aClass = Class.forName(classMetadata.getClassName());
classes.add((Class<T>) aClass);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
throw new InitDataBaseException(e.getMessage(), e);
}
return classes;
}
/**
* 添加需要扫描的规则
*
* @param includeFilter includeFilter
*/
private void addIncludeFilter(TypeFilter includeFilter) {
includeTypeFilterList.add(includeFilter);
}
/**
* 匹配过滤规则
*
* @param metadataReader metadataReader
* @return boolean
* @throws IOException exception
*/
private boolean matchTypeFilter(MetadataReader metadataReader) throws IOException {
for (TypeFilter typeFilter : excludeTypeFilterList) {
if (typeFilter.match(metadataReader, metadataReaderFactory)) {
return false;
}
}
for (TypeFilter typeFilter : includeTypeFilterList) {
if (typeFilter.match(metadataReader, metadataReaderFactory)) {
return true;
}
}
return false;
}
}
InitDataBaseStarter建表启动器
/**
* 数据库自动配置启动类
*
* @author LiDong
* @version 1.0.0
* @createTime 9/9/2022 8:15 PM
*/
@Component
@Order(Integer.MIN_VALUE)
public class InitDataBaseStarter implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(InitDataBaseStarter.class);
/**
* 数据库自动配置建表入口
*
* @param args args
* @throws Exception exception
*/
@Override
public final void run(String... args) throws Exception {
logger.info("lowcode-database 建表开始...");
// 创建数据库建表记录表
InitDataBase initDataBase = LowcodeDbSpringContextUtils.getBean(InitDataBase.class);
// 创建数据库建表固化表
initDataBase.createCuringTable();
List<DataBaseTable> dataBaseTableList = initDataBase.scanAnnotationGetTableInfo();
logger.info("lowcode-database 共扫描到表 {} 个", dataBaseTableList.size());
initDataBase.start(dataBaseTableList);
logger.info("lowcode-database 建表结束...");
}
}
项目地址
- https://gitee.com/Marlon_Brando/lowcode_back/tree/develop/lowcode/lowcode-database