zxpnet网站 zxpnet网站
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

zxpnet

一个爱学习的java开发攻城狮
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 大后端课程视频归档
  • 南航面试题
  • 并发编程

  • 性能调优

  • java8语法

  • lombok

  • 日志

  • 工具类

  • spring

  • mybatis

  • springboot

  • redis

  • zookeeper

  • springcloud

  • dubbo

  • netty

  • springsecurity

  • mq消息中间件

  • shiro

  • beetle

  • 模板引擎

  • jpa

  • 数据结构与算法

  • 数据库知识与设计

  • gradle

  • maven

  • bus

  • 定时任务

  • docker

  • centos

  • 加解密

  • biz业务

  • pigx项目

  • 开源项目

  • 品达通用权限项目-黑马

  • 货币交易项目coin-尚学堂

  • php

  • backend
  • 代码生成器
shollin
2021-06-01
目录

手写简易版代码生成器

  • 需求分析
  • 代码生成器原理
    • 知识点:
    • 难点
  • 数据库元数据相关知识
    • 数据库元数据(DatabaseMetaData)
    • 参数元数据(ParameterMetaData)
    • 结果集元数据(ResultSetMetaData):
  • jdbc操作数据库
  • 写模板
  • freemarker用到的指令
  • 写生成代码的业务
  • 安装ojdbc6驱动
  • 数据类型的一些坑

# 需求分析

  • 后台系统有一些业务逻辑是相似的,如单表的增删改查,用代码生成器快速生成重复的代码,生成后再小范围修改
  • CMS系统中,有些页面访问量很大,如果都从数据库或者缓存里面查,会有性能上的下降,如果数据很少修改,则可以直接将数据生成静态页面。

# 代码生成器原理

利用模板语言的静态化功能,如freemarker, 数据+模板=静态文件

  1. 用户填写的数据库信息,工程搭建信息等等需要构造到实体类对象中方便操作
  2. 数据库表信息,数据库字段信息需要构造到实体类中
  3. 构造Freemarker数据模型,将数据库表对象和基本配置存入到Map集合中
  4. 借助Freemarker完成代码生成
  5. 自定义公共代码模板

image-20210616233602390

# 知识点:

字符串的相关操作,文件相关操作、配置文件的相关操作

# 难点

读取数据库的元数据(数据库、表、字段),然后根据一些生成规则,这里主要是表的生成规则,生成出符合业务需求的代码,常规业务场景有: 生成model、service、controller、sql、视图层(增加、编辑、列表)、数据的JSR303验证等等。 复杂一点的生成器,可以多表关联生成,如主子表、树形表等。

常规业务的代码生成如: 人人开源;

image-20210618174039729

复杂的代码生成如:jeesite、 jeecg等。

image-20210618174232207

# 数据库元数据相关知识

# 数据库元数据(DatabaseMetaData)

是由Connection对象通过getMetaData方法获取而来,主要封装了是对数据库本身的一些整体综合信息,例如数据库的产品名称,数据库的版本号,数据库的URL,是否支持事务等等。 以下有一些关于DatabaseMetaData的常用方法:

  • getDatabaseProductName:获取数据库的产品名称
  • getDatabaseProductName:获取数据库的版本号
  • getUserName:获取数据库的用户名
  • getURL:获取数据库连接的URL
  • getDriverName:获取数据库的驱动名称
  • driverVersion:获取数据库的驱动版本号
  • isReadOnly:查看数据库是否只允许读操作
  • supportsTransactions:查看数据库是否支持事务

具体可以参考jdk1.8的文档CHM.

image-20210616225558521

# 参数元数据(ParameterMetaData)

是由PreparedStatement对象通过getParameterMetaData方法获取而来,主要是针对PreparedStatement对象和其预编译的SQL命令语句提供一些信息,ParameterMetaData能提供 占位符参数的个数,获取指定位置占位符的SQL类型等等,以下有一些关于ParameterMetaData的常用方法:

  • getParameterCount:获取预编译SQL语句中占位符参数的个数
@Test
public void test() throws Exception {
	String sql = "select * from bs_user where id=?";
	PreparedStatement pstmt = conn.prepareStatement(sql);
	pstmt.setString(1, "1063705482939731968");
	//获取ParameterMetaData对象
	ParameterMetaData paramMetaData = pstmt.getParameterMetaData();
	//获取参数个数
	int paramCount = paramMetaData.getParameterCount();
	System.out.println(paramCount);
}
1
2
3
4
5
6
7
8
9
10
11

# 结果集元数据(ResultSetMetaData):

是由ResultSet对象通过getMetaData方法获取而来,主要是针对由数据库执行的SQL脚本命令获取的结果集对象ResultSet中提供的一些信息,比如结果集中的列数、指定列的名称、指定 列的SQL类型等等,可以说这个是对于框架来说非常重要的一个对象。 以下有一些关于ResultSetMetaData的常用方法:

  • getColumnCount:获取结果集中列项目的个数
  • getColumnType:获取指定列的SQL类型对应于Java中Types类的字段
  • getColumnTypeName:获取指定列的SQL类型
  • getClassName:获取指定列SQL类型对应于Java中的类型(包名加类名)

参考文章:数据库元数据MetaData - fjdingsd - 博客园 (cnblogs.com) (opens new window)

# jdbc操作数据库

@Slf4j
public class DbUtil {

    private static ThreadLocal<Connection> connectionTl = new ThreadLocal<Connection>();
    private static Connection connection;

    /**
     * 初始化
     * @param db
     * @return
     */
    public static Connection initDB(Db db) throws ClassNotFoundException, SQLException {

        String driver = StrUtil.emptyToDefault(db.getDriver(), "com.mysql.cj.jdbc.Driver");
        //
        String url = StrUtil.format("jdbc:mysql://{}:{}/{}?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai",
                StrUtil.emptyToDefault(db.getHost(), "localhost"), ObjectUtil.defaultIfNull(db.getPort(), 3306), db.getDbname());

        String username = db.getUsername();
        String password = db.getPassword();
        log.debug("数据库连接信息:"+db);

        //classLoader,加载对应驱动
        Class.forName(driver);
        connection = (Connection) DriverManager.getConnection(url, username, password);
        connectionTl.set(connection);

        return connection;
    }

    public static Connection getConnection() {
        return connection;
    }

    /**
     * 获取所有的表名
     * @param connection
     * @return
     * @throws SQLException
     */
    public static List<String> getTableNames(Connection connection) throws SQLException {
        DatabaseMetaData metaData = connection.getMetaData();
        String catalog = connection.getCatalog();
        ResultSet tables = metaData.getTables(catalog, null, null, null);
        List<String> tableNameLists = new ArrayList<>();
        while (tables.next()) {
            String table_name = tables.getString("TABLE_NAME");
            // 将表转成驼峰形式
            String modelName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, table_name);
            tableNameLists.add(table_name);
        }
        log.debug(StrUtil.format("获取数据库:{} 的所有表格,{}",catalog,tableNameLists));
        return tableNameLists;
    }

    /**
     * 获取表的所有列
     *
     * @return
     */
    public static List<ColumnClass> getColumns(String tableName) throws SQLException {
        DatabaseMetaData metaData = connection.getMetaData();
        // 找出表的所有列
        //catalog 其实也就是数据库名
        final String catalog = connection.getCatalog();
        ResultSet columns = metaData.getColumns(catalog, null, tableName, null);
        // 找出表的主键,有可能有多个
        ResultSet primaryKeys = metaData.getPrimaryKeys(catalog, null, tableName);

        ArrayList<ColumnClass> columnLists = Lists.newArrayList();
        while (columns.next()) {
            String column_name = columns.getString("COLUMN_NAME");
            String type_name = columns.getString("TYPE_NAME");
            String remarks = columns.getString("REMARKS");
            ColumnClass columnClass = new ColumnClass();
            columnClass.setRemark(remarks);
            columnClass.setColumnName(column_name);
            columnClass.setType(type_name);
            columnClass.setPropertyName(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, column_name));
            columnClass.setIsPrimary(false);
            columnLists.add(columnClass);

        }
        while (primaryKeys.next()) {
            String pkName = primaryKeys.getString("COLUMN_NAME");
            if (StrUtil.isNotBlank(pkName)) {
                columnLists.stream().forEach(columnClass -> {
                    if (pkName.equals(columnClass.getColumnName())) {
                        columnClass.setIsPrimary(true);
                    }
                });
            }
        }
        log.debug(StrUtil.format("获取{}数据表:{} 的所有列",catalog,tableName));
        return columnLists;
    }

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Db db = new Db();
        db.setDriver("");
        db.setPort(3306);
        db.setHost("localhost");
        db.setDbname("nacos");
        db.setUsername("root");
        db.setPassword("123456");

        Connection connection = initDB(db);
        List<String> tableNames = getTableNames(connection);
        List<ColumnClass> configInfo = getColumns("his_config_info");
        System.out.println(tableNames);
        System.out.println(configInfo);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

参考文章:

通过jdbc获取数据库中的表结构 主键 各个表字段类型及应用生成实体类 - 若 ♂ 只如初见 - 博客园 (cnblogs.com) (opens new window)

# 写模板

有freemarker的模板,也有vm模板,如果生成mybatis的model和xml,需要判断很多条件。

package ${packageName}.service;

import ${packageName}.model.${modelName};
import ${packageName}.mapper.${mapperName};
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;

@Service
public class ${serviceName}{

    @Autowired
    ${mapperName} ${mapperName?uncap_first};
    public List<${modelName}> getAll${modelName}s(){
        return ${mapperName?uncap_first}.getAll${modelName}s();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# freemarker用到的指令

字符串使用的内建函数:

  • html: 字符串中所有的特殊 HTML 字符都需要用实体引用来代替(比如<代替<)。
  • cap_first:字符串的第一个字母变为大写形式
  • lower_case:字符串的小写形式
  • upper_case:字符串的大写形式
  • trim:去掉字符串首尾的空格
${column.propertyName?uncap_first}
1

# 写生成代码的业务

private void generate(Template template, TableClass tableClass, String path) throws IOException, TemplateException {
        File folder = new File(path);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        String fileName = path + "/" + tableClass.getModelName() + template.getName().replace(".ftl", "").replace("Model", "");
        @Cleanup
        FileOutputStream fos = new FileOutputStream(fileName);
        @Cleanup
        OutputStreamWriter out = new OutputStreamWriter(fos);
        template.process(tableClass,out);
}
1
2
3
4
5
6
7
8
9
10
11
12

# 安装ojdbc6驱动

[ojdbc6最新版下载](Central Repository: com/oracle/database/jdbc/ojdbc6 (maven.org) (opens new window))

mvn install:install-file -Dfile=ojdbc6-11.2.0.4.jar -DgroupId=com.oracle -DartifactId=ojdbc6 -Dversion=11.2.0.4 -Dpackaging=jar

<dependency
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>11.2.0.4</version>
</dependency>
1
2
3
4
5
6
7

# 数据类型的一些坑

数据库里面的字段类型,要对应成model属性里面的java类型,在mybatis当中,可能还与mybatis的jdbyType类型不一样,也要转换对应,如INT字段类型,mybatis里面就没有

  • BIGINT UNSIGNED 、INT UNSIGNED 等
  • LONGTEXT 长文本型

参考项目:

江南一点雨/generate_code (gitee.com) (opens new window)

参考视频:2019JAVA最新FreeMarker的教程-代码生成器实战 (opens new window)

freemarker教程,都是实用干货

#代码生成器
最近更新
01
国际象棋
09-15
02
成语
09-15
03
自然拼读
09-15
更多文章>
Theme by Vdoing | Copyright © 2019-2023 zxpnet | 粤ICP备14079330号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式