数据源管理(五)

动态权限校验,表结构和数据迁移流程

原创 知了一笑 2020-04-14 07:20:00

一、数据同步简介

1、场景描述

如果经常接触数据开发,会有这样一个场景,服务A提供一个数据源,假设称为动态数据源A,需要读取该数据源下的数据;服务B提供一个数据源,假设称为动态数据源B,需要写入数据到该数据源。这个场景通常描述为数据同步,或者数据搬运。

2、基本流程

数据源管理 | 动态权限校验,表结构和数据迁移流程

 

基于上述流程图,整体步骤如下:

  • 测试多个数据源是否连接成功,并动态管理;
  • 判断数据源提供的账号是否有操作权限,例如读写;
  • 读取数据源A的表结构,在数据源B创建表;
  • 数据读取或者分页读取,写入数据源B中;
  • 在不知道表结构情况下,还需要读取表结构,生成SQL;

3、JDBC基础API

  • Statement

Java中JDBC下执行数据库操作的一个重要接口,在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。

  • PreparedStatement

继承Statement接口,且实现SQL预编译,可以提高批量处理效率。常应用于批量数据写入场景。

  • ResultSet

存储JDBC查询结果集的对象,ResultSet接口提供从当前行检索列值的方法。

二、基础工具封装

1、数据源管理

提供一个数据源管理的Factory,当前场景下主要管理一个读库即数据源A,和一个写库即数据源B,数据源连接验证通过,放入容器中。

@Component
public class ConnectionFactory {
    private volatile Map<String, Connection> connectionMap = new HashMap<>();
    @Resource
    private JdbcConfig jdbcConfig ;
    @PostConstruct
    public void init (){
        ConnectionEntity read = new ConnectionEntity(
        "MySql","jdbc:mysql://localhost:3306/data_read","user01","123");
        if (jdbcConfig.getConnection(read) != null){
            connectionMap.put(JdbcConstant.READ,jdbcConfig.getConnection(read));        }        ConnectionEntity write = new ConnectionEntity(
        "MySql","jdbc:mysql://localhost:3306/data_write","user01","123");
        if (jdbcConfig.getConnection(write) != null){
            connectionMap.put(JdbcConstant.WRITE,jdbcConfig.getConnection(write));        }    }    public Connection getByKey (final String key){
        return connectionMap.get(key) ;
    }}

2、动态SQL拼接

基础SQL管理

主要提供SQL的基础模板,例如全表查,分页查,表结构查询。

public class BaseSql {
    public static String READ_SQL = "SELECT * FROM %s LIMIT 1";
    public static String WRITE_SQL = "INSERT INTO %s (SELECT * FROM %s WHERE 1=0)" ;
    public static String CREATE_SQL = "SHOW CREATE TABLE %s" ;
    public static String SELECT_SQL = "SELECT * FROM %s" ;
    public static String COUNT_SQL = "SELECT COUNT(1) countNum FROM %s" ;
    public static String PAGE_SQL = "SELECT * FROM %s LIMIT %s,%s" ;
    public static String STRUCT_SQL (){
        StringBuffer sql = new StringBuffer() ;
        sql.append(" SELECT                     ");
        sql.append("     COLUMN_NAME,           ");
        sql.append("     IS_NULLABLE,           ");
        sql.append("     COLUMN_TYPE,           ");
        sql.append("     COLUMN_KEY,            ");
        sql.append("     COLUMN_COMMENT         ");
        sql.append(" FROM                       ");
        sql.append(" information_schema.COLUMNS ");
        sql.append(" WHERE                      ");
        sql.append(" table_schema = '%s'        ");
        sql.append(" AND table_name = '%s'      ");
        return String.valueOf(sql) ;
    }}

SQL参数拼接

根据SQL模板中缺失的参数,进行动态补全,生成完成SQL语句。

public class BuildSql {
    /**
     * 读权限SQL
     */
    public static String buildReadSql(String table) {
        String readSql = null ;
        if (StringUtils.isNotEmpty(table)){
            readSql = String.format(BaseSql.READ_SQL, table);
        }        return readSql;
    }    /**
     * 读权限SQL
     */
    public static String buildWriteSql(String table){
        String writeSql = null ;
        if (StringUtils.isNotEmpty(table)){
            writeSql = String.format(BaseSql.WRITE_SQL, table,table);
        }        return writeSql ;
    }    /**
     * 表创建SQL
     */
    public static String buildStructSql (String table){
        String structSql = null ;
        if (StringUtils.isNotEmpty(table)){
            structSql = String.format(BaseSql.CREATE_SQL, table);
        }        return structSql ;
    }    /**
     * 表结构SQL
     */
    public static String buildTableSql (String schema,String table){
        String structSql = null ;
        if (StringUtils.isNotEmpty(table)){
            structSql = String.format(BaseSql.STRUCT_SQL(), schema,table);
        }        return structSql ;
    }    /**
     * 全表查询SQL
     */
    public static String buildSelectSql (String table){
        String selectSql = null ;
        if (StringUtils.isNotEmpty(table)){
            selectSql = String.format(BaseSql.SELECT_SQL,table);
        }        return selectSql ;
    }    /**
     * 总数查询SQL
     */
    public static String buildCountSql (String table){
        String countSql = null ;
        if (StringUtils.isNotEmpty(table)){
            countSql = String.format(BaseSql.COUNT_SQL,table);
        }        return countSql ;
    }    /**
     * 分页查询SQL
     */
    public static String buildPageSql (String table,int offset,int size){
        String pageSql = null ;
        if (StringUtils.isNotEmpty(table)){
            pageSql = String.format(BaseSql.PAGE_SQL,table,offset,size);
        }        return pageSql ;
    }}

三、业务化流程

1、基础鉴权

读库尝试一次单条数据读取,写库尝试一次不成立条件的写入,如果没有权限,会抛出相应异常。

@RestController
public class CheckController {    @Resource    private ConnectionFactory connectionFactory ;    // MySQLSyntaxErrorException: SELECT command denied to user
    @GetMapping("/checkRead")
    public String checkRead (){        try {            String sql = BuildSql.buildReadSql("rw_read") ;
            ExecuteSqlUtil.query(connectionFactory.getByKey(JdbcConstant.READ),sql) ;            return "success" ;
        } catch (SQLException e) {            e.printStackTrace();        }        return "fail" ;
    }    // MySQLSyntaxErrorException: INSERT command denied to user    @GetMapping("/checkWrite")
    public String checkWrite (){        try {            String sql = BuildSql.buildWriteSql("rw_read") ;
            ExecuteSqlUtil.update(connectionFactory.getByKey(JdbcConstant.WRITE),sql) ;            return "success" ;
        } catch (SQLException e) {            e.printStackTrace();        }        return "fail" ;
    }}

2、同步表结构

这里执行最简单操作,把读库表创建语句查询出来,丢到写库中执行。

@RestController
public class StructController {
    @Resource
    private ConnectionFactory connectionFactory ;
    @GetMapping("/syncStruct")
    public String syncStruct (){
        try {
            String sql = BuildSql.buildStructSql("rw_read") ;
            ResultSet resultSet = ExecuteSqlUtil.query(connectionFactory.getByKey(JdbcConstant.READ),sql) ;            String createTableSql = null ;
            while (resultSet.next()){
                createTableSql = resultSet.getString("Create Table") ;
            }            if (StringUtils.isNotEmpty(createTableSql)){
                ExecuteSqlUtil.update(connectionFactory.getByKey(JdbcConstant.WRITE),createTableSql) ;            }            return "success" ;
        } catch (SQLException e) {
            e.printStackTrace();        }        return "fail" ;
    }}

3、同步表数据

读库的表数据读取,批量放入写库中。这里特别说一个方法:statement.setObject();在不知道参数个数和类型时,自动适配数据类型。

@RestController
public class DataSyncController {
    @Resource
    private ConnectionFactory connectionFactory ;
    @GetMapping("/dataSync")
    public List<RwReadEntity> dataSync (){
        List<RwReadEntity> rwReadEntities = new ArrayList<>() ;
        try {
            Connection readConnection = connectionFactory.getByKey(JdbcConstant.READ) ;            String sql = BuildSql.buildSelectSql("rw_read") ;
            ResultSet resultSet = ExecuteSqlUtil.query(readConnection,sql) ;            while (resultSet.next()){
                RwReadEntity rwReadEntity = new RwReadEntity() ;
                rwReadEntity.setId(resultSet.getInt("id"));
                rwReadEntity.setSign(resultSet.getString("sign"));
                rwReadEntities.add(rwReadEntity) ;            }            if (rwReadEntities.size() > 0){
                Connection writeConnection = connectionFactory.getByKey(JdbcConstant.WRITE) ;                writeConnection.setAutoCommit(false);
                PreparedStatement statement = writeConnection.prepareStatement("INSERT INTO rw_read VALUES(?,?)");
                // 基于动态获取列,和statement.setObject();自动适配数据类型
                for (int i = 0 ; i < rwReadEntities.size() ; i++){
                    RwReadEntity rwReadEntity = rwReadEntities.get(i) ;
                    statement.setInt(1,rwReadEntity.getId()) ;
                    statement.setString(2,rwReadEntity.getSign()) ;
                    statement.addBatch();
                    if (i>0 && i%2==0){
                        statement.executeBatch() ;
                    }
                }
                // 处理最后一批数据
                statement.executeBatch();
                writeConnection.commit();
            }
            return rwReadEntities ;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null ;
    }
}

4、分页查询

提供一个分页查询工具,在数据量大的情况下不能一次性读取大量的数据,避免资源占用过高。

public class PageUtilEntity {
    /**
     * 分页生成方法
     */
    public static PageHelperEntity<Object> pageResult (int total, int pageSize,int currentPage, List dataList){
        PageHelperEntity<Object> pageBean = new PageHelperEntity<Object>();
        // 总页数
        int totalPage = PageHelperEntity.countTotalPage(pageSize,total) ;
        // 分页列表
        List<Integer> pageList = PageHelperEntity.pageList(currentPage,pageSize,total) ;
        // 上一页
        int prevPage = 0 ;
        if (currentPage==1){
            prevPage = currentPage ;
        } else if (currentPage>1&¤tPage<=totalPage){
            prevPage = currentPage -1 ;
        }
        // 下一页
        int nextPage =0 ;
        if (totalPage==1){
            nextPage = currentPage ;
        } else if (currentPage<=totalPage-1){
            nextPage = currentPage+1 ;
        }
        pageBean.setDataList(dataList);
        pageBean.setTotal(total);
        pageBean.setPageSize(pageSize);
        pageBean.setCurrentPage(currentPage);
        pageBean.setTotalPage(totalPage);
        pageBean.setPageList(pageList);
        pageBean.setPrevPage(prevPage);
        pageBean.setNextPage(nextPage);
        pageBean.initjudge();
        return  pageBean ;
    }
}

四、最后总结

很多复杂度偏高的业务,越是需要借助基础API解决,因为复杂度高,不容易抽象化统一封装,如果数据同步这块业务,可以适配多种数据库,完全可以独立封装为中间件,开源项目中关于多方数据同步或计算的中间件也有好多,可以自行了解下,增长眼界开阔思路。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在配置mybatis-plus的数据源时,我们需要进行以下步骤: 第一步,首先我们需要在项目的配置文件(通常是application.properties或application.yml)中配置数据库连接信息。首先,我们需要指定数据库驱动,一般来说,如果使用的是MySQL数据库,驱动为com.mysql.cj.jdbc.Driver。然后,我们需要设置数据库的URL,格式为jdbc:mysql://主机名:端口号/数据库名?参数1=值1&参数2=值2...,其中主机名为数据库服务器的地址,端口号为数据库服务器的端口号,数据库名为要连接的数据库的名称,参数1、参数2等为可选的数据库连接参数,例如字符编码等。接下来,我们需要设置数据库的用户名和密码,分别对应着数据库连接时的用户名和密码。 第二步,接下来我们需要在Spring Boot的主启动类中添加对MyBatisPlus的注解@EnableTransactionManagement,这样可以让Spring Boot开启事务管理的支持。 第三步,我们需要创建一个数据源的Bean,并将其注入到MyBatisPlus的Configuration对象中。可以通过在配置文件中配置数据源,或者通过编程的方式创建数据源Bean。 第四步,我们需要在MyBatisPlus的配置类中配置数据库连接信息。通过调用MyBatisPlus的GlobalConfig对象的setDbConfig方法,可以设置数据库连接信息,包括数据库类型、数据库驱动、数据库连接URL、数据库用户名和密码等。 第步,最后我们需要在项目中编写Mapper接口和对应的Mapper.xml文件,通过MyBatisPlus提供的注解和工具类来实现数据库操作。 通过以上步骤,我们就可以完成对mybatis-plus的数据源配置工作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值