All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.wzc789376152.springboot.utils.DatabaseUtils Maven / Gradle / Ivy

The newest version!
package com.github.wzc789376152.springboot.utils;

import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.github.wzc789376152.springboot.config.SpringContextUtil;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.shardingsphere.driver.jdbc.core.datasource.ShardingSphereDataSource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class DatabaseUtils {
    private static JdbcTemplate jdbcTemplate;
    //查库名称
    private static final String schemaSql = "SELECT DATABASE()";
    //查表
    private static final String tableCountSql = "SELECT COUNT(1) FROM information_schema.TABLES WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';";

    //查索引
    private static final String statisticsSql = "select TABLE_SCHEMA,TABLE_NAME,INDEX_NAME,SEQ_IN_INDEX,COLUMN_NAME,NON_UNIQUE,INDEX_TYPE from information_schema.statistics where table_schema = '%s' and table_name = '%s';";

    private static final String columnSql = "select table_schema,table_name,column_name,column_type,column_comment,data_type,column_default,is_nullable,extra from information_schema.columns where table_schema = '%s' and table_name ='%s'";
    // 修改列的sql模版
    private static final String MODIFY_COLUMN = "ALTER TABLE %s.%s MODIFY COLUMN %s %s %s %s %s %s";
    // 新增列的sql模版
    private static final String ADD_COLUMN = "ALTER TABLE %s.%s ADD COLUMN %s %s %s %s %s %s";

    private static final String DELETE_COLUMN = "ALTER TABLE %s.%s DROP COLUMN %s";

    // 添加索引的模版(区分唯一/不唯一索引)
    // ALTER TABLE %s.%s ADD %s INDEX %s (列名)
    // ALTER TABLE %s.%s ADD PRIMARY KEY (id);
    // ALTER TABLE %s.%s ADD FULLTEXT xxx(NAME);
    private static final String ADD_INDEX = "ALTER TABLE %s.%s ADD %s %s (%s)";

    // 索引没有修改,只能先删除,再添加
    private static final String DROP_INDEX = "ALTER TABLE %s.%s DROP INDEX %s";

    @Data
    public static class ColumnDO {
        private String columnName;
        private String columnType;
        private String columnComment;
        private String dataType;
        private String columnDefault;
        private String isNullable;
        private String extra;
        // 自己添加的属性,这个列是否为新增(还是修改)
        private boolean isAdd;

        private boolean isDelete;
    }

    @Data
    @Builder
    public static class StatisticsDTO {
        private String indexName;
        private List columns;
        private int nonUnique;
        private String indexType;
    }


    @Data
    public static class StatisticsDO {
        private String indexName;
        private int seqInIndex;
        private String columnName;
        private int nonUnique;
        private String indexType;

    }

    public static JdbcTemplate getJdbcTemplate() {
        if (jdbcTemplate == null) {
            JdbcTemplate jdbcTemplate1 = SpringContextUtil.getBean(JdbcTemplate.class);
            if (jdbcTemplate1 != null) {
                DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextUtil.getBean(DynamicRoutingDataSource.class);
                DataSource dataSource = dynamicRoutingDataSource.getDataSource("master");
                if (dataSource instanceof ShardingSphereDataSource) {
                    PooledDataSource pooledDataSource = new PooledDataSource();
                    Environment environment = SpringContextUtil.getApplicationContext().getEnvironment();
                    String defultName = environment.getProperty("spring.shardingsphere.sharding.default-data-source-name");
                    String datasourcePropertyStr = "spring.shardingsphere.datasource." + defultName;
                    pooledDataSource.setUrl(environment.getProperty(datasourcePropertyStr + ".url"));
                    pooledDataSource.setUsername(environment.getProperty(datasourcePropertyStr + ".username"));
                    pooledDataSource.setPassword(environment.getProperty(datasourcePropertyStr + ".password"));
                    pooledDataSource.setDriver(environment.getProperty(datasourcePropertyStr + ".driver-class-name"));
                    jdbcTemplate = new JdbcTemplate(pooledDataSource);
                } else {
                    jdbcTemplate = jdbcTemplate1;
                }
            }
        }
        return jdbcTemplate;
    }

    /**
     * 同步表结构
     */
    public static void syncTable(String table, String sourceTable) {
        //查当前库名
        String schemaName = getJdbcTemplate().queryForObject(schemaSql, String.class);
        Integer tableCount = getJdbcTemplate().queryForObject(String.format(tableCountSql, schemaName, table), Integer.class);
        if (tableCount == null || tableCount == 0) {
            return;
        }
        Integer sourceTableCount = getJdbcTemplate().queryForObject(String.format(tableCountSql, schemaName, sourceTable), Integer.class);
        if (sourceTableCount == null || sourceTableCount == 0) {
            return;
        }
        log.info("开始同步表" + table);
        Date startTime = new Date();
        syncColumn(schemaName, table, sourceTable);
        syncStatistics(schemaName, table, sourceTable);
        log.info("结束同步表" + table + ",耗时:" + (new Date().getTime() - startTime.getTime()));
    }

    // 同步列
    private static void syncColumn(String schemaName, String table, String sourceTable) {
        // 1、获取,在src原 数据库实例下库的表的结构(字段+索引)
        List srcColumns = getJdbcTemplate().query(String.format(columnSql, schemaName, sourceTable), new BeanPropertyRowMapper<>(ColumnDO.class));
        // 2、获取,在dst目标 数据库实例下库的表的结构(字段+索引)
        List dstColumnDOS = getJdbcTemplate().query(String.format(columnSql, schemaName, table), new BeanPropertyRowMapper<>(ColumnDO.class));
        // 1与2 实现的功能是一摸一样的,只不过2这里又封装了一下。
        // 3、diff 差异
        List columnDOS = diffColumn(srcColumns, dstColumnDOS);
        // 4、基于差异,生成sql
        List sqls = generateSql(schemaName, table, columnDOS);
        for (String sql : sqls) {
            // 5、执行sql
            try {
                getJdbcTemplate().execute(sql);
            } catch (Exception e) {
                log.error("执行SQL异常:" + sql, e);
            }
        }
    }

    // 做差集,使用第三方包guava
    private static List diffColumn(List srcColumns, List dstColumnDOS) {
        // 1、区分列的是实体类,是新增的还是修改的
        // 如何判断是新增的:列的名字不一致,就是新增的

        // 将List转成Set集合,然后求差值
        Set diffColumns = Sets.difference(new HashSet<>(srcColumns), new HashSet<>(dstColumnDOS)).immutableCopy();

        // 将src 列的集合,每个实体的类的名字组合成一个集合
        Set srcNames = srcColumns.stream().map(ColumnDO::getColumnName).collect(Collectors.toSet());
        // 将src 列的集合,每个实体的类的名字组合成一个集合
        Set dstNames = dstColumnDOS.stream().map(ColumnDO::getColumnName).collect(Collectors.toSet());
        // 将src 列名字的集合与dst列名字的集合求差值.判断哪些列是新增的
        Set addNames = Sets.difference(new HashSet<>(srcNames), new HashSet<>(dstNames)).immutableCopy();
        Set deleteNames = Sets.difference(new HashSet<>(dstNames), new HashSet<>(srcNames)).immutableCopy();
        // 给Column 设置 isAdd。
        Set columnDOList = diffColumns.stream()
                .peek(columnDO -> {
                    if (addNames.contains(columnDO.getColumnName())) {
                        columnDO.setAdd(true);
                    }
                }).collect(Collectors.toSet());
        for (ColumnDO columnDO : dstColumnDOS) {
            if (deleteNames.contains(columnDO.getColumnName())) {
                columnDO.setDelete(true);
                columnDOList.add(columnDO);
            }
        }
        return new ArrayList<>(columnDOList);
    }

    private static List generateSql(String schemaName, String tableName, List columnDOS) {
        // ALTER TABLE Student MODIFY COLUMN id VARCHAR(32) NOT NULL DEFAULT "000" COMMENT '备注';
        // ALTER TABLE %s.%s MODIFY COLUMN %s %s %s %s %s;
        // 将ColumnDo 的list 转成 String 语句的list,转换型,使用map
        return columnDOS.stream()
                .map(columnDO -> {
//                    String sqlModel;
//                    if (columnDo.isAdd()){
//                        sqlModel = SqlModel.ADD_COLUMN;
//                    } else {
//                        sqlModel = SqlModel.MODIFY_COLUMN;
//                    }
                    if (columnDO.isDelete()) {
                        return String.format(DELETE_COLUMN, schemaName, tableName, columnDO.getColumnName());
                    } else {
                        return String.format(columnDO.isAdd() ? ADD_COLUMN : MODIFY_COLUMN,
                                schemaName, //库名
                                tableName, // 表名
                                columnDO.getColumnName(),// 列名
                                columnDO.getColumnType(),// 列的类型
                                nullableSet(columnDO.getIsNullable()),// 列是否为空
                                defaultSet(columnDO.getColumnDefault()),// 列的默认值设置
                                getExtra(columnDO.getExtra()),
                                commentSet(columnDO.getColumnComment())// 设置列的备注
                        );
                    }
                }).collect(Collectors.toList());
    }

    // 列是否为空的处理
    private static String nullableSet(String nullable) {
        if ("NO".equals(nullable)) {
            return "not null";
        }
        return "null";
    }

    // 列,默认值的处理,(可能为空,null,0)
    private static String defaultSet(String defaultValue) {
        if (Objects.isNull(defaultValue)) {
            return "";
        }
        return "DEFAULT " + defaultValue;
    }

    private static String getExtra(String extra) {
        return extra != null ? extra.replaceAll("DEFAULT_GENERATED", "") : "";
    }

    // 列,备注的处理
    private static String commentSet(String comment) {
        if (Objects.isNull(comment)) {
            return "";
        }
        return "COMMENT '" + comment + "'";
    }

    //
    private static String indexTypeSet(StatisticsDTO dto) {
        // 1、主键 index_name 为 PRIMARY
        // 2、唯一索引 unique = 0 并且 index_name != PRIMARY
        // 3、普通索引 unique = 1 并且 index_type = BTREE
        // 4、全文索引 unique = 1 并且 index_type = FULLTEXT

        if (("PRIMARY").equals(dto.getIndexName())) {
            return "PRIMARY KEY";
        }
        if (dto.getNonUnique() == 0) {
            return "UNIQUE";
        }
        if ("BTREE".equals(dto.getIndexType())) {
            return "INDEX";
        } else {
            return "FULLTEXT";
        }
    }

    // 同步索引
    private static void syncStatistics(String schemaName, String tableName, String sourceTable) {
        // 1、获取,在src原 数据库实例下库的表的结构(字段+索引)
        List srcStatisticDos = getJdbcTemplate().query(String.format(statisticsSql, schemaName, sourceTable), new BeanPropertyRowMapper<>(StatisticsDO.class));
        // 2、获取,在dst目标 数据库实例下库的表的结构(字段+索引)
        List dstStatisticDos = getJdbcTemplate().query(String.format(statisticsSql, schemaName, tableName), new BeanPropertyRowMapper<>(StatisticsDO.class));
        // 3、diff 差异
        Map> diffMap = diffStatistics(srcStatisticDos, dstStatisticDos);
        Map> diffMap1 = diffStatistics(dstStatisticDos, srcStatisticDos);

        // 4、基于差异,生成sql
        List addSqls = generateAddIndex(schemaName, tableName, diffMap.get(true));
        // 删除的索引
        List modifyDropSqls = generateDropIndex(schemaName, tableName, diffMap.get(false));
        List dropSqls = generateDropIndex(schemaName, tableName, diffMap1.get(true));
        if (!dropSqls.isEmpty()) {
            modifyDropSqls.addAll(dropSqls);
        }
        // 再增加
        List modifyCreateSqls = generateAddIndex(schemaName, tableName, diffMap.get(false));
        // 5、执行sql
        for (String sql : addSqls) {
            try {
                getJdbcTemplate().execute(sql);
            } catch (Exception e) {
                log.error("执行SQL异常:" + sql, e);
            }
        }
        for (String sql : modifyDropSqls) {
            try {
                getJdbcTemplate().execute(sql);
            } catch (Exception e) {
                log.error("执行SQL异常:" + sql, e);
            }
        }
        for (String sql : modifyCreateSqls) {
            try {
                getJdbcTemplate().execute(sql);
            } catch (Exception e) {
                log.error("执行SQL异常:" + sql, e);
            }
        }
    }

    // 生成新增索引的sql
    private static List generateAddIndex(String schemaName, String tableName, List statisticsDTOS) {
        // "ALTER TABLE %s.%s ADD %s INDEX %s (%s)";

        return statisticsDTOS.stream()
                .map(dto -> String.format(ADD_INDEX,
                        schemaName,
                        tableName,
                        indexTypeSet(dto),
                        dto.getIndexName().equals("PRIMARY") ? "" : dto.getIndexName(),
                        // dto.getColumns().stream().collect(Collectors.joining(","))
                        Joiner.on(",").join(dto.getColumns())
                ))
                .collect(Collectors.toList());

    }

    // 删除索引
    private static List generateDropIndex(String schemaName, String tableName, List statisticsDTOS) {
        return statisticsDTOS.stream()
                .map(statisticsDTO -> String.format(DROP_INDEX,
                        schemaName,
                        tableName,
                        statisticsDTO.getIndexName()))
                .collect(Collectors.toList());

    }

    // diff 索引的差异
    private static Map> diffStatistics(List srcStatisticDos, List dstStatisticDos) {
        // 将do 转成dto
        List srcDtos = fromStatisticsDOToDTO(srcStatisticDos);
        List dstDtos = fromStatisticsDOToDTO(dstStatisticDos);
        // diff,diffStatisticsDTOs 是包含新增与修改的,所有的DTO实例
        Set diffStatisticsDTOs = Sets.difference(new HashSet<>(srcDtos), new HashSet<>(dstDtos)).immutableCopy();
        // 区分哪些索引是新增的,哪些索引是变动的
        // 将src 源集合中的,IndexName所有索引名称,组成一个集合
        Set srcNames = srcDtos.stream().map(StatisticsDTO::getIndexName).collect(Collectors.toSet());
        // 将dst 源集合中的,IndexName所有索引名称,组成一个集合
        Set dstNames = dstDtos.stream().map(StatisticsDTO::getIndexName).collect(Collectors.toSet());
        // 在diffNames  中的实例,都是添加的。
        Set diffNames = srcNames.stream().filter(i -> !dstNames.contains(i)).collect(Collectors.toSet());

        // Map,为true则是所有新增的。为false,是所有修改的。
        return diffStatisticsDTOs.stream()
                // partitioning 分区。只能分两个区,true与false
                .collect(Collectors.partitioningBy(statisticsDTO -> diffNames.contains(statisticsDTO.getIndexName())));


    }

    // 将StatisticsDo 转成 StatisticsDTO
    private static List fromStatisticsDOToDTO(List statisticDos) {
        // 按照索引的名称进行分组 根据IndexName 对List 分组。因为联合索引的原因。
        // 联合索引,索引名称一样,但是对应的列和SeqInIndex的值不一样
        Map> dos = statisticDos.stream().collect(Collectors.groupingBy(StatisticsDO::getIndexName));

        return dos.values().stream()
                .map(statisticsDOS -> {
                    //
                    StatisticsDO sdo = statisticsDOS.get(0);
                    List columns = statisticsDOS.stream()
                            .sorted(Comparator.comparingInt(StatisticsDO::getSeqInIndex))
                            .map(StatisticsDO::getColumnName).collect(Collectors.toList());
                    return StatisticsDTO.builder()
                            .indexName(sdo.getIndexName())
                            .nonUnique(sdo.getNonUnique())
                            .indexType(sdo.getIndexType())
                            .columns(columns)
                            .build();
                })
                .collect(Collectors.toList());

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy