本文源码:GitHub·点这里 || GitEE·点这里

一、数据同步简介

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<>();@Resourceprivate JdbcConfig jdbcConfig ;@PostConstructpublic 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 {@Resourceprivate 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 {@Resourceprivate 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 {@Resourceprivate 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&&currentPage<=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解决,因为复杂度高,不容易抽象化统一封装,如果数据同步这块业务,可以适配多种数据库,完全可以独立封装为中间件,开源项目中关于多方数据同步或计算的中间件也有好多,可以自行了解下,增长眼界开阔思路。

五、源代码地址

GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent

推荐相关阅读
数据源管理:主从库动态路由,AOP模式读写分离
数据源管理:基于JDBC模式,适配和管理动态数据源

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

  1. 数据源管理 | 关系型分库分表,列式库分布式计算

    本文源码:GitHub·点这里 || GitEE·点这里 一.数据拆分概念 1.场景描述 随着业务发展,数据量的越来越大,业务系统越来越复杂,拆分的概念逻辑就应运而生.数据层面的拆分,主要解决部分表数 ...

  2. oracle如何导出表结构及数据,PLSQL怎样导出oracle表结构和数据

    1.导出表结构和数据 方式1.tools->export user objects是导出表结构 tools ->export user object 选择选项,导出.sql文件 说明:导出 ...

  3. Sql表结构及数据对比工具

    1 目的与意义 现管理技术小团队做zw项目.在上线时经常遇到sql未执行的漏洞,导致线上环境反复出现测试环境未出现过的bug. 为解决以上问题,对于由于sql导致的环境迁移的bug,需从配置数据.表结 ...

  4. oracle点勾算提交吗,oracle表结构和数据导出时的一些勾选项说明

    使用pl/sql developer导出oracle数据库的表结构和表数据时,有一些勾选项供用户选择,需要用户根据实际情况进行勾选或取消. 导出方法如下: 一.只导出表结构 1.使用pl/sql de ...

  5. SQL Server表结构和数据导入到MySQL

    借助的工具:Navicat for MySQL,链接:http://pan.baidu.com/s/1kVCw8IF 密码:g927 可以很明确的肯定,主键和自增列是没办法导入的,只能是表结构和数据. ...

  6. mysql复制表结构和数据

    手动方法: 方法1:把你要复制的表当成一个文件一样,右键复制,右键粘贴,这个方法复制的是表结构和数据. 方法2:选中原表拖动到新位置的空白处,松开鼠标,会提示你复制表结构和数据,或者仅复制表结构,没有 ...

  7. mysql重新安装后之前表_关于重装mysql数据库之后.新的数据库引入旧数据的表结构和数据问题...

    由于种种问题,也许某一天数据库崩了,或者电脑崩了.重新装Mysql数据库的话:如何导入旧的数据库中的的表结构以及数据呢? 我是在重置电脑之后.进行重装数据库之后.遇到此问题.由于之前未遇到过此种问题. ...

  8. sql复制表结构和数据_SQL复制表

    sql复制表结构和数据 In real time we do face situations where we need to copy data from one table to another ...

  9. mysql命令导出表结构和数据_mysql-用命令导出、导入表结构或数据

    1. 导出整个数据库(表结构和数据) mysqldump -u用户名 -p  数据库名 > 导出的文件名 [root@localhost work]# mysqldump -uroot -p m ...

最新文章

  1. bash 脚本的自解压流程
  2. 易捷win10系统电脑卡在“正在关机”界面怎么处理?
  3. 对于五个问题的思考与感想
  4. 面向组合子程序设计方法 之 新约
  5. 转-测试用例-基本控件
  6. Java生鲜电商平台-微服务架构概述
  7. 为什么要用python不用origin_Python告诉你为什么百度已死
  8. 要想下班早,微服务架构少不了
  9. Linux下如何发现内存泄漏问题(测试角度)
  10. 动态规划-矩阵连乘问题
  11. 20181031-1
  12. 你们知道我们山东考生是怎么过来的么!山大校长写给你!
  13. 使用Dism++对电脑优化
  14. 一年代码功能点的创新性怎么写_创新项目计划书模板
  15. 苹果手机怎么查看已连接wifi密码_手机连不上wifi显示已保存怎么回事【原因介绍】...
  16. matlab RFID解码,UHF RFID编码之TPP编码
  17. 如何用html制作彩虹,javascript – 如何使用HTML5画布生成彩虹圈?
  18. 用python绘制树和深林
  19. 海量数据处理算法—Bloom Filter
  20. vue项目中引入Luckysheet

热门文章

  1. LeetCode:892. 三维形体的表面积
  2. 计算机网络之网络层:8、开放最短路径优先算法OSPF
  3. LeedCode篇:234. 回文链表
  4. 基础编程题之最大连续bit数(位运算)
  5. C++设计模式-Facade模式
  6. 103. 二叉树的锯齿形层次遍历/102. 二叉树的层序遍历
  7. 第九章 国际化、帮助系统和Qt插件
  8. office卸载工具、安装工具
  9. Linux 守护进程创建原理及简易方法
  10. Linux系统编程:循环创建N个子线程并顺序输出