Mybatis-plus代码生成器通过Oracle,只生成文件而不能生成字节码的原因探究

源码分析

Mybatis-plus逆向工程生成源码的主要流程是在AutoGenerator调用execute()方法时,会根据你设置的DataSourceConfig里的

DbType属性,判断你的数据库类型,然后从你的数据库查询出来相应的数据表名,而后将信息存入到TableInfo里,而后进行逆向生

成,进行逆向生成的过程中。

第一步解析配置:

/**
 * 生成代码
 */
public void execute() {
    logger.debug("==========================准备生成文件...==========================");
    // 初始化配置
    if (null == config) {
       //这里会生成配置,非常重要,也是唯一受可以受我们影响的,也是最容易出问题的,我就是在这里出了错。
        config = new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig);
        if (null != injectionConfig) {
            injectionConfig.setConfig(config);
        }
    }
    if (null == templateEngine) {
        // 为了兼容之前逻辑,采用 Velocity 引擎 【 默认 】
        templateEngine = new VelocityTemplateEngine();
    }
    // 模板引擎初始化执行文件输出
    templateEngine.init(this.pretreatmentConfigBuilder(config)).mkdirs().batchOutput().open();
    logger.debug("==========================文件生成完成!!!==========================");
}

第二步:使用new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig);去生成config配置信息。

//这里是是创建一个ConfigBuilder
public ConfigBuilder(PackageConfig packageConfig, DataSourceConfig dataSourceConfig, StrategyConfig strategyConfig,
                     TemplateConfig template, GlobalConfig globalConfig) {
    // 全局配置
    if (null == globalConfig) {
        this.globalConfig = new GlobalConfig();
    } else {
        this.globalConfig = globalConfig;
    }
    // 模板配置
    if (null == template) {
        this.template = new TemplateConfig();
    } else {
        this.template = template;
    }
    // 包配置
    if (null == packageConfig) {
        handlerPackage(this.template, this.globalConfig.getOutputDir(), new PackageConfig());
    } else {
        handlerPackage(this.template, this.globalConfig.getOutputDir(), packageConfig);
    }
    this.dataSourceConfig = dataSourceConfig;
    //这一步,就是解析你配置的dataSourceConfig对象,这一步代码会产生三个非常重要的属性
    handlerDataSource(dataSourceConfig);
    // 策略配置
    if (null == strategyConfig) {
        this.strategyConfig = new StrategyConfig();
    } else {
        this.strategyConfig = strategyConfig;
    }
    //SQLITE 数据库不支持注释获取
    commentSupported = !dataSourceConfig.getDbType().equals(DbType.SQLITE);
    //这里会进行非常重要的功能解析,其中要针对哪些表的操作,就是发生在这里
    handlerStrategy(this.strategyConfig);
}


 /**
     * 处理数据源配置,这是非常非常关键的一步,这三个属性接下来会用到
     *
     * @param config DataSourceConfig
     */
    private void handlerDataSource(DataSourceConfig config) {
        connection = config.getConn();
        dbType = config.getDbType();
        dbQuery = config.getDbQuery();
    }

 

第三步:执行handlerStrategy(this.strategyConfig);这里会针对你的配置,来对表进行处理

private void handlerStrategy(StrategyConfig config) {
    processTypes(config);
    //这一步就是根据你配置的信息,去获取相关的表及表的column信息
    tableInfoList = getTablesInfo(config);
}

第四步:执行 getTablesInfo(config);

private List<TableInfo> getTablesInfo(StrategyConfig config) {
    boolean isInclude = (null != config.getInclude() && config.getInclude().length > 0);
    boolean isExclude = (null != config.getExclude() && config.getExclude().length > 0);
    if (isInclude && isExclude) {
        throw new RuntimeException("<strategy> 标签中 <include> 与 <exclude> 只能配置一项!");
    }
    if (config.getNotLikeTable() != null && config.getLikeTable() != null) {
        throw new RuntimeException("<strategy> 标签中 <likeTable> 与 <notLikeTable> 只能配置一项!");
    }
    //TableInfo里封装的就是各个表和表里的列对应的信息集合,这个集合深刻影响着我们生成的entity
    List<TableInfo> tableList = new ArrayList<>();

    //需要反向生成或排除的表信息
    List<TableInfo> includeTableList = new ArrayList<>();
    List<TableInfo> excludeTableList = new ArrayList<>();

    //不存在的表名
    Set<String> notExistTables = new HashSet<>();
     //这里非常重要,这里会判断你要解析的数据库类型,并且生成对数据库的数据表查询语句
    try {
        //这里获取tableSql,tableSql就是一条查询数据库信息的sql语句,不同的数据库类型,拥有不同的sql,我们可以看不同数据库实现的IDbQuery
       //这里使用的dbQuery就是第二步解析datasource获取的dbQuery,而这个属性,在创建datasourceconfig时,我们可以指定
        String tablesSql = dbQuery.tablesSql();
        //这里的dbType也是第二步解析的datasource获取的
        if (DbType.POSTGRE_SQL == this.dbType) {
            String schema = dataSourceConfig.getSchemaName();
            if (schema == null) {
                //pg 默认 schema=public
                schema = "public";
                dataSourceConfig.setSchemaName(schema);
            }
            tablesSql = String.format(tablesSql, schema);
        } else if (DbType.KINGBASE_ES == this.dbType) {
            String schema = dataSourceConfig.getSchemaName();
            if (schema == null) {
                //kingbase 默认 schema=PUBLIC
                schema = "PUBLIC";
                dataSourceConfig.setSchemaName(schema);
            }
            tablesSql = String.format(tablesSql, schema);
        } else if (DbType.DB2 == this.dbType) {
            String schema = dataSourceConfig.getSchemaName();
            if (schema == null) {
                //db2 默认 schema=current schema
                schema = "current schema";
                dataSourceConfig.setSchemaName(schema);
            }
            tablesSql = String.format(tablesSql, schema);
        }
        //oracle数据库表太多,出现最大游标错误
        else if (DbType.ORACLE == this.dbType) {
             //我们设置的this.dbType为oracle,因此会走这里,我们可以看到这里首先获取了datasourceConfig的SchemaName属性
            String schema = dataSourceConfig.getSchemaName();
            //这里我们可以看到,如果我们不手动在datasourceConfig里设置scheam的话,那么它会使用username作为默认的shceam。
            if (schema == null) {
                schema = dataSourceConfig.getUsername().toUpperCase();
                dataSourceConfig.setSchemaName(schema);
            }
           //这里就是生成数据库查询表的语句,我的正是这里出了问题,因为我的数据库用户名和密码,并不是和我数据库的
            scheam相同,所以导致,生成的查询表语句,查不到对应的表,而官方示例的代码里又没有配置scheam的,导致我的
            查不到对应表。而且基本上排查到这一步,就能查询出问题了。
            tablesSql = String.format(tablesSql, schema);
        }
        StringBuilder sql = new StringBuilder(tablesSql);
        if (config.isEnableSqlFilter()) {
            if (config.getLikeTable() != null) {
                sql.append(" AND ").append(dbQuery.tableName()).append(" LIKE '").append(config.getLikeTable().getValue()).append("'");
            } else if (config.getNotLikeTable() != null) {
                sql.append(" AND ").append(dbQuery.tableName()).append(" NOT LIKE '").append(config.getNotLikeTable().getValue()).append("'");
            }
            if (isInclude) {
                sql.append(" AND ").append(dbQuery.tableName()).append(" IN (")
                    .append(Arrays.stream(config.getInclude()).map(tb -> "'" + tb + "'").collect(Collectors.joining(","))).append(")");
            } else if (isExclude) {
                sql.append(" AND ").append(dbQuery.tableName()).append(" NOT IN (")
                    .append(Arrays.stream(config.getExclude()).map(tb -> "'" + tb + "'").collect(Collectors.joining(","))).append(")");
            }
        }
        TableInfo tableInfo;
        //这一步同样是使用的第二步解析datasource获取的connection,我的bug在上一步已经解决,加入该步骤是为了说明代码生成器的拓展点,
        try (PreparedStatement preparedStatement = connection.prepareStatement(sql.toString());
             ResultSet results = preparedStatement.executeQuery()) {
            while (results.next()) {
                String tableName = results.getString(dbQuery.tableName());
                if (StringUtils.isNotBlank(tableName)) {
                    tableInfo = new TableInfo();
                    tableInfo.setName(tableName);

                    if (commentSupported) {
                        String tableComment = results.getString(dbQuery.tableComment());
                        if (config.isSkipView() && "VIEW".equals(tableComment)) {
                            // 跳过视图
                            continue;
                        }
                        tableInfo.setComment(tableComment);
                    }

                    if (isInclude) {
                        for (String includeTable : config.getInclude()) {
                            // 忽略大小写等于 或 正则 true
                            if (tableNameMatches(includeTable, tableName)) {
                                includeTableList.add(tableInfo);
                            } else {
                                //过滤正则表名
                                if (!REGX.matcher(includeTable).find()) {
                                    notExistTables.add(includeTable);
                                }
                            }
                        }
                    } else if (isExclude) {
                        for (String excludeTable : config.getExclude()) {
                            // 忽略大小写等于 或 正则 true
                            if (tableNameMatches(excludeTable, tableName)) {
                                excludeTableList.add(tableInfo);
                            } else {
                                //过滤正则表名
                                if (!REGX.matcher(excludeTable).find()) {
                                    notExistTables.add(excludeTable);
                                }
                            }
                        }
                    }
                    tableList.add(tableInfo);
                } else {
                    System.err.println("当前数据库为空!!!");
                }
            }
        }
        // 将已经存在的表移除,获取配置中数据库不存在的表
        for (TableInfo tabInfo : tableList) {
            notExistTables.remove(tabInfo.getName());
        }
        if (notExistTables.size() > 0) {
            System.err.println("表 " + notExistTables + " 在数据库中不存在!!!");
        }

        // 需要反向生成的表信息
        if (isExclude) {
            tableList.removeAll(excludeTableList);
            includeTableList = tableList;
        }
        if (!isInclude && !isExclude) {
            includeTableList = tableList;
        }
        // 性能优化,只处理需执行表字段 github issues/219
        includeTableList.forEach(ti -> convertTableFields(ti, config));
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return processTable(includeTableList, config.getNaming(), config);
}

拓展总结:从上面的源码分析我们可以看出来,自动代码生成器提供的一个拓展点,就是DatasourceConfig,他里面的IDbQuery类型的属性,影响着entity的类名、属性名称等,IDbQuery是一个接口,不同的数据库类型,对应者不同的IDbQuery,当然我们可以自定义一个IDbQuery来影响我们的Entity生成。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值