做了几年开发,头一次把自己写的一套完整代码开源了,将我在公司中写的一套代码,去业务化,将其完全通用化(当时我设计程序结构时就是往这个方向做的),适配各种业务系统,主要功能用于同步数据库系统中的数据,采用的是通过SQL语句抽取数据,打包的方式进行,目前原版系统Synch2.0已经用于中国某政府部门的系统中,并已经全国推广上线,用于上级部门下发表结构、数据等至下级单位;我的这套Synch4j就是从Synch2.0中去业务化、通用化后演变而来的;后续我将更新更多细节信息在该文章中;
Synch4j的Github地址:https://github.com/YeLuoSC/synch4j
synch4j适用的业务场景:
1.适合传统行业中的业务系统,如有某个系统在多个省市均有各自的数据库实例,各个点之间有数据同步的需求;
2.间接的备份数据需求;
synch4j的一些优点:
1.在符合某些规范的前提下,支持对数据的增量导出;
2.基于回调,可以做定制开发,贴合实际业务,例如可以基于某套业务规则,动态导出一些表格数据
3.使用简便,通用仅需简单配置导出的表格信息,即可实现导出功能;
4.完美支持ORACLE的CLOB/BLOB值的导入导出;
5.易于扩展,基于面向对象的设计,扩展一种导出模式,只需实现3个方法即可;
synch4j需要遵守的规范:
1.数据库中需要导出的表格,必须有主键;
2.如果需要支持增量导出,那么该表中的每条数据都必须包含有时间戳的标识,当该数据发生变化时,及时更新该标识(可自行通过触发器实现)
1. 系统简介
Synch4j系统(以下简称“同步系统”)的前身为内外网同步系统,经过改写后形成了第一版的同步系统,后来由于业务变化繁杂,原先程序结构已经无法跟上业务变化速度,后经本人重构,完成了第二版的上下级同步系统,主要在程序结构等方面做了大范围的修改。使得第二版同步系统变得容易扩展,多数业务通过实现预定义的接口即可完成。而Synch4j是基于第二版同步系统去业务化及通用化而来的;
2. 系统功能
同步系统功能简单描述为导出当前数据库相关表的数据并打包压缩文件,通过某种形式发送至目标方,目标方接收后解压文件,再将数据导入目标方的数据库中;
3. 快速开发指南
3.1 包结构
本节只介绍一些扩展业务可能需要用到的一些接口,阅读本节后,应当可以对扩展的业务进行相应的代码开发工作。
同步系统2.0开发包在com.synch4j下,目前包结构如下:
annotation:注解(暂时未用)
callback:回调
dataimporter:数据导入器
datapicker:数据提取器
exception:封装的系统异常
execute:前台和后台交互的控制器等
exp:导出算法包
fascade:调用导入和导出的对外接口
imp:导入算法包
log:导入导出日志记录相关
po:封装数据对象
remote:远程调用的接口
resolver:解析器
secret:加解密器
sqlgenerator:sql语句生成器
synchenmu:同步系统枚举
test:测试包
util:工具包
zip:压缩工具包
3.2 导出及导入模式
在synchenmu包中,定义了ExportMode和ImportMode的枚举,因为业务比较繁杂,不同业务的下发规则(所下发的数据)不相同,在程序内部,通过该枚举值来判断当前处于哪种模式的导入或导出,尤其在回调实现中运用最多的,这些业务变化点已经抽象出接口了,在写实现的时候,就可以根据业务来对不同的模式进行特定的逻辑处理了。比如,现在有2种导出模式,第一种只导出A表,第二种模式只导出B表,现在新需求要求将第一种模式同时导出A表和B表,而第二种模式不需要改变,在我们的实现中,只要针对第一种模式进行判断即可(实际上可以理解为一个switch-case的结构分别处理)
目前同步系统包含以下几种模式:
1. 导出模式:标准模式导出
2. 导入模式:单线程导入模式;
3.3 回调接口
本节重点介绍callback包中的内容,callback中具体类及接口如下:
ü CallbackManager:回调管理器,回调机制的核心类,该类在spring启动时自动加载com.synch4j.callback包中的所有接口以及impl包中所有的实现类,加载完成后,可以通过管理器的几个方法获取实现类并调用,这些方法均在其他组件中,通过管理器回调了实现类,在我们写的接口实现类中,调用不到这些代码,所以相关内容在后续介绍;
ü 在介绍后续接口前,有一个概念需要提前说明,在一些组件中会根据实现类的返回值进行判断,比如if(接口A(参数1)),如果此时接口A有多个实现类分别返回true和false,那么明显就会有冲突,程序不知道应该以哪一个接口的实现类为准,所以为了避免这种情况的出现,我对某些接口的实现类进行了限制,有的接口只能有一个实现类,如上述情况中,在进行回调时,系统仅会获取一个实现类,当获取到了多个实现类会直接抛出相应的异常;
ü 接口列表:
序号 |
接口名称 |
允许多个实现 |
作用 |
返回值 |
1 |
ExportDbValueChangeProcessor |
是 |
导出时,可以改变导出数据值 |
String(修改后的字符) |
2 |
ExportDecideIncreaseTableProcessor |
否 |
导出时,判断当前表数据是否每次都导出 |
boolean(true,永久导出,false按照增量导出) |
4 |
ExportPostProcessor |
是 |
导出前及导出后回调该接口 |
Void(但可以通过参数修改其引用值) |
5 |
StandardExportPostProcessor |
是 |
标记接口,规范模式导出前及后调用 |
同上 |
6 |
NotStandardExportPostProcessor |
是 |
标记接口,非规范模式导出前及后调用 |
同上 |
7 |
ExportSqlChangeProcessor |
是 |
生成导出SQL时,回调接口 |
String(修改后的导出SQL语句) |
8 |
ImportDbProcessor |
是 |
导入器导入时,在3个时间点会进行回调 |
Void |
9 |
ImportDbValueChangeProcessor |
是(可能会改) |
导入时,可以修改导入的数据值 |
String(修改后的导入值) |
10 |
ImportPostProcessor |
是 |
导入前及完成后调用 |
void |
11 |
ImportResolveProcessor |
否 |
导入时,如果需要将导入表名修改为另一张表时回调 |
String(修改后的表名) |
12 |
ImportSqlGenerateProcessor(未开放) |
是 |
导入时如果需要修改导入SQL,实现该接口 |
String(修改后的导入SQL语句) |
|
|
|
|
|
关于各个接口的详细说明及方法签名,见代码中的注释,这里不再赘述了;
4 详细开发指南
4.1 导出
首先,我们需要先考虑清楚同步系统如何做的导出,理清思路后在继续展开后续的内容,就会顺理成章了;
导出数据,归根结底都是通过SQL语句将某些表的数据取到我们的内存中,之后通过压缩文件流将这些内存中的数据写入到一个文本文件中,最后流关闭,压缩文件生成完毕,这就是一个最简单的导出描述。那么,我们如何得知需要导出哪些表?如何通过这些表和业务规则生成相应的SQL语句?这里就引出了两个组件,导出解析器和导出SQL语句生成器。
导出解析器:包路径com.synch4j.resolver.exp,
IExportSynchPOResolver为导出解析器的接口,它的职责就是通过某种途径(如数据库,配置文件,XML等等)获取需要导出的表信息,这些需要导出的表信息被封装在SynchPO(称为“同步对象”)中,表信息中包括了物理表名,WHERE条件,是否包含大数据等一系列属性;
导出SQL语句生成器:包路径com.synch4j.sqlgenerator.exp,IExportSqlGenerator为导出SQL语句生成器的接口,它的职责是根据现有的同步对象集合,生成这张物理表的SQL语句;
现在我们已经有了SQL导出语句,下一步可以到数据库中进行查询提取数据了,而这一步提取工作,又交由了数据提取器进行处理;
数据提取器:包路径com.synch4j.datapicker,IDataPicker为其接口,职责是通过同步对象的属性获取数据,现在提供了一个数据库的实现,未来完全可以通过实现该接口从excel或txt中提取数据;
这样最简单的一条导出路径就显示了出来,导出解析器->导出SQL语句生成器->DB数据提取器。具体哪一个模式选择哪一款解析器、生成器、提取器,是在各个导出算法中,进行设置的,使得每一个模式都有可以自由搭配组件的能力,导出算法实际上就是各个导出模式的算法;每种导出模式都对应了一套导出算法;
导出算法:包路径com.synch4j.exp,基础接口:IBaseExportStrategy,针对目前使用最多的数据库导出,提供了一个抽象类作为模板,各个子模式通过继承抽象类的方式进行业务逻辑细节的开发,抽象模板类:AbsExportZipStrategy
实际上,在我的程序中,我是将SQL语句生成器通过组合的方式放到了数据提取器中,使之成为了导出解析器->DB数据提取器提取前获取SQL->SQL语句生成器->DB数据提取器通过生成的SQL提取,这是因为数据提取器未来可能会有多种扩展,数据库版的数据提取器才是应该和SQL语句生成器存在耦合关系的,否则如果有一日出现了excel版的数据提取器,拿到了SQL语句又有何用呢?
这里顺带一提我们系统的数据有一部分是按照增量形式导出的,也就是说如果这条数据在上一次已经导出过了,那么这一次导出将跳过这一条数据,去导出那些没有导出的数据。那么如果这一条导出过的数据,后来经过了修改,我们依然是需要导出的,否则导入方的数据还会是原始未修改的数据;
导出模式时序图:(简略)
如果需要扩展新的导出算法,通过实现IBaseExportStrategy接口可以实现,目前提供了一个抽象模板类AbsExportZipStrategy,其作为上述接口的默认抽象类,在该模板类中已经预制的处理了大部分的逻辑代码,比如写入同步对象信息至SynchInfo.txt文件,将其他业务数据写入至包中;继承这个抽象类仅需要实现3个方法即可完成一个新的导出算法扩展;
具体扩展方法如下:
1.实现setExportMode方法,实现该方法前,需要先在synchenum包中增加新算法的标识,作为后续回调等中,判断导出模式的标记;新增后,在该方法中直接返回这个新增的枚举标识即可;
2.实现prepareExportCallback方法,该方法在程序进行打包业务数据前调用,如果需要回调,就在该方法中通过回调管理器获取回调接口实现类的bean,直接调用bean的相关方法即可,如果不需要回调,空实现可以完成;
3.实现endExportCallback方法,该方法在数据打包完成后进行回调,一般处理一些收尾的工作;
根据以往的导出模式业务的变化来看,以上3种方法
prepareExportCallback方法是比较重要的,每种模式因为业务规则不同可能会有不同表格的导出需求,这时候需要结合回调来进行扩展,在实现相关回调接口之后,此处调用该回调就可以了。之前的一些扩展基本都是会影响到所有导出模式的,所以全部实现的都是一个接口ExportPostProcessor,需要注意的是该接口是一个通用的接口,此处“通用”意为所有导出模式都会回调;ExportPostProcessor下有2个子接口,分别是StandardExportPostProcessor和NotStandardExportPostProcessor,这两个接口分别对应“规范模式”和“非规范模式”下发的回调(非规范模式包括规范模式以外的所有模式,如oa-bgt下发,spf下发,bgt上报等等),这样做的目的是因为规范模式和非规范模式各有一些特殊处理;规范模式下发只会回调StandardExportPostProcessor接口的实现类,非规范模式下发只回调NotStandardExportPostProcessor接口的实现类;那么现在会带出一个新的问题,如果一个回调需要同时被规范和非规范模式同时调用,应当如何处理呢?目前有2个实现类可以供参考,它们分别是AllExportPostProcessorA1A2和AllExportPostProcessorRelationData,从实现类名称也可以看出是影响全部导出模式的回调,前者用于导出A1(浮动表)和A2(固定行列表)的模板数据,后者用于导出级联引用表数据,再次强调的是,实现该接口的实现类会被所有模式回调,(除上报模式以外,因为上报模式中prepareExportCallback方法为空实现,此处并没有回调接口);这些实现类同时实现2个接口StandardExportPostProcessor与NotStandardExportPostProcessor,因为这2个接口全部是ExportPostProcessor的子接口,说到底,它们其实只是用来做“标记”用的接口,类似mybatis中每个dao中都要继承SuperMapper一样;所以方法签名都是相同的,这样就解决了这个问题;
4.2 导入
导入的基础逻辑是从压缩包中读取并解析文件,生成导入SQL语句,执行SQL语句完成导入;对应的需要分离出一些组件
导入解析器:包路径com.synch4j.resolver.imp,
负责从压缩包中解析文件,并将所有数据插入到对应的中间表中,如将CLOB或BLOB存入P#SYNCH_T_BLOBCLOB表中,将其他数据库类型的数据存入P#SYNCH_T_DECRYDATA表中;还有比较特殊的一点,它会通过在导出时写入的同步对象信息文件(SynchInfo.txt)中逆向解析出一个同步对象集合,每个同步对象都有一些导入时必要的属性;
导入准备工作完成后,即将该这些数据保存至数据库中了,这时候需要一个数据导入器来完成此项工作;
数据导入器:包路径com.synch4j.dataimporter
数据导入器通过在解析阶段得到的同步对象集合以及中间表数据,可以知道需要多少张表和数据进行导入,数据导入器组件中,组合了一个导入SQL语句生成器,类似导出SQL语句生成器,生成导入SQL语句,导入器执行导入SQL完成其职责。
同步机制:目前系统的同步机制为将全部数据全部插入至“物理表名_S”的临时表中,之后再通过调用存储过程,将临时表数据导入到正式的表中。
顺序为:
1.执行导入SQL前回调,回调创建临时表;
2.插入业务数据至临时表(如果为CLOB,BLOB则自动替换为EMPTY_CLOB(),EMPTY_BLOB());
3.读取CLOB,BLOB数据更新至第2步插入的业务数据中;
4.调用DICT_SYNCHRONIZE_DATA存储过程,将临时表数据全部移至正式表中。(通过主键判断是否有数据,有则更新,无则插入)
TIPS:当前这种同步机制是无法控制住事务的,这个和代码没有关系,因为创建临时表是DDL语句,ORACLE中每次执行DDL语句都会进行隐式提交,每一次创建或删除一张临时表,上一次的DML语句已经提交了;
通过以上步骤,导入流程基本就介绍完成了。
下面配上导入模式的时序图:(简略)
通过观察时序图可以发现,大部分的业务变化点已经全部抽象为接口,从通用的代码中分离了出去,比如“修改导入表名”,“创建临时表”,“调用挪数据的存储过程”,还有一些未体现在时序图中的回调,比较重要的有“调用存储过程同步表结构”,“重建视图”,“添加分区”等等,未来如果需要修改,可以直接在相应的回调实现类中修改即可。倘若未来不需要通过使用临时表的机制进行同步,修改或删除相关的回调类,就可以轻松实现;
导入和导出的算法是对应的,只要导出算法是通过继承AbsExportZipStrategy模板类来实现的或者是按照与其一样的导出压缩包结构,导入算法就不需要对应修改。
目前导入算法按照单线程的形式进行导入。
类似的,导入导出的各个组件职责分工都是很明确的,每个组件对应只做一类事情,如果组件功能需要修改,也可以通过重写组件接口完成
4.3 数据库表及存储过程用处
l 数据库表
序号 |
物理表名 |
用处 |
1 |
P#SYNCH_T_SETTING |
导出表信息配置表 |
2 |
P#SYNCH_T_REMOTEPROCEDURE |
远程脚本代码信息表 |
3 |
P#SYNCH_T_MAINLOG |
导入导出主日志表 |
4 |
P#SYNCH_T_IMPORTLOG |
导入日志信息表 |
5 |
P#SYNCH_T_EXPORTLOG |
导出日志信息表 |
6 |
P#SYNCH_T_IMPORTSQL |
开发模式下,导入时记录导入SQL |
7 |
P#SYNCH_T_EXPORTSQL |
开发模式下,导出时记录导出SQL |
8 |
P#SYNCH_T_VIEWCODE |
下发视图信息表 |
9 |
P#SYNCH_T_BLOBCLOB |
导入时处理BLOBCLOB信息表 |
10 |
P#SYNCH_T_DECRYPTDATA |
导入时业务数据解密信息表 |
l 存储过程
序号 |
存储过程名 |
用处 |
1 |
DICT_SYNCHRONIZE_CREATE_TABLE |
创建临时表用的存储过程 |
2 |
DICT_SYNCHRONIZE_DATA |
临时表同步原表存储过程 |
3 |
DICT_SYNCHRONIZE_DEL_TABLE |
删除临时表用的存储过程 |