前言

在实际的项目开发中,根据数据库表创建实体、service、controller等结构是一件非常繁琐的事。所以我们经常需要使用到各种代码生成器,例如mybatis-plus,若依等框架都有自己的代码生成器和生成逻辑。本篇文章我们就从0开始,手写一个简单的代码生成器。 源码github地址

项目依赖

本项目是基于springboot+javapoet+dom4j实现的。

    <dependencies><!-- DOM4J依赖 --><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!-- https://mvnrepository.com/artifact/com.squareup/javapoet --><dependency><groupId>com.squareup</groupId><artifactId>javapoet</artifactId><version>1.13.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency><dependency><groupId>io.swagger.core.v3</groupId><artifactId>swagger-annotations</artifactId><version>2.2.0</version></dependency><!-- jdbc --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId><version>2.1.7.RELEASE</version></dependency><!-- jdbc --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.6.8</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.0</version></dependency></dependencies>

整体思路

代码生成的逻辑主要是基于以下两个sql语句实现的:

show tables //查询所有数据库表
SHOW FULL COLUMNS FROM User" // 查询指定表中的所有字段信息

查询出来的表字段信息如下图所示:

我们遍历这些表,根据每个表生成对应的实体类,再根据这些表的字段信息生成不同的属性和变量。整体架构图如下:

核心类

在主程序使用代码生成器的时候将Datasource传入给Generate 类,Generate 对象根据数据源使用jdbcTemplate查询数据库表信息。并根据配置设置各项需求(我没有写,例如是否使用lombok、controllerPath等设置,读者可以自己尝试完成)

/*** 生成器最核心的类,保存了实体类、service、mapper的输出路径以及数据库的链接信息* @author 黎勇炫* @date 2022年08月19日 11:17*/
@Data
@Accessors(chain = true)
public class Generate {private static final String SHOWTABLES = "show tables";/** jdbc */private JdbcTemplate jdbcTemplate;/** 实体类 */private String domainPath;/** service */private String servicePath;/** dao */private String daoPath;/** controller */private String controllerPath;/** 是否使用lombok */private boolean lombok;/** 所有表名 */private List<String> table;/** 表前缀 */private String tablePrefix;/*** 包路径(entity,controller,service包的父包)*/private String pkgRootPath;/*** 资源路径*/private String sourcePath;private BuilderChain chain;public Generate(DataSource dataSource,String pkgRootPath,String sourcePath) {this.pkgRootPath = pkgRootPath;this.sourcePath = sourcePath;jdbcTemplate = new JdbcTemplate(dataSource);}/*** 开始生成代码*/public void doCreate(){// 1.在数据库中查询所有的表table = jdbcTemplate.queryForList(SHOWTABLES,String.class);// 处理表名table = table.stream().map(item -> {String entityName = buildEntityName(item);// 从新建实体类开始chain = createChain(jdbcTemplate,entityName,item,pkgRootPath,sourcePath);chain.build(jdbcTemplate, entityName, item, pkgRootPath, sourcePath);return entityName;}).collect(Collectors.toList());}/*** 创建生成类的调用链 实体类-dao-service-controller*/private BuilderChain createChain(JdbcTemplate jdbcTemplate, String entityName, String item, String pkgRootPath, String sourcePath) {EntityBuilder chain = new EntityBuilder();chain.appendNext(new DaoBuilder()).appendNext(new ServiceBuilder()).appendNext(new ControllerBuilder());return chain;}/*** 构建实体类名称*/private String buildEntityName(String name) {// 替换前缀if (!StringUtils.isEmpty(tablePrefix)) {name = name.replaceFirst(tablePrefix,"");}// 首字母大写// 驼峰命名String[] hump = name.split("_");StringBuilder builder = new StringBuilder();for (String s : hump) {builder.append(s.substring(0,1).toUpperCase()+s.substring(1));}return builder.toString();}}

类构建者

因为实体类、Dao、Service、Controller之间有调用关系,例如Dao需要实体类作为泛型,Service又需要注入Dao层的类。所以我们需要保证整个创建者链的调用顺序,使用责任链模式实现。

/*** @author 黎勇炫* @date 2022年08月22日 16:03*/
public class EntityBuilder extends BuilderChain{/****/public static final String ENTITY_PKG_SUFFID = ".domain";@Overridepublic BuilderChain appendNext(BuilderChain next){this.next = next;return next;}@Overridepublic void build(JdbcTemplate jdbcTemplate, String entityName, String tableName, String pkgRootPath, String sourcePath) {// 查询表中所有的字段List<Columns> columns = jdbcTemplate.query("SHOW FULL COLUMNS FROM " + tableName, new BeanPropertyRowMapper<Columns>(Columns.class));// 开始创建实体类TypeSpec.Builder builder = TypeSpec.classBuilder(entityName)// 关联表.addAnnotation(AnnotationSpec.builder(TableName.class).addMember("value","\""+tableName+"\"").build())// lombok注解.addAnnotation(AnnotationSpec.builder(Data.class).build())// 修饰符.addModifiers(Modifier.PUBLIC).addJavadoc(entityName+"\n@author 黎勇炫 \n@Date "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));for (Columns column : columns) {System.out.println(column);FieldSpec.Builder fieldBuilder = FieldSpec.builder((TypeName) FieldRel.getJavaType(column.getType()), buildFieldName(column.getField()), Modifier.PRIVATE).addAnnotation(AnnotationSpec.builder(Schema.class).addMember("title", "\""+column.getComment()+"\"").build());// 如果是主键就加上@TableIdif(!StringUtils.isEmpty(column.getKey())){fieldBuilder.addAnnotation(AnnotationSpec.builder(TableId.class).build());}builder.addField(fieldBuilder.build());}genJavaSourceFile(pkgRootPath+ENTITY_PKG_SUFFID,sourcePath,builder);// 创建实体类if(null != next){next.build(jdbcTemplate,entityName,tableName,pkgRootPath,sourcePath);}}/*** 转换变量名称-小写/驼峰*/private String buildFieldName(String field) {field = field.toLowerCase();String[] segment = field.split("_");StringBuilder str = new StringBuilder();for (int i = 0; i < segment.length; i++) {if(i==0){str.append(segment[i]);}else {str.append(segment[i].substring(0,1).toUpperCase()+segment[i].substring(1));}}return str.toString();}
}
/*** @author 黎勇炫* @date 2022年08月23日 11:25*/
public class DaoBuilder extends BuilderChain{/*** edit*/public static final String DAO_SUFFIX = "Dao";/*** edit*/public static final String DAO_PKG_SUFFIX = ".dao";@Overridepublic void build(JdbcTemplate jdbcTemplate, String entityName, String tableName, String pkgRootPath, String sourcePath) {Class<BaseMapper> clazz = BaseMapper.class;// 创建接口TypeSpec.Builder builder = TypeSpec.interfaceBuilder(entityName + DAO_SUFFIX).addAnnotation(Mapper.class).addModifiers(Modifier.PUBLIC).addSuperinterface(ParameterizedTypeName.get(ClassName.get(clazz), ClassName.get(pkgRootPath + EntityBuilder.ENTITY_PKG_SUFFID, entityName))).addJavadoc(entityName+"\n@author 黎勇炫 \n@Date "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));genJavaSourceFile(pkgRootPath+DAO_PKG_SUFFIX,sourcePath,builder);// 生成mapper文件createMapperFile(pkgRootPath+DAO_PKG_SUFFIX+"."+entityName,entityName+"Dao");if(null != this.next){this.next.build(jdbcTemplate,entityName,tableName,pkgRootPath,sourcePath);}}
}
/*** @author 黎勇炫* @date 2022年08月23日 15:37*/
public class ServiceBuilder extends BuilderChain{/*** service后缀*/public static final String SERVICE_SUFFIX = "Service";/*** serviceimpl后缀*/public static final String SERVICEIMPL_SUFFIX = "ServiceImpl";/*** service包后缀*/public static final String SERVICE_PKG_SUFFIX = ".service";/*** serviceimpl包后缀*/public static final String SERVICEIMPL_PKG_SUFFIX = ".service.impl";@Overridepublic void build(JdbcTemplate jdbcTemplate, String entityName, String tableName, String pkgRootPath, String sourcePath) {Class<IService> service = IService.class;Class<ServiceImpl> serviceImpl = ServiceImpl.class;// 创建intefaceTypeSpec.Builder builder = TypeSpec.interfaceBuilder(entityName + SERVICE_SUFFIX)// 继承iservice接口.addSuperinterface(ParameterizedTypeName.get(ClassName.get(service), ClassName.get(pkgRootPath+EntityBuilder.ENTITY_PKG_SUFFID,entityName))).addModifiers(Modifier.PUBLIC).addJavadoc(entityName+"\n@author 黎勇炫 \n@Date "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 生成service接口genJavaSourceFile(pkgRootPath+SERVICE_PKG_SUFFIX, sourcePath, builder);// 加载刚才生成的service接口,在生成实现类的时候继承这个接口// 创建implTypeSpec.Builder impl = null;impl = TypeSpec.classBuilder(entityName + SERVICEIMPL_SUFFIX).superclass(ParameterizedTypeName.get(ClassName.get(serviceImpl), ClassName.get(pkgRootPath+DaoBuilder.DAO_PKG_SUFFIX,entityName+DaoBuilder.DAO_SUFFIX), ClassName.get(pkgRootPath+EntityBuilder.ENTITY_PKG_SUFFID,entityName))).addSuperinterface(ClassName.get(pkgRootPath+SERVICE_PKG_SUFFIX,entityName+SERVICE_SUFFIX)).addModifiers(Modifier.PUBLIC).addAnnotation(Service.class).addJavadoc(entityName+"\n@author 黎勇炫 \n@Date "+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 生成实现类genJavaSourceFile(pkgRootPath+SERVICEIMPL_PKG_SUFFIX + ".impl", sourcePath, impl);// todo 生成mapper文件if(null != this.next){this.next.build(jdbcTemplate,entityName,tableName,pkgRootPath,sourcePath);}}
}
/*** 生成controller* @author 黎勇炫* @date 2022年08月24日 13:51*/
public class ControllerBuilder extends BuilderChain{public static final String CONTROLLER_SUFFIX = "Controller";public static final String CONTROLLER_OKG_SUFFIX = ".controller";@Overridepublic void build(JdbcTemplate jdbcTemplate, String entityName, String item, String pkgRootPath, String sourcePath) {/*** 1.先创建controller类,添加相关注解和权限标识* 2.创建属性,注入service类* 3.创建api接口*/TypeSpec.Builder builder = TypeSpec.classBuilder(entityName + CONTROLLER_SUFFIX)// 添加@RestController注解.addAnnotation(AnnotationSpec.builder(RestController.class).build())// 添加RequestMapping注解.addAnnotation(AnnotationSpec.builder(RequestMapping.class).addMember("value", "\"/" + entityName.toLowerCase()+"\"").build())// 导入实体类// 权限修饰符.addModifiers(Modifier.PUBLIC);// 添加Service注入builder.addField(FieldSpec.builder(ClassName.get(pkgRootPath+ServiceBuilder.SERVICE_PKG_SUFFIX,entityName+ServiceBuilder.SERVICE_SUFFIX),entityName.substring(0,1).toLowerCase()+entityName.substring(1)+ServiceBuilder.SERVICE_SUFFIX).addModifiers(Modifier.PRIVATE).addAnnotation(AnnotationSpec.builder(Autowired.class).build()).build());genJavaSourceFile(pkgRootPath+CONTROLLER_OKG_SUFFIX,sourcePath,builder);}
}

测试代码生成器

到这里,实体类、Dao层、Service层以及Controller层的类基本都已经创建了。在需要代码生成的项目中导入代码生成器,在测试类中调用

    @Testvoid contextLoads() {DataSource dataSource = context.getBean(DataSource.class);Generate generate  = new Generate(dataSource,"gencode.demo","src/main/java");generate.setTablePrefix("l_").setDaoPath("ss");generate.doCreate();}

测试前项目结构:

测试后项目结构:

简单的代码生成器已经完成了,还有很多功能因为我太懒了没开发,感兴趣的可以自己可以尝试拓展。这个代码生成器当然是不够完善的,主要是尝试理解代码生成器的设计思路。

从0-1带你手写代码生成器(Java版)相关推荐

  1. BP神经网络实现手写数字识别Python实现,带GUI手写画板

    BP神经网络实现手写数字识别 BP神经网络模型 用tkinter编写用于手写输入的画板 程序运行的效果截图 在B站看了一个机器学习基础的视频( 链接)后,发现到资料里面有一个用BP神经网络对手写数字进 ...

  2. 【线程池】自行准备linux环境,带你手写线程池,只需仅仅150行代码|内存池|API|连接池|应用协议丨C/C++Linux服务器开发

    [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码 视频讲解如下,点击观看: [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码|内存池|API|连接池|应用协 ...

  3. 【线程池】自行准备linux环境,带你手写线程池,只需仅仅150行代码

    [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码 视频讲解如下,点击观看: [线程池]自行准备linux环境,带你手写线程池,只需仅仅150行代码|内存池|API|连接池|应用协 ...

  4. 手写 springIoc 注解版 ,实现@Service (beng),@Resource (依赖注入)

    手写springIoc 注解版 代码demo https://pan.baidu.com/s/1jyvLMDrg_bfpKmhtrTTZSQ 提取码:5ju1 代码目录结构 1.pom.xml < ...

  5. 纯手写SpringFramework-完结版(原创)

    个人简介 作者是一个来自河源的大三在校生,以下笔记都是作者自学之路的一些浅薄经验,如有错误请指正,将来会不断的完善笔记,帮助更多的Java爱好者入门. 文章目录 个人简介 纯手写SpringFrame ...

  6. 【c++】手写笔记扫描版

    c++手写笔记扫描版 c++基础 类与对象 数据的共享与保护 数组.指针与字符串 进阶与派生 多态 模板与群体数据 泛型函数设计与c++标准模板 流类库与输入输出 异常处理 3月份境外确诊病例数增多 ...

  7. 【转载】Windows下Tesseract4.0识别与中文手写字体训练

    一 . tesseract 4.0 安装及使用 1. tesseract 4.0 安装 安装包下载地址: http://digi.bib.uni-mannheim.de/tesseract/tesse ...

  8. Windows下Tesseract4.0识别与中文手写字体训练

    一 . tesseract 4.0 安装及使用 1. tesseract 4.0 安装 安装包下载地址: http://digi.bib.uni-mannheim.de/tesseract/tesse ...

  9. 安卓pdf阅读器_安卓6.0+2GB+32GB支持手写!BOOX Nova Pro电子书阅读器隆重上市

    近两年,给阅读器配备手写笔逐渐成为了电纸书行业的一种潮流.2月23日,文石隆重推出了一款全新的7.8寸电子书阅读器BOOX Nova Pro. 这款产品最特别的地方在于,它首次给7.8寸的小屏阅读器配 ...

最新文章

  1. SharePoint Server 2007 页面模型
  2. oracle删除日志文件
  3. 路由器 jffs分区 简介
  4. 大话oraclerac集群、高可用性、备份与恢复_Oracle RAC结构
  5. 协同工作php,PHPOA:灵活、高效、协同,让企业高效运转
  6. 【华为云技术分享】玩转云上数据湖,解析Serverless 技术落地
  7. 在 Chrome 浏览器中安装印象笔记·剪藏插件
  8. C++有序map和无序unordered_map性能测试对比
  9. CSS入门二、美化页面元素
  10. 洛谷3238 HNOI2014 道路阻塞 最短路 线段树(无代码)
  11. java mysql 多表查询_Java编程基础32——MySQL多表联查
  12. 实验1 理想介质中的均匀平面波传播
  13. 1exe1.net - 1个exe文件搞定1切
  14. 游戏编程之十 图像引擎DirectXDraw
  15. “无任何网络提供程序接受指定的网络路径”的解决办法
  16. 局域网内环境搭建-PC篇
  17. 【APP测试】Windows下夜神模拟器的Drozer安装与连接
  18. 搭建有效的供应链管理系统软件,能增强企业的核心竞争力
  19. 使用SR替代LDP,配置ospf sham-link
  20. VUE非父子组件之间通信的几种方式

热门文章

  1. makefile中.PHONY的最直接理解
  2. vuex 中的mutations
  3. JAVA高并发测试方法
  4. c语言基础练习题及答案,C语言基础练习题(含答案)
  5. 帮你实现Type-c手机同时充电和数据传输(OTG)功能的方案
  6. 20170913 nwjs的bg-script的坑
  7. Linux 之 ATT汇编语言 mov、add、sub指令、数据段
  8. Less:less的使用方法及如何使less文件
  9. Linux 关闭未知显示器,系统黑屏,不能进入图形界面的办法
  10. MyReport报表系统(二)