• MyBatis-plus+注解形式实现项目与数据库绑定动态更新


    前言:在自己电脑上使用mybatis-plus生成映射数据库表的实体类,将项目上传到服务器启动时根据实体类自动创建、更新相应的数据库表,这样做的目的是使得在自己电脑上创建数据库表后不用再到服务器去创建

     实现思路:

    1. 使用mybatis-plus生成实体类
    2. 在生成实体类的时候获取类字段信息并通过注解的形式写入到实体类里面去
    3. 在服务器启动项目的时候获取项目的所有实体类与数据库所有表进行对比,解析实体类上注解信息,写入特定的类用于将数据库没有对应的表进行创建

    详细步骤

    1.配置mybatis-plus自动生成代码

       依赖mybatis-plus   

    1. <dependency>
    2. <groupId>com.baomidougroupId>
    3. <artifactId>mybatis-plusartifactId>
    4. <version>2.3version>
    5. dependency>
    6. <dependency>
    7. <groupId>com.baomidougroupId>
    8. <artifactId>mybatisplus-spring-boot-starterartifactId>
    9. <version>1.0.5version>
    10. dependency>
    11. <dependency>
    12. <groupId>org.freemarkergroupId>
    13. <artifactId>freemarkerartifactId>
    14. dependency>
    15. <dependency>
    16. <groupId>org.apache.velocitygroupId>
    17. <artifactId>velocity-engine-coreartifactId>
    18. <version>2.0version>
    19. dependency>

     配置

    1. mybatis-plus:
    2. mapper-locations: classpath*:/mapper/*/*Mapper.xml
    3. global-config:
    4. id-type: 3
    5. field-strategy: 2
    6. db-column-underline: true
    7. refresh-mapper: true
    8. configuration:
    9. map-underscore-to-camel-case: true
    10. cache-enabled: true
    11. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

     代码生成器

    MpGenerator.java

    1. import com.baomidou.mybatisplus.enums.FieldFill;
    2. import com.baomidou.mybatisplus.enums.IdType;
    3. import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
    4. import com.baomidou.mybatisplus.generator.AutoGenerator;
    5. import com.baomidou.mybatisplus.generator.InjectionConfig;
    6. import com.baomidou.mybatisplus.generator.config.*;
    7. import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
    8. import com.baomidou.mybatisplus.generator.config.po.TableField;
    9. import com.baomidou.mybatisplus.generator.config.po.TableFill;
    10. import com.baomidou.mybatisplus.generator.config.po.TableInfo;
    11. import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
    12. import com.baomidou.mybatisplus.generator.config.rules.DbType;
    13. import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    14. import com.baomidou.mybatisplus.toolkit.StringUtils;
    15. import java.sql.Connection;
    16. import java.sql.PreparedStatement;
    17. import java.sql.ResultSetMetaData;
    18. import java.sql.SQLException;
    19. import java.util.*;
    20. public class MpGenerator {
    21. /**
    22. *

    23. * 读取控制台内容
    24. *

    25. */
    26. public String scanner(String tip) {
    27. Scanner scanner = new Scanner(System.in);
    28. StringBuilder help = new StringBuilder();
    29. help.append("请输入" + tip + ":");
    30. System.out.println(help.toString());
    31. if (scanner.hasNext()) {
    32. String ipt = scanner.next();
    33. if (StringUtils.isNotEmpty(ipt)) {
    34. return ipt;
    35. }
    36. }
    37. throw new MybatisPlusException("请输入正确的" + tip + "!");
    38. }
    39. private int type;
    40. public PackageConfig getPackageConfig() {
    41. PackageConfig pc = new PackageConfig();
    42. if (type == 3) {
    43. pc.setParent("com.wdz.tofs");
    44. } else {
    45. pc.setParent("com.wdz");
    46. }
    47. pc.setModuleName(scanner("模块名"));
    48. pc.setEntity("entity");
    49. pc.setController(null);
    50. return pc;
    51. }
    52. /**
    53. * 获取表字段信息
    54. */
    55. public ResultSetMetaData getMetaData(DataSourceConfig dsc,String tableName){
    56. Connection connection = dsc.getConn();
    57. try {
    58. try (PreparedStatement preparedStatement = connection.prepareStatement("select * from " + tableName)) {
    59. return preparedStatement.executeQuery().getMetaData();
    60. }
    61. } catch (SQLException e) {
    62. e.printStackTrace();
    63. return null;
    64. }
    65. }
    66. /**
    67. *

    68. * MySQL 生成演示
    69. *

    70. */
    71. public static void main(String[] args) {
    72. AutoGenerator mpg = new AutoGenerator();
    73. MpGenerator mpGenerator = new MpGenerator();
    74. // 选择 freemarker 引擎,默认 Veloctiy
    75. //mpg.setTemplateEngine(new FreemarkerTemplateEngine());
    76. // 全局配置
    77. GlobalConfig gc = new GlobalConfig();
    78. gc.setOpen(false);//完成后不打开文件夹
    79. gc.setAuthor("autor");
    80. String oPath = System.getProperty("user.dir");//得到当前项目的路径
    81. gc.setOutputDir(oPath + "/core_database/src/main/java/"); //生成文件输出根目录
    82. gc.setFileOverride(false);// 是否覆盖同名文件,默认是false
    83. gc.setActiveRecord(true);// 不需要ActiveRecord特性的请改为false
    84. gc.setEnableCache(false);// XML 二级缓存
    85. gc.setBaseResultMap(true);// XML ResultMap
    86. gc.setBaseColumnList(false);// XML columList
    87. gc.setIdType(IdType.UUID);
    88. mpg.setGlobalConfig(gc);
    89. // 数据源配置
    90. DataSourceConfig dsc = new DataSourceConfig();
    91. dsc.setDbType(DbType.MYSQL);
    92. dsc.setTypeConvert(new MySqlTypeConvert() {
    93. // 自定义数据库表字段类型转换【可选】
    94. @Override
    95. public DbColumnType processTypeConvert(String fieldType) {
    96. // 注意!!processTypeConvert 存在默认类型转换,如果不是你要的效果请自定义返回、非如下直接返回。
    97. if (fieldType.equals("datetime")) {
    98. fieldType = "String";
    99. }
    100. System.out.println("转换类型:" + fieldType);
    101. return super.processTypeConvert(fieldType);
    102. }
    103. });
    104. dsc.setDriverName("com.mysql.jdbc.Driver");
    105. dsc.setUsername("root");
    106. dsc.setPassword("00000");
    107. dsc.setUrl("jdbc:mysql://190.289.20.1:3306/***?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT&allowPublicKeyRetrieval=true");
    108. mpg.setDataSource(dsc);
    109. // 包配置
    110. PackageConfig pc = mpGenerator.getPackageConfig();
    111. mpg.setPackageInfo(pc);
    112. // 策略配置
    113. StrategyConfig strategy = new StrategyConfig();
    114. // strategy.setCapitalMode(true);// 全局大写命名 ORACLE 注意
    115. strategy.setTablePrefix(new String[]{""});// 此处可以修改为您的表前缀
    116. //下划线转驼峰命名法
    117. strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
    118. //自动填充设置
    119. List tableFillList = new ArrayList();
    120. tableFillList.add(new TableFill("create_date", FieldFill.INSERT));//设置为插入时自动填充
    121. tableFillList.add(new TableFill("update_date", FieldFill.INSERT_UPDATE));//设置为更新时自动填充
    122. strategy.setTableFillList(tableFillList);
    123. strategy.setRestControllerStyle(true);
    124. String tables = mpGenerator.scanner("表名,以逗号隔开需要生成的表");
    125. if (tables.length() != 0) {
    126. if ((tables.charAt(tables.length() - 1)) == ',') {
    127. tables = tables.substring(0, tables.length() - 1);
    128. }
    129. String[] tabless = tables.split(",");
    130. strategy.setInclude(tabless); // 需要生成的表
    131. }
    132. strategy.setControllerMappingHyphenStyle(true);
    133. mpg.setStrategy(strategy);
    134. // 自定义配置
    135. InjectionConfig cfg = new InjectionConfig() {
    136. @Override
    137. public void initMap() {
    138. // to do nothing
    139. Map map = new HashMap<>();
    140. this.setMap(map);
    141. }
    142. };
    143. // 如果模板引擎是 freemarker
    144. // String templatePath = "/templates/mapper.xml.ftl";
    145. // 如果模板引擎是 velocity
    146. String templatePath = "/templates/mapper.xml.vm";
    147. String entityPath = "/template/entity.java.vm";
    148. // 自定义输出配置
    149. List focList = new ArrayList<>();
    150. // 自定义配置会被优先输出
    151. focList.add(new FileOutConfig(templatePath) {
    152. @Override
    153. public String outputFile(TableInfo tableInfo) {
    154. // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
    155. List list = tableInfo.getFields();
    156. Iterator iterator = list.iterator();
    157. ResultSetMetaData resultSetMetaData = mpGenerator.getMetaData(dsc,tableInfo.getName());
    158. int i = 1;
    159. while (iterator.hasNext()){
    160. Map map = new HashMap();
    161. try {
    162. //在数据库中字符最大长度
    163. map.put("length",resultSetMetaData.getColumnDisplaySize(i));
    164. //类型
    165. map.put("type",resultSetMetaData.getColumnTypeName(i));
    166. //是否为null
    167. map.put("nullable",resultSetMetaData.isNullable(i));
    168. } catch (SQLException e) {
    169. e.printStackTrace();
    170. }
    171. i++;
    172. iterator.next().setCustomMap(map);
    173. }
    174. return oPath + "/core_database/src/main/resources/mapper/" + mpGenerator.change(tableInfo.getEntityName())+ "/"+tableInfo.getEntityName() + "Mapper.xml";
    175. }
    176. });
    177. cfg.setFileOutConfigList(focList);
    178. mpg.setCfg(cfg);
    179. TemplateConfig tc = new TemplateConfig();
    180. //不生成controller
    181. tc.setController(null);
    182. //不生成xml(xml已经放到resource下面了,这里是不再Java文件夹下面存放了)
    183. tc.setXml(null);
    184. mpg.setTemplate(tc);
    185. // 执行生成
    186. mpg.execute();
    187. }
    188. /**
    189. * 全部转换为小写
    190. *
    191. * @param str 需要转换的参数
    192. * @return 返回替换后字符串
    193. */
    194. private String change(String str) {
    195. char[] cs = str.toCharArray();
    196. StringBuilder rstr = new StringBuilder();
    197. for (char c : cs) {
    198. //如果输入的是大写,+32即可得到小写
    199. if (c >= 'A' && c <= 'Z') {
    200. c += 32;
    201. }
    202. rstr.append(c);
    203. }
    204. return rstr.toString();
    205. }
    206. }

     自定义表信息以及表字段信息注解

    TableInfo(表信息)

    1. import java.lang.annotation.*;
    2. /**
    3. * 表详细信息
    4. * @author wuchuancheng
    5. * @since 2022-07-28
    6. */
    7. @Documented
    8. @Retention(RetentionPolicy.RUNTIME)
    9. @Target(ElementType.TYPE)
    10. public @interface TableInfo {
    11. /**
    12. * 表名
    13. * @return
    14. */
    15. String name() default "";
    16. /**
    17. * 是否需要自动创建
    18. * @return
    19. */
    20. boolean isCreate() default true;
    21. /**
    22. * 注释
    23. * @return
    24. */
    25. String comment() default "";
    26. }

    ColumnInfo(表字段信息)

    1. import java.lang.annotation.*;
    2. /**
    3. * 表字段详细信息
    4. * @author wuchuancheng
    5. * @since 2022-07-28
    6. */
    7. @Documented
    8. @Retention(RetentionPolicy.RUNTIME)
    9. @Target(ElementType.FIELD)
    10. public @interface ColumnInfo {
    11. /**
    12. * 字段名
    13. */
    14. String name() default "";
    15. /**
    16. * 字段注释
    17. */
    18. String comment() default "";
    19. /**
    20. * 最大长度
    21. */
    22. int length() default 0;
    23. /**
    24. * 字段类型
    25. */
    26. String type() default "";
    27. /**
    28. * 是否是主键
    29. */
    30. boolean key() default false;
    31. /**
    32. * 是否为空
    33. */
    34. int nullable() default 0;
    35. }

    自定义模板

    entity.java.vm(放在resource/template下面)

    1. package ${package.Entity};
    2. //这两个改写成你自己的目录
    3. import com.wdz.annotations.ColumnInfo;
    4. import com.wdz.annotations.TableInfo;
    5. #foreach($pkg in ${table.importPackages})
    6. import ${pkg};
    7. #end
    8. #if(${entityLombokModel})
    9. import com.baomidou.mybatisplus.annotations.Version;
    10. import lombok.Data;
    11. import lombok.EqualsAndHashCode;
    12. import lombok.experimental.Accessors;
    13. #end
    14. /**
    15. *

    16. * $!{table.comment}
    17. *

    18. *
    19. * @author ${author}
    20. * @since ${date}
    21. */
    22. @TableInfo(name="${table.name}",comment = "${table.comment}")
    23. #if(${entityLombokModel})
    24. @Data
    25. #if(${superEntityClass})
    26. @EqualsAndHashCode(callSuper = true)
    27. #else
    28. @EqualsAndHashCode(callSuper = false)
    29. #end
    30. @Accessors(chain = true)
    31. #end
    32. #if(${table.convert})
    33. @TableName("${table.name}")
    34. #end
    35. #if(${superEntityClass})
    36. public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
    37. #elseif(${activeRecord})
    38. public class ${entity} extends Model<${entity}> {
    39. #else
    40. public class ${entity} implements Serializable {
    41. #end
    42. private static final long serialVersionUID = 1L;
    43. ## ---------- BEGIN 字段循环遍历 ----------
    44. #foreach($field in ${table.fields})
    45. #if(${field.keyFlag})
    46. #set($keyPropertyName=${field.propertyName})
    47. #end
    48. #if("$!field.comment" != "")
    49. /**
    50. * ${field.comment}
    51. */
    52. #end
    53. @ColumnInfo(name="${field.name}",comment = "${field.comment}",length = ${field.customMap.length},type = "${field.customMap.type}",key = ${field.keyFlag},nullable = ${field.customMap.nullable})
    54. #if(${field.keyFlag})
    55. ## 主键
    56. #if(${field.keyIdentityFlag})
    57. @TableId(value = "${field.name}", type = IdType.AUTO)
    58. #elseif(!$null.isNull(${idType}) && "$!idType" != "")
    59. @TableId(value = "${field.name}", type = IdType.${idType})
    60. #elseif(${field.convert})
    61. @TableId("${field.name}")
    62. #end
    63. ## 普通字段
    64. #elseif(${field.fill})
    65. ## ----- 存在字段填充设置 -----
    66. #if(${field.convert})
    67. @TableField(value = "${field.name}", fill = FieldFill.${field.fill})
    68. #else
    69. @TableField(fill = FieldFill.${field.fill})
    70. #end
    71. #elseif(${field.convert})
    72. @TableField("${field.name}")
    73. #end
    74. ## 乐观锁注解
    75. #if(${versionFieldName}==${field.name})
    76. @Version
    77. #end
    78. ## 逻辑删除注解
    79. #if(${logicDeleteFieldName}==${field.name})
    80. @TableLogic
    81. #end
    82. private ${field.propertyType} ${field.propertyName};
    83. #end
    84. ## ---------- END 字段循环遍历 ----------
    85. #if(!${entityLombokModel})
    86. #foreach($field in ${table.fields})
    87. #if(${field.propertyType.equals("boolean")})
    88. #set($getprefix="is")
    89. #else
    90. #set($getprefix="get")
    91. #end
    92. public ${field.propertyType} ${getprefix}${field.capitalName}() {
    93. return ${field.propertyName};
    94. }
    95. #if(${entityBuilderModel})
    96. public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
    97. #else
    98. public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
    99. #end
    100. this.${field.propertyName} = ${field.propertyName};
    101. #if(${entityBuilderModel})
    102. return this;
    103. #end
    104. }
    105. #end
    106. #end
    107. #if(${entityColumnConstant})
    108. #foreach($field in ${table.fields})
    109. public static final String ${field.name.toUpperCase()} = "${field.name}";
    110. #end
    111. #end
    112. #if(${activeRecord})
    113. @Override
    114. protected Serializable pkVal() {
    115. #if(${keyPropertyName})
    116. return this.${keyPropertyName};
    117. #else
    118. return null;
    119. #end
    120. }
    121. #end
    122. #if(!${entityLombokModel})
    123. @Override
    124. public String toString() {
    125. return "${entity}{" +
    126. #foreach($field in ${table.fields})
    127. #if($!{velocityCount}==1)
    128. "${field.propertyName}=" + ${field.propertyName} +
    129. #else
    130. ", ${field.propertyName}=" + ${field.propertyName} +
    131. #end
    132. #end
    133. "}";
    134. }
    135. #end
    136. }

    第一阶段代码生成器的改造基本完成,这一阶段的作用是根据数据库表生成相对应的实体类,实体类里面还有描述实体类的表信息注解以及字段信息注解,如以下代码类似

    1. import com.wdz.annotations.ColumnInfo;
    2. import com.baomidou.mybatisplus.annotations.TableField;
    3. import com.baomidou.mybatisplus.enums.FieldFill;
    4. import com.baomidou.mybatisplus.annotations.TableId;
    5. import com.baomidou.mybatisplus.enums.IdType;
    6. import com.baomidou.mybatisplus.activerecord.Model;
    7. import com.baomidou.mybatisplus.annotations.TableName;
    8. import com.wdz.annotations.TableInfo;
    9. import java.io.Serializable;
    10. /**
    11. *

    12. * 测试
    13. *

    14. *
    15. * @author wuchuancheng
    16. * @since 2022-07-28
    17. */
    18. @TableInfo(name="test",comment = "测试")
    19. @TableName("test")
    20. public class Test extends Model {
    21. private static final long serialVersionUID = 1L;
    22. /**
    23. * 主键
    24. */
    25. @ColumnInfo(name="id",comment = "主键",length = 32,type = "VARCHAR",key = true,nullable = 0)
    26. @TableId(value = "id", type = IdType.UUID)
    27. private String id;
    28. /**
    29. * 测试
    30. */
    31. @ColumnInfo(name="test",comment = "测试",length = 255,type = "VARCHAR",key = false,nullable = 1)
    32. private String test;
    33. public String getWay() {
    34. return way;
    35. }
    36. public void setWay(String way) {
    37. this.way = way;
    38. }
    39. /**
    40. * 测试way
    41. */
    42. @ColumnInfo(name="way",comment = "测试way",length = 255,type = "VARCHAR",key = false,nullable = 1)
    43. private String way;
    44. /**
    45. * 数字
    46. */
    47. @ColumnInfo(name="ino",comment = "数字",length = 10,type = "INT",key = false,nullable = 1)
    48. private Integer int1;
    49. /**
    50. * 时间
    51. */
    52. @ColumnInfo(name="create_date",comment = "时间",length = 0,type = "DATETIME",key = false,nullable = 1)
    53. @TableField(value = "create_date", fill = FieldFill.INSERT)
    54. private String createDate;
    55. public String getId() {
    56. return id;
    57. }
    58. public void setId(String id) {
    59. this.id = id;
    60. }
    61. public String getTest() {
    62. return test;
    63. }
    64. public void setTest(String test) {
    65. this.test = test;
    66. }
    67. public Integer getInt1() {
    68. return int1;
    69. }
    70. public void setInt1(Integer int1) {
    71. this.int1 = int1;
    72. }
    73. public String getCreateDate() {
    74. return createDate;
    75. }
    76. public void setCreateDate(String createDate) {
    77. this.createDate = createDate;
    78. }
    79. @Override
    80. protected Serializable pkVal() {
    81. return this.id;
    82. }
    83. @Override
    84. public String toString() {
    85. return "Test{" +
    86. ", id=" + id +
    87. ", test=" + test +
    88. ", int1=" + int1 +
    89. ", createDate=" + createDate +
    90. "}";
    91. }
    92. }

    另外,mapper.xml会在resource下生成,不会生成controller。

    关键代码解释:

    这一部分的精髓在这里

    1. // 如果模板引擎是 freemarker
    2. // String templatePath = "/templates/mapper.xml.ftl";
    3. // 如果模板引擎是 velocity
    4. String templatePath = "/templates/mapper.xml.vm";
    5. String entityPath = "/template/entity.java.vm";
    6. // 自定义输出配置
    7. List focList = new ArrayList<>();
    8. // 自定义配置会被优先输出
    9. focList.add(new FileOutConfig(templatePath) {
    10. @Override
    11. public String outputFile(TableInfo tableInfo) {
    12. // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
    13. List list = tableInfo.getFields();
    14. Iterator iterator = list.iterator();
    15. ResultSetMetaData resultSetMetaData = mpGenerator.getMetaData(dsc,tableInfo.getName());
    16. int i = 1;
    17. while (iterator.hasNext()){
    18. Map map = new HashMap();
    19. try {
    20. //在数据库中字符最大长度
    21. map.put("length",resultSetMetaData.getColumnDisplaySize(i));
    22. //类型
    23. map.put("type",resultSetMetaData.getColumnTypeName(i));
    24. //是否为null
    25. map.put("nullable",resultSetMetaData.isNullable(i));
    26. } catch (SQLException e) {
    27. e.printStackTrace();
    28. }
    29. i++;
    30. iterator.next().setCustomMap(map);
    31. }
    32. return oPath + "/core_database/src/main/resources/mapper/" + mpGenerator.change(tableInfo.getEntityName())+ "/"+tableInfo.getEntityName() + "Mapper.xml";
    33. }
    34. });

    /templates/mapper.xml.vm模板是在依赖包mybatis-plus-generate下面

    /template/entity.java.vm模板是在自己项目resource下面

     resultSetMetaData对象是表字段信息集合

    1. /**
    2. * 获取表字段信息
    3. */
    4. public ResultSetMetaData getMetaData(DataSourceConfig dsc,String tableName){
    5. Connection connection = dsc.getConn();
    6. try {
    7. try (PreparedStatement preparedStatement = connection.prepareStatement("select * from " + tableName)) {
    8. return preparedStatement.executeQuery().getMetaData();
    9. }
    10. } catch (SQLException e) {
    11. e.printStackTrace();
    12. return null;
    13. }
    14. }

    而这一段

    1. while (iterator.hasNext()){
    2. Map map = new HashMap();
    3. try {
    4. //在数据库中字符最大长度
    5. map.put("length",resultSetMetaData.getColumnDisplaySize(i));
    6. //类型
    7. map.put("type",resultSetMetaData.getColumnTypeName(i));
    8. //是否为null
    9. map.put("nullable",resultSetMetaData.isNullable(i));
    10. } catch (SQLException e) {
    11. e.printStackTrace();
    12. }
    13. i++;
    14. iterator.next().setCustomMap(map);
    15. }

    是将表字段信息写入到实体类里面去,重点在于.setCustomMap();这个方法

    源码

    将几个数据写入 customMap里面去就可以在entity.java.vm里面使用它

     然后就可以生成带有表信息以及表字段信息注解的实体类了

    2.项目启动时创建表

    这一步的关键是要去数据库读取所有表名和项目的实体类名然后进行判断,判断出若项目有某个实体类而数据库没有这张表,就自动创建这张表,所以我自建了Table,FieldInfo和与之对应的TableMapper,FieldInfoMapper类来查询数据库表以及表字段、创建或更新表。创建或更新表也可以直接在mapper.xml里面写sql语句

     封装表信息实体类

    Table.java

    1. import com.baomidou.mybatisplus.activerecord.Model;
    2. import java.io.Serializable;
    3. import java.util.List;
    4. /**
    5. * 数据库表名集合
    6. * @author wuchuancheng
    7. */
    8. public class Table extends Model {
    9. private static final long serialVersionUID = 1L;
    10. private String tableName;
    11. private String comment;
    12. private List fieldInfos;
    13. public List getFieldInfos() {
    14. return fieldInfos;
    15. }
    16. public void setFieldInfos(List fieldInfos) {
    17. this.fieldInfos = fieldInfos;
    18. }
    19. public String getTableName() {
    20. return tableName;
    21. }
    22. public String getComment() {
    23. return comment;
    24. }
    25. public void setComment(String comment) {
    26. this.comment = comment;
    27. }
    28. public void setTableName(String tableName) {
    29. this.tableName = tableName;
    30. }
    31. @Override
    32. protected Serializable pkVal() {
    33. return serialVersionUID;
    34. }
    35. }
    36. Mapper

      TableMapper.java

      1. import com.baomidou.mybatisplus.mapper.BaseMapper;
      2. import org.apache.ibatis.annotations.Mapper;
      3. import org.apache.ibatis.annotations.Select;
      4. import java.util.List;
      5. /**
      6. * @author wuchuancheng
      7. */
      8. @Mapper
      9. public interface TableMapper extends BaseMapper
      {
    37. /**
    38. * 查询所以表名
    39. * @return 表集合
    40. */
    41. @Select("select table_name from information_schema.tables where table_schema='bd_dongzhebk' and table_type='base table';")
    42. List
    43. tableList();
    44. /**
    45. * 创建表
    46. * @param tableInfo 表信息
    47. */
    48. void createTable(Table tableInfo);
    49. /**
    50. * 更新表
    51. * @param tableInfo 表信息
    52. */
    53. void alterField(Table tableInfo);
    54. }
    55.  TableMapper.xml

      1. mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      2. <mapper namespace="com.wdz.table.mapper.TableMapper">
      3. <resultMap id="BaseResultMap" type="com.wdz.table.entity.Table">
      4. <result column="table_name" property="tableName"/>
      5. resultMap>
      6. <update id="createTable" parameterType="com.wdz.table.entity.Table">
      7. SET NAMES utf8mb4;
      8. SET FOREIGN_KEY_CHECKS = 0;
      9. DROP TABLE IF EXISTS ${tableName};
      10. CREATE TABLE ${tableName}
      11. <foreach collection="fieldInfos" index="index" item="item" open="(" close=")">
      12. ${item.name} ${item.type}(${item.length})
      13. <if test="item.type=='VARCHAR'">
      14. CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
      15. if>
      16. <if test="item.isNull==0">NOT NULLif>
      17. <if test="item.isNull==1">NULL DEFAULT
      18. <if test="(item.type).contains('INT')">
      19. 0
      20. if>
      21. <if test="!(item.type).contains('INT')">
      22. NULL
      23. if>
      24. if>
      25. COMMENT '${item.comment}',
      26. <if test="index == fieldInfos.size()-1">
      27. <foreach collection="fieldInfos" item="item2">
      28. <if test="item2.isKey == true">
      29. PRIMARY KEY (${item2.name}) USING BTREE
      30. if>
      31. foreach>
      32. if>
      33. foreach>
      34. ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '${comment}' ROW_FORMAT
      35. = Compact;
      36. update>
      37. <update id="alterField" parameterType="com.wdz.table.entity.Table">
      38. <foreach collection="fieldInfos" item="item">
      39. alter table ${tableName} add ${item.name} ${item.type}(${item.length})
      40. <if test="item.type=='VARCHAR'">
      41. CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci
      42. if>
      43. <if test="item.isNull==0">NOT NULLif>
      44. <if test="item.isNull==1">NULL DEFAULT
      45. <if test="(item.type).contains('INT')">
      46. 0
      47. if>
      48. <if test="!(item.type).contains('INT')">
      49. NULL
      50. if>
      51. if>
      52. comment '${item.comment}';
      53. foreach>
      54. update>
      55. mapper>

      封装字段信息实体类

       FieldInfo.java

      1. import com.baomidou.mybatisplus.activerecord.Model;
      2. import java.io.Serializable;
      3. /**
      4. * 字段信息
      5. * @author wuchuancheng
      6. */
      7. public class FieldInfo extends Model {
      8. private static final long serialVersionUID = 1L;
      9. private String name;
      10. private String type;
      11. private Integer length;
      12. private Integer decimal;
      13. private String defaulc;
      14. public String getDefaulc() {
      15. return defaulc;
      16. }
      17. public void setDefaulc(String defaulc) {
      18. this.defaulc = defaulc;
      19. }
      20. public String getCollation() {
      21. return collation;
      22. }
      23. public void setCollation(String collation) {
      24. this.collation = collation;
      25. }
      26. private Integer isNull;
      27. private String nullable;
      28. private String collation;
      29. private String keyValue;
      30. public String getKeyValue() {
      31. return keyValue;
      32. }
      33. public void setKeyValue(String keyValue) {
      34. this.keyValue = keyValue;
      35. }
      36. public String getNullable() {
      37. return nullable;
      38. }
      39. public void setNullable(String nullable) {
      40. this.nullable = nullable;
      41. }
      42. private boolean isKey;
      43. public String getName() {
      44. return name;
      45. }
      46. public void setName(String name) {
      47. this.name = name;
      48. }
      49. public String getType() {
      50. return type;
      51. }
      52. public void setType(String type) {
      53. this.type = type;
      54. }
      55. public Integer getLength() {
      56. return length;
      57. }
      58. public void setLength(Integer length) {
      59. this.length = length;
      60. }
      61. public Integer getDecimal() {
      62. return decimal;
      63. }
      64. public void setDecimal(Integer decimal) {
      65. this.decimal = decimal;
      66. }
      67. public Integer getIsNull() {
      68. return isNull;
      69. }
      70. public void setIsNull(Integer isNull) {
      71. this.isNull = isNull;
      72. }
      73. public boolean isKey() {
      74. return isKey;
      75. }
      76. public void setKey(boolean key) {
      77. isKey = key;
      78. }
      79. public String getAnnotate() {
      80. return annotate;
      81. }
      82. public void setAnnotate(String annotate) {
      83. this.annotate = annotate;
      84. }
      85. private String annotate;
      86. private String comment;
      87. public String getComment() {
      88. return comment;
      89. }
      90. public void setComment(String comment) {
      91. this.comment = comment;
      92. }
      93. @Override
      94. protected Serializable pkVal() {
      95. return serialVersionUID;
      96. }
      97. @Override
      98. public String toString() {
      99. return "FieldInfo{" +
      100. "name='" + name + '\'' +
      101. ", type='" + type + '\'' +
      102. ", length=" + length +
      103. ", decimal=" + decimal +
      104. ", isNull=" + isNull +
      105. ", isKey=" + isKey +
      106. ", annotate='" + annotate + '\'' +
      107. ", comment='" + comment + '\'' +
      108. '}';
      109. }
      110. }

       FieldInfoMapper.java

      1. import com.baomidou.mybatisplus.mapper.BaseMapper;
      2. import org.apache.ibatis.annotations.Param;
      3. import org.apache.ibatis.annotations.Select;
      4. import java.util.List;
      5. /**
      6. * @author wuchuancheng
      7. */
      8. public interface FieldInfoMapper extends BaseMapper {
      9. /**
      10. * 根据表名查询所有字段信息集合
      11. * @param tableName 表名
      12. * @return 字段信息集合
      13. */
      14. List getFieldInfos(@Param("tableName") String tableName);
      15. }

      FieldInfoMapper.xml

      1. mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      2. <mapper namespace="com.wdz.table.mapper.FieldInfoMapper">
      3. <resultMap id="BaseResultMap" type="com.wdz.table.entity.FieldInfo">
      4. <result column="Field" property="name" />
      5. <result column="Type" property="type" />
      6. <result column="length" property="length" />
      7. <result column="decimal" property="decimal" />
      8. <result column="Null" property="nullable" />
      9. <result column="Comment" property="comment" />
      10. <result column="Collation" property="collation" />
      11. <result column="Default" property="defaulc" />
      12. <result column="Key" property="keyValue" />
      13. resultMap>
      14. <select id="getFieldInfos" parameterType="String" resultMap="BaseResultMap">
      15. show full columns from ${tableName};
      16. select>
      17. mapper>

      功能类

      AutomaticConstruct.java

      1. import com.wdz.annotations.ColumnInfo;
      2. import com.wdz.annotations.TableInfo;
      3. import com.wdz.table.entity.FieldInfo;
      4. import com.wdz.table.entity.Table;
      5. import com.wdz.table.mapper.FieldInfoMapper;
      6. import com.wdz.table.mapper.TableMapper;
      7. import org.springframework.beans.factory.annotation.Value;
      8. import org.springframework.boot.ApplicationArguments;
      9. import org.springframework.boot.ApplicationRunner;
      10. import org.springframework.core.annotation.Order;
      11. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
      12. import org.springframework.core.io.support.ResourcePatternResolver;
      13. import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
      14. import org.springframework.core.type.classreading.MetadataReader;
      15. import org.springframework.core.type.classreading.MetadataReaderFactory;
      16. import org.springframework.stereotype.Component;
      17. import org.springframework.util.ClassUtils;
      18. import javax.annotation.Resource;
      19. import java.io.IOException;
      20. import java.lang.reflect.Field;
      21. import java.util.*;
      22. @Component
      23. @Order(1)//项目启动时自动执行
      24. public class AutomaticConstruct implements ApplicationRunner {
      25. /**
      26. * 获取当前所在环境 根据项目自己更改,作用是判断当前项目是不是在正式服启动
      27. */
      28. @Value("${spring.profiles.active}")
      29. private String tenant;
      30. @Resource
      31. private TableMapper tableMapper;
      32. @Resource
      33. private FieldInfoMapper fieldInfoMapper;
      34. /**
      35. * 将测试服数据库同步到正式服上
      36. * @param args
      37. * @throws Exception
      38. */
      39. @Override
      40. public void run(ApplicationArguments args) throws Exception {
      41. //如果是正式服环境就做这些事情,这些自动建表的事情
      42. String tenant = "pro";
      43. if(tenant.equals(this.tenant)){
      44. List
      iterator = tableMapper.tableList();
    56. List
    57. tables = getTableNames();
    58. boolean bln = true;
    59. List
    60. n = new ArrayList<>();
    61. for (Table table : tables) {
    62. bln = true;
    63. for(Table table1 : iterator){
    64. if((table.getTableName()).equals(table1.getTableName())){
    65. bln = false;
    66. List fieldInfos = fieldInfoMapper.getFieldInfos(table.getTableName());
    67. List fieldInfos2 = table.getFieldInfos();
    68. List list = new ArrayList<>();
    69. boolean bln2 = true,bln3 = false;
    70. for(FieldInfo fieldInfo:fieldInfos2){
    71. bln2 = true;
    72. for(FieldInfo fieldInfo2:fieldInfos){
    73. if((fieldInfo.getName()).equals(fieldInfo2.getName())){
    74. bln2 = false;
    75. break;
    76. }
    77. }
    78. if(bln2){
    79. list.add(fieldInfo);
    80. bln3 = true;
    81. }
    82. }
    83. //执行更新
    84. if(bln3) {
    85. Table table2 = new Table();
    86. table2.setTableName(table.getTableName());
    87. table2.setFieldInfos(list);
    88. tableMapper.alterField(table2);
    89. }
    90. break;
    91. }
    92. }
    93. if(bln){
    94. n.add(table);
    95. }
    96. }
    97. for(Table table:n){
    98. tableMapper.createTable(table);
    99. }
    100. }
    101. }
    102. /**
    103. * 获取模块中的数据库对应实体类
    104. * @return
    105. */
    106. public List
    107. getTableNames(){
    108. List
    109. list = new ArrayList<>();
    110. //spring工具类,可以获取指定路径下的全部类
    111. ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    112. try {
    113. String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
    114. ClassUtils.convertClassNameToResourcePath("com.wdz") + "/**/entity/*.class";
    115. org.springframework.core.io.Resource[] resources =resourcePatternResolver.getResources(pattern);
    116. //MetadataReader 的工厂类
    117. MetadataReaderFactory readerfactory = new CachingMetadataReaderFactory(resourcePatternResolver);
    118. for (org.springframework.core.io.Resource resource : resources) {
    119. //用于读取类信息
    120. MetadataReader reader = readerfactory.getMetadataReader(resource);
    121. //扫描到的class
    122. String classname = reader.getClassMetadata().getClassName();
    123. Class clazz = Class.forName(classname);
    124. //判断是否有指定主解
    125. TableInfo anno = clazz.getAnnotation(TableInfo.class);
    126. if (anno != null && anno.isCreate()) {
    127. //将含有@TableInfo的类的类名放入list
    128. Table table = new Table();
    129. table.setTableName(anno.name());
    130. table.setComment(anno.comment());
    131. List fieldInfos = new ArrayList<>();
    132. Field[] fields = clazz.getDeclaredFields();
    133. for(Field field:fields){
    134. field.setAccessible(true);
    135. ColumnInfo columnInfo = field.getAnnotation(ColumnInfo.class);
    136. if(columnInfo != null){
    137. FieldInfo fieldInfo = new FieldInfo();
    138. fieldInfo.setType(columnInfo.type());
    139. fieldInfo.setName(columnInfo.name());
    140. fieldInfo.setLength(columnInfo.length());
    141. fieldInfo.setKey(columnInfo.key());
    142. fieldInfo.setIsNull(columnInfo.nullable());
    143. fieldInfo.setComment(columnInfo.comment());
    144. fieldInfos.add(fieldInfo);
    145. }
    146. }
    147. table.setFieldInfos(fieldInfos);
    148. list.add(table);
    149. }
    150. }
    151. } catch (IOException | ClassNotFoundException ignored) {
    152. ignored.printStackTrace();
    153. }
    154. return list;
    155. }
    156. }
    157. AutomaticConstruct类的作用是,在项目启动的时候

      1. 获取数据库所有表集合(调用tableMapper.tableList())
      2. 获取项目所有实体类集合(调用getTableNames())
      3. 两者进行判断,数据库没有的就创建(调用tableMapper.createTable(table))
      4. 数据库有但是表字段不全(调用tableMapper.alterField(table2))更新

      3.完成

      就这样大功告成了,我把所有需要用到的都贴出来了,复制就可以直接使用,不会有问题。制作不易,还望点个赞。如果有问题可以留言评论。

      欢迎大家访问我的博客way博客

    158. 相关阅读:
      2022年Java面试题最新整理,附白话答案
      测试.net文字转语音模块System.Speech
      XML配置文件解析与建模
      数据库复习
      亿级流量高并发春晚互动前端技术揭秘
      开源相机管理库Aravis学习(一)——安装
      CCF CSP认证 历年题目自练Day26
      Linux友人帐之环境变量
      ​Linux·i2c驱动架构​
      vue实现防抖函数、节流函数,全局使用【输入框、按钮】
    159. 原文地址:https://blog.csdn.net/weixin_43900374/article/details/126049835