示例项目地址: https://git.code.tencent.com/xinzhang0618/code-generator.git

实现思路

  1. 使用velocity, 先制作模板, 然后填充数据, 生成文件即可
  2. 表结构信息在information_schema.TABLES, information_schema.COLUMNS两个表中, 查询到表结构信息后, 构建成模板所需的数据即可
  3. 要注意解决一些特殊场景, 比如枚举的处理, 实体添加自定义字段, 布尔类型的is方法, 有无模块定义(有模块目录结构会不同)等
  4. 难点在于如何灵活的将配置分离

解析

CodeGenerator

  1. package top.xinzhang0618.code.generator;
  2. import com.alibaba.fastjson.JSON;
  3. import org.apache.velocity.Template;
  4. import org.apache.velocity.VelocityContext;
  5. import org.apache.velocity.app.Velocity;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.stereotype.Component;
  9. import org.springframework.util.CollectionUtils;
  10. import top.xinzhang0618.code.generator.config.Configuration;
  11. import top.xinzhang0618.code.generator.config.Profile;
  12. import top.xinzhang0618.code.generator.schema.domain.Bean;
  13. import top.xinzhang0618.code.generator.schema.domain.Field;
  14. import top.xinzhang0618.code.generator.service.SchemaService;
  15. import top.xinzhang0618.code.generator.util.FileUtils;
  16. import top.xinzhang0618.code.generator.util.StringUtils;
  17. import java.io.StringWriter;
  18. import java.time.LocalDate;
  19. import java.util.*;
  20. /**
  21. * @author xinzhang
  22. * @date 2020/11/6 9:59
  23. */
  24. @Component
  25. public class CodeGenerator {
  26. @Autowired
  27. private SchemaService schemaService;
  28. @Value("${spring.profiles.active}")
  29. private String activeProfile;
  30. public void run() {
  31. Configuration configuration = JSON.parseObject(FileUtils.read("json/configuration.json"), Configuration.class);
  32. Profile profile = JSON.parseObject(FileUtils.read("json/" + activeProfile + ".json"), Profile.class);
  33. // 初始化流程引擎
  34. Properties prop = new Properties();
  35. prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
  36. Velocity.init(prop);
  37. //封装模板数据
  38. Map<String, Object> context = new HashMap<>(6);
  39. context.put("basePackage", profile.getBasePackage());
  40. context.put("date", LocalDate.now());
  41. Map<String, List<String>> moduleMap = profile.getModuleMap();
  42. String outPutDir =
  43. System.getProperty("user.dir") + "/target/" + profile.getBasePackage().replace(".", "/") + "/";
  44. moduleMap.forEach((module, tables) -> {
  45. List<Bean> beans = schemaService.listBeans(profile.getDataSources(), tables);
  46. context.put("module", module);
  47. beans.forEach(bean -> {
  48. processBean(configuration, profile, bean);
  49. addExtendImport(bean, context);
  50. context.put("bean", bean);
  51. configuration.getTemplates().forEach(template -> {
  52. StringWriter sw = new StringWriter();
  53. Template tpl = Velocity.getTemplate(template, "UTF-8");
  54. String fileName = buildFileName(outPutDir, module, template, bean.getBeanName());
  55. tpl.merge(new VelocityContext(context), sw);
  56. FileUtils.writeFile(fileName, sw.toString());
  57. });
  58. });
  59. });
  60. }
  61. private void addExtendImport(Bean bean, Map<String, Object> context) {
  62. if (!CollectionUtils.isEmpty(bean.getExtendFields())) {
  63. List<String> extendImports = new ArrayList<>();
  64. boolean needImportList = bean.getExtendFields().stream().anyMatch(field -> field.getJavaType().startsWith(
  65. "List"));
  66. if (needImportList) {
  67. extendImports.add("java.util.List");
  68. }
  69. context.put("extendImports", extendImports);
  70. }
  71. }
  72. private String buildFileName(String outPutDir, String module, String template, String beanName) {
  73. if (template.contains("domain")) {
  74. return outPutDir + "domain/" + module + "/" + beanName + ".java";
  75. } else if (template.contains("mapper.java")) {
  76. return outPutDir + "mapper/" + module + "/" + beanName + "Mapper.java";
  77. } else if (template.contains("mapper.xml")) {
  78. return outPutDir + "mapper/xml/" + module + "/" + beanName + "Mapper.xml";
  79. } else if (template.contains("service.java")) {
  80. return outPutDir + "service/" + module + "/" + beanName + "Service.java";
  81. } else {
  82. return outPutDir + "service/impl/" + module + "/" + beanName + "ServiceImpl.java";
  83. }
  84. }
  85. public void processBean(Configuration configuration, Profile profile, Bean bean) {
  86. bean.setBeanName(parseBeanName(bean.getTableName(), profile.getTablePrefix()));
  87. bean.getFields().forEach(field -> {
  88. field.setFieldName(parseFieldName(field.getColumnName()));
  89. field.setJavaType(configuration.getTypeMap().getOrDefault(field.getJdbcType(), "String"));
  90. if (field.getColumnName().startsWith("is")) {
  91. field.setGetName("is" + StringUtils.upperFirst(field.getFieldName()));
  92. } else {
  93. field.setGetName("get" + StringUtils.upperFirst(field.getFieldName()));
  94. }
  95. field.setSetName("set" + StringUtils.upperFirst(field.getFieldName()));
  96. });
  97. if (!CollectionUtils.isEmpty(profile.getExtendMap()) && profile.getExtendMap().get(bean.getTableName()) != null) {
  98. Profile.ExtendInfo extendInfo = profile.getExtendMap().get(bean.getTableName());
  99. // 替换枚举类型
  100. if (!CollectionUtils.isEmpty(extendInfo.getEnumMap())) {
  101. Map<String, String> enumMap = extendInfo.getEnumMap();
  102. bean.getFields().forEach(field -> field.setJavaType(enumMap.get(field.getColumnName()) == null ?
  103. field.getJavaType() : enumMap.get(field.getColumnName())));
  104. bean.setEnumJavaTypes(enumMap.values());
  105. }
  106. // 添加字段
  107. bean.setExtendFields(extendInfo.getExtendFields());
  108. }
  109. }
  110. private String parseBeanName(String tableName, String tablePrefix) {
  111. String[] words = tableName.replace(tablePrefix, "").split("_");
  112. StringBuilder sb = new StringBuilder();
  113. Arrays.stream(words).forEach(w -> sb.append(StringUtils.upperFirst(w)));
  114. return sb.toString();
  115. }
  116. private static String parseFieldName(String columnName) {
  117. String[] words = columnName.split("_");
  118. ArrayList<String> list = new ArrayList<>(Arrays.asList(words));
  119. list.remove("is");
  120. StringBuilder sb = new StringBuilder(list.get(0));
  121. for (int i = 1; i < list.size(); i++) {
  122. sb.append(StringUtils.upperFirst(list.get(i)));
  123. }
  124. return sb.toString();
  125. }
  126. }

代码如上, 核心的步骤:

  • 初始化流程引擎, 获取全局配置等
  • 封装模板数据
    • 包目录, 作者信息
    • 模块
      • 表—>实体
        • 构建实体名
        • 构建字段名称, getter/setter
        • 枚举处理
        • 添加额外字段
        • 添加额外导入
  • 从全局配置中拿到模板, 构建生成路径, 并生成文件

查询sql:

  1. <!-- 通用查询映射结果 -->
  2. <resultMap id="beanResultMap" type="top.xinzhang0618.code.generator.schema.domain.Bean">
  3. <result column="table_name" property="tableName"/>
  4. <result column="table_comment" property="comment"/>
  5. <collection property="fields" ofType="top.xinzhang0618.code.generator.schema.domain.Field">
  6. <result column="column_name" property="columnName"/>
  7. <result column="data_type" property="jdbcType"/>
  8. <result column="column_comment" property="comment"/>
  9. <result column="pk" property="pk"/>
  10. </collection>
  11. </resultMap>
  12. <select id="listBeans" resultMap="beanResultMap">
  13. SELECT
  14. t.table_name,
  15. t.table_comment,
  16. c.column_name,
  17. c.data_type,
  18. c.column_comment,
  19. IF
  20. ( c.column_key = 'PRI', TRUE, FALSE ) as pk
  21. FROM
  22. information_schema.TABLES t
  23. LEFT JOIN information_schema.COLUMNS c ON t.table_name = c.table_name
  24. WHERE
  25. t.table_schema in
  26. <foreach item="item" index="index" collection="databases" open="(" separator="," close=")">
  27. #{item}
  28. </foreach>
  29. and t.table_name in
  30. <foreach item="item" index="index" collection="tableNames" open="(" separator="," close=")">
  31. #{item}
  32. </foreach>
  33. ORDER BY
  34. c.ordinal_position
  35. </select>

配置分离

为了方便配置的编写, 此处使用了json作为补充的配置文件(项目使用springboot, 其yml文件仅有数据库连接信息)
1.通用配置比如模板位置以及字段数据库类型到java数据类型的对应关系放到全局配置文件configuration.json中

  1. {
  2. "templates": [
  3. "templates/domain.java.vm",
  4. "templates/mapper.java.vm",
  5. "templates/mapper.xml.vm",
  6. "templates/service.java.vm",
  7. "templates/serviceImpl.java.vm"
  8. ],
  9. "typeMap": {
  10. "unique identifier": "String",
  11. "bit": "Boolean",
  12. "date": "LocalDate",
  13. "timestamp": "LocalDateTime",
  14. "datetime": "LocalDateTime",
  15. "varchar": "String",
  16. "nvarchar": "String",
  17. "mediumtext": "String",
  18. "char": "String",
  19. "bigint": "Long",
  20. "bigint unsigned": "Long",
  21. "int": "Integer",
  22. "int unsigned": "Integer",
  23. "double": "Double",
  24. "double unsigned": "Double",
  25. "decimal": "Double",
  26. "decimal unsigned": "Double",
  27. "tinyint": "Boolean",
  28. "tinyint unsigned": "Integer",
  29. "time": "LocalTime",
  30. "smallint": "Integer",
  31. "smallint unsigned": "Integer"
  32. }
  33. }

2.针对每个项目的配置单独建立该项目的配置文件, 如下demo.json

  1. {
  2. "basePackage": "top.xinzhang0618.oa",
  3. "dataSources": [
  4. "oa_admin_dev",
  5. "oa_biz_dev"
  6. ],
  7. "tablePrefix": "oa_",
  8. "moduleMap": {
  9. "base": [
  10. "oa_company",
  11. "oa_data_dict",
  12. "oa_department",
  13. "oa_menu",
  14. "oa_message",
  15. "oa_privilege",
  16. "oa_role",
  17. "oa_user",
  18. "oa_user_role"
  19. ],
  20. "test": [
  21. "oa_user"
  22. ]
  23. },
  24. "extendMap": {
  25. "oa_data_dict": {
  26. "enumMap": {
  27. "data_dict_type": "DataDictType"
  28. },
  29. "extendFields": [
  30. {
  31. "fieldName": "testField",
  32. "javaType": "String",
  33. "comment": "测试添加额外字段",
  34. "setName": "setTestField",
  35. "getName": "getTestField"
  36. }
  37. ]
  38. }
  39. }
  40. }