需求背景:

我们工作中经常会用到多个环境下的数据库,因此需要经常同步开发、生产的数据库表结构,所以我这边用java开发了一个可以快速比对2个不同数据库的所有表结构的工具类。

使用方法:

依赖:JDK1.8、MySql8、lombok(作用是省掉get set方法,可以去掉自己写get set)

只需要在main方法中输入两个数据库的连接,需要比对的数据库名称,然后运行mian方法,然后结果就会默认生成一个txt的比对结果文档到C://根目录下。如图:
在这里插入图片描述

工具代码:

只需要一个JAVA类:
TableCompareUtil:

package com.jack.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.sql.*;
import java.util.*;

import lombok.Data;

/**
 * @program: 数据库比对工具
 * @description: 比对2个数据库的所有表、结构、注解的不同之处
 * @author: Jack.Fang
 * @date: 2020年3月27日
 **/
public class TableCompareUtil {

    /**
     * 定义数据库连接参数
     */
    public static String product_driverClassName;
    public static String product_url;
    public static String product_username;
    public static String product_password;

    public static String develop_driverClassName;
    public static String develop_url;
    public static String develop_username;
    public static String develop_password;

    /**
     * 封装提示信息
     */
    public static StringBuffer[] sb = { new StringBuffer(), new StringBuffer(),
            new StringBuffer(), new StringBuffer(), new StringBuffer(),
            new StringBuffer() };

    /**
     * 封装表属性
     */
    @Data
    static class Table {
        //表名称
        public String tableName;
        //字段名称+字段类型
        public HashMap<String,String> column;
        //字段名称+字段注解
        public HashMap<String,String> columnComment;
    }

    /**
     * 生产库
     * @return
     * @throws Exception
     */
    public static Connection getTransaction_product() throws Exception {
        Class.forName(product_driverClassName);
        Connection conn = DriverManager.getConnection(
                product_url, product_username, product_password);
        if (conn != null)System.out.println("生产---数据库加载成功!");
        return conn;
    }

    /**
     * 开发库
     * @return
     * @throws Exception
     */
    public static Connection getTransaction_develop() throws Exception {
        Class.forName(develop_driverClassName);
        Connection conn = DriverManager.getConnection(
                develop_url,develop_username,develop_password);
        if (conn != null)System.out.println("开发---数据库加载成功!");
        return conn;
    }

    /**
     * 初始化 比对文件提示信息
     */
    public static void initSbString(){
        sb[0].append("1、生产存在,开发不存在的表:\r\n");
        sb[1].append("2、生产不存在,开发存在的表:\r\n");
        sb[2].append("3、生产存在,开发不存在的字段:\r\n");
        sb[3].append("4、生产不存在,开发存在的字段:\r\n");
        sb[4].append("5、表和字段都相同,但字段类型不同的内容:\r\n");
        sb[5].append("6、表和字段、字段类型都相同,但字段注解不同的内容:\r\n");
    }

    /**
     * 比较生产库和开发库的数据表,包括表名、字段名、字段类型、字段注解
     * @param product_dbSchema 生产库 名称
     * @param develop_dbSchema 开发库 名称
     * @throws Exception
     */
    public static void compareTables(String product_dbSchema,String develop_dbSchema) throws Exception {

        // 初始化SubString[]
        initSbString();

        // 生产数据库连接
        Connection connection_product = getTransaction_product();
        Map<String, Table> map_product = getTables(connection_product,product_dbSchema);

        // 开发数据库连接
        Connection connection_develop = getTransaction_develop();
        Map<String, Table> map_develop = getTables(connection_develop,develop_dbSchema);

        System.out.println("START----开始比对开发库");
        // 遍历开发库Map
        for (Iterator<String> iter_table = map_develop.keySet().iterator(); iter_table
                .hasNext();) {
            String key_table = (String) iter_table.next();
            Table table_develop = map_develop.get(key_table);// 获得开发库中的表
            Table table_product = map_product.get(key_table);// 尝试从生产库中获得同名表
            if (table_product == null) { // 如果获得表为空,说明开发存在,生产不存在
                append(table_develop, null, 2);
            } else { // 表相同,判断字段、字段类型、字段注解
                for (Iterator<String> column_develop = table_develop.column.keySet().iterator(); column_develop
                        .hasNext();) {
                    String key_column = (String) column_develop.next();
                    String value_develop = table_develop.column.get(key_column);// 获得开发库中的列
                    String value_column = table_product.column.get(key_column);// 尝试从生产库中获得同名列
                    if (value_column == null) {// 如果列名为空,说明开发存在,生产不存在
                        append(table_develop, key_column, 4);
                    } else {// 说明两者都存在
                        if (!value_column.equals(value_develop))// 字段类型不一致
                            append(table_develop, key_column, 5);

                        String comment_product = table_product.columnComment.get(key_column);// 获得生产库中的字段注解
                        String develop_product = table_develop.columnComment.get(key_column);// 尝试从开发库中获得同名字段注解
                        if (!comment_product.equals(develop_product))// 字段注解不一致
                            append(table_develop, key_column, 6);
                    }
                }
            }
        }
        System.out.println("END----结束比对开发库");

        System.out.println("START----开始比对生产库");
        // 遍历生产库Map
        for (Iterator<String> iter_table = map_product.keySet().iterator(); iter_table
                .hasNext();) {
            String key_table = (String) iter_table.next();
            Table table_product = map_product.get(key_table);// 从生产库中获得同名表
            Table table_develop = map_develop.get(key_table);// 尝试获得开发库中的表
            if (table_develop == null) { // 如果获得表为空,说明生产存在,开发不存在
                append(table_product, null, 1);
            } else { // 表相同,判断字段、字段类型、字段注解
                for (Iterator<String> column_product = table_product.getColumn().keySet().iterator(); column_product
                        .hasNext();) {
                    String key_column = (String) column_product.next();
                    String value_column = table_product.column.get(key_column);// 获得生产库中的列
                    String value_develop = table_develop.column.get(key_column);// 尝试从开发库中获得同名列
                    if (value_develop == null) {// 如果列名为空,说明生产存在,开发不存在
                        append(table_develop, key_column, 3);
                    }

                    // 字段相等 类型或者注解不相等上一步已经比对过,这里无需重复比对。
                }
            }
        }
        System.out.println("END----结束比对生产库");
    }

    /**
     * 封装TABLE数据
     * @param connection
     * @param tableSchema
     * @return
     * @throws Exception
     */
    public static Map<String, Table> getTables(Connection connection,String tableSchema)
            throws Exception {

        String sSql = "SELECT \n" +
                "  table_name AS tableName,\n" +
                "  GROUP_CONCAT(column_name) AS columnName,\n" +
                "  GROUP_CONCAT(column_type) AS columnType,\n" +
                "  GROUP_CONCAT(IF(column_comment=\"\",\"无\",column_comment)) AS columnComment\n" +
                "FROM\n" +
                "  information_schema.`COLUMNS` \n" +
                "WHERE table_schema = '"+tableSchema+"' \n" +
                "GROUP BY tableName\n" +
                "ORDER BY tableName;";

        Statement statement = connection.createStatement();//创建Statement对象
        ResultSet rs = statement.executeQuery(sSql);

        Map<String, Table> map = new HashMap<String, Table>();
        while (rs.next()) {
            Table table = new Table();
            HashMap<String,String> columnMap = new HashMap<>();
            HashMap<String,String> columnCommentMap = new HashMap<>();

                table.setTableName(rs.getString("tableName"));
                String columnName = rs.getString("columnName");
                String columnType = rs.getString("columnType");
                String columnComment = rs.getString("columnComment");
                String columnName_ [] = columnName.split(",");
                String columnType_ [] = columnType.split(",");
                String columnComment_ [] = columnComment.split(",");

                for(int i=0;i<columnName_.length;i++){
                    columnMap.put(columnName_[i],columnType_[i]);
                    columnCommentMap.put(columnName_[i],columnComment_[i]);
                }
                table.setColumn(columnMap);
                table.setColumnComment(columnCommentMap);
                map.put(rs.getString("tableName"), table);
        }
        if (rs!=null)
            rs.close();
        connection.close();
        return map;
    }

    /**
     * 封装提示信息 (追加到满足条件的StringBuffer)
     * @param table
     * @param column
     * @param flag
     * @throws Exception
     */
    public static void append(Table table, String column, int flag)
            throws Exception {
        switch (flag) {
            case 1:
                System.out.println("1、生产存在,开发不存在的表:" + table.getTableName()+ "\r\n");
                sb[0].append(table.getTableName() + "\r\n");
                break;
            case 2:
                System.out.println("2、生产不存在,开发存在的表:" + table.getTableName()+ "\r\n");
                sb[1].append(table.getTableName() + "\r\n");
                break;
            case 3:
                System.out.println("3、生产存在,开发不存在的字段:" + table.getTableName() + "["+ column+"]\r\n");
                sb[2].append(table.getTableName() + "["+ column+"]\r\n");
                break;
            case 4:
                System.out.println("4、生产不存在,开发存在的字段:" + table.getTableName() + "["+ column+"]\r\n");
                sb[3].append(table.getTableName() + "["+ column+"]\r\n");
                break;
            case 5:
                System.out.println("5、表和字段都相同,但字段类型不同的内容:" + "["+ column+"]["+table.column.get(column)+"]\r\n");
                sb[4].append(table.getTableName() + "["+ column+"]["+table.column.get(column)+"]\r\n");
                break;
            case 6:
                System.out.println("6、表和字段、字段类型都相同,但字段注解不同的内容:" + table.getTableName() + "["+ column+"]["+table.columnComment.get(column)+"]\r\n");
                sb[5].append(table.getTableName() + "["+ column+"]["+table.columnComment.get(column)+"]\r\n");
                break;
        }
    }

    /**
     * 写出比对结果到文件
     * 将StringBuffer中的值写入文件中
     * @throws Exception
     */
    public static void writeFile() throws Exception {
        // 合并输出到txt
        StringBuffer rs = new StringBuffer();
        rs.append(sb[0]+ "\r\n\r\n").append(sb[1]+ "\r\n\r\n").append(sb[2]+ "\r\n\r\n").append(sb[3]+ "\r\n\r\n").append(sb[4]+ "\r\n\r\n").append(sb[5]+ "\r\n\r\n");

        File file = new File("c://数据库比对结果.txt");
        OutputStream os = new FileOutputStream(file);
        os.write(rs.toString().getBytes());
        os.flush();
        os.close();
    }

    /**
     *  两个数据库比对步骤:
     *   1、修改 生产库 和 开发库 数据库连接地址
     *   2、main方法中传入 生产库 和 开发库 需要比对的数据库名称
     *   3、运行main方法
     *   4、生成的比对文件默认在 C:盘根目录
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        // 1、配置数据库
        // ~ 生产库
        product_driverClassName = "com.mysql.cj.jdbc.Driver";
        product_url = "jdbc:mysql://localhost:3306/db_test_product?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";
        product_username = "root";
        product_password = "123456";

        // ~ 开发库
        develop_driverClassName = "com.mysql.cj.jdbc.Driver";
        develop_url = "jdbc:mysql://localhost:3306/db_test_develop?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai";
        develop_username = "root";
        develop_password = "123456";

        // 2、传入生产库 和开发库 数据库名称
        compareTables("db_test_product","db_test_develop");

        // 3、 写入文件
        writeFile();
    }
}

执行结果:

运行结果:
在这里插入图片描述
生成的比对文件:
在这里插入图片描述
在这里插入图片描述

备注:

此工具类目前只适配Mysql数据库。其他数据库类型,可以自己按需修改下个别代码块。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐