从本文开始,我将介绍一个数据库同步系统的设计方案,该系统是我在2010年给某软件公司做设计模式内训时指导几位开发人员所开发的一个项目,系统以某省级移动公司应急管理系统数据备份(数据库同步)需求为原型,基本需求如下:
为了在数据库发生故障的情况下不影响核心业务的运行,需要将生产数据库定期备份到应急数据库,以备生产数据库发生故障时,能切换到应急数据库,保证业务的正常运行。由于移动公司的数据量非常大,所以只需要对基础数据和关键数据进行备份,为了确保切换到应急数据库时保证核心业务能够运行,还需要备份整个数据库结构。
系统目前需求仅要求支持Oracle数据库的同步,但系统设计时需要考虑以后可以方便地支持其他数据库。Oracle数据库的结构由各种数据库对象组成,要求完成对各种数据库对象的同步,包括表(包括约束)、索引、触发器、分区表、视图、存储过程、函数、包、数据库连接、序列、物化视图和同义词。各类数据库对象的同步有一定的顺序关系,总体流程如图1所示:
图1 数据库同步流程图
数据库同步系统界面如图2所示
图2 数据库同步系统界面
用户在操作界面指定源数据库、目标数据库、控制数据库(用于读取配置信息)的数据库连接串,同时选取需要同步的数据库对象类型,对象类型存储在配置文件database_syn_config.xml中,通过输入SQL语句可以获取需要同步的表数据。
数据库对象同步的处理逻辑描述如下:
(1) 对于一般的数据库对象,同步时先取出源数据库与目标数据库该类数据库对象进行对比,然后将对象更新到目标数据库。
(2) 对于DBLink对象,由于数据库环境发生变化,需要手工调整,同步过程只记录新增的DBLink信息,而不执行创建操作。
(3) 表的同步处理由于其包含数据,因此较为特殊,需先对表结构变化进行分析,再同步数据。表数据的同步有三种方式:增量同步、先Delete后Insert方式、临时表方式。
(I) 增量同步。适用于可确定最后修改时间戳字段的情况。
(II) 先Delete后Insert方式。即先删除表的数据,再将源数据库的该表数据插入到目标数据库,为确保数据安全,要求在一个事务内完成。
(III) 临时表方式。用于最大限度保证数据的完整性,是一种在发生意外情况时,不丢失数据而使用的较为复杂的方式。
由于对数据库结构修改无法做事务回滚,因此如果后面的步骤发生异常,需要通过手工编码方式来实现目标数据库结构变化的回滚。
在本系统实现过程中使用了多种设计模式,下面对其进行简要分析(为了简化代码和类图,省略了关于包的描述,在实际应用中已将不同的类封装在不同包中):
1. 建造者模式
在本系统实现时提供了一个数据库同步流程管理器DBSynchronizeManager类,它用于负责控制数据库同步的具体执行步骤。用户在前台界面可以配置同步参数,程序运行时,需要根据这些参数来创建DBSynchronizeManager对象,创建完整DBSynchronizeManager对象的过程由类DBSynchronizeManagerBuilder负责,此时可以使用建造者模式来一步一步构造一个完整的复杂对象,类图如图3所示
图3 建造者模式实例类图
在图3中省略了抽象建造者,DBSynchronizeManagerDirector充当指挥者类,DBSynchronizeManagerBuilder充当建造者,DBSynchronizeManager充当复杂产品。
2. 简单工厂模式
DBSynchronizeManagerBuilder类的buildLife()方法可以创建一个初始的DBSynchronizeManager实例,再一步一步为其设置属性,为了保证在更换数据库时无须修改DBSynchronizeManagerBuilder类的源代码,在此处使用简单工厂模式进行设计,将数据库类型存储在配置文件中,如下片段代码所示:
…… <dbSynchronizeManager dbType="oracle" class="com. chinacreator.dbSyn.oracle.OracleDB SynchronizeManager"/> …… |
类图如图4所示:
图4 简单工厂模式实例类图
使用简单工厂模式设计的工厂类DBSynchronizeManagerFactory代码如下所示:
public class DBSynchronizeManagerFactory { public static DBSynchronizeManager factory(String dbType) throws Exception { String className = DBSynConfigParser.getSynchronizeManagerClass(dbType); return (DBSynchronizeManager)Class.forName(className).newInstance(); } }
|
其中DBSynConfigParser类用于读取配置文件,在图4中,DBSynchronizeManagerFactory类充当数据库同步流程管理器的简单工厂,DBSynchronizeManager是抽象产品,而OracleDBSynchronizeManager为具体产品。
3. 享元模式和单例模式
在数据库同步系统中,抽象类DBObjectSynchronizer表示需要同步的数据库对象,对于不同的数据库对象类型,提供了不同的子类实现,在数据库同步时可能有多个线程在同时进行同步工作,为了节省系统资源,可以使用享元模式来共享DBObjectSynchroizer对象,提供了享元工厂类DBObjectSynchronizerFlyweightFactory,且享元工厂类使用单例模式实现,类图如图5所示:
图5 享元模式和单例模式实例类图
在图5中,DBObjectSynchronizerFlyweightFactory充当数据库对象同步执行者的享元工厂,同步对象执行类DBObjectSynchronizer充当抽象享元,其间接子类OracleDBlinkDBSynchronizer、OracleTableDBSynchronizer等充当具体享元(由于篇幅问题,未将所有数据库对象类一一列出)。
在实现DBObjectSynchronizerFlyweightFactory时使用了单例模式(饿汉式单例),其代码片段如下所示:
public class DBObjectSynchronizerFlyweightFactory { private static DBObjectSynchronizerFlyweightFactory instance = new DBObjectSynchronizerFlyweightFactory(); private Map<String, DBObjectSynchronizer> map = new HashMap<String, DBObjectSynchronizer>(); private DBObjectSynchronizerFlyweightFactory(){ } public static DBObjectSynchronizerFlyweightFactory getInstance(){ return instance; …… }
|
4. 观察者模式
在数据库同步系统中,用户可以自行决定需要同步哪些对象,需要同步的DBObjectSynchronizer子类对象将注册到DBSynchronizeManager中,DBSynchronizeManager类的代码片段如下所示:
public abstract class DBSynchronizeManager{ …… public void attachDBSynchronizer(DBObjectSynchronizer dbSynchronizer) { synchronizers.add(dbSynchronizer); } public void detachDBSynchronizer(DBObjectSynchronizer dbSynchronizer) { synchronizers.remove(dbSynchronizer); } public abstract void executeSyn() throws Exception; }
|
其中attachDBSynchronizer(DBObjectSynchronizerdbSynchronizer)为注册方法,detachDBSynchronizer(DBObjectSynchronizerdbSynchronizer)为注销方法,executeSyn()为抽象的通知方法,其子类OracleDBSynchronizeManager为executeSyn()方法提供了具体实现,类图如图6所示:
图6 观察者模式实例类图
在数据库同步时,如果DBSynchronizeManager的executeSyn()方法被调用,将遍历观察者集合,调用每一个DBObjectSynchronizer对象的executeSyn()方法和compileDBObject()方法,此时DBSynchronizeManager充当抽象观察目标,OracleDBSynchronizeManager充当具体观察目标,DBObjectSynchronizer充当抽象观察者,OracleTableDBSynchronizer充当具体观察者。
5. 模板方法模式
在执行同步时,OracleDBSynchronizeManager的executeSyn()方法将依次调用synDBObject()和compileDBObject()方法,并在这两个方法中分别调用DBObjectSynchronizer的processSyn()和compile()方法,在OracleDBSynchronizeManager的子类中可以覆盖synDBObject()和compileDBObject()方法,如图7所示:
图7 模板方法模式实例类图
在图7中,OracleDBSynchronizeManager充当抽象父类,其中定义了模板方法executeSyn(),NewOracleDBSynchronizeManager充当具体子类,其中OracleDBSynchronize
Manager的代码片段如下所示:
public class OracleDBSynchronizeManager extends DBSynchronizeManager { public void executeSyn() throws Exception { synDBObject(); compileDBObject(); } protected void synDBObject(){ for (DBObjectSynchronizer dbSynchronizer : synchronizers) { try { dbSynchronizer.processSyn(this); } catch (Exception e) { e.printStackTrace(); } } } protected void compileDBObject(){ for (DBObjectSynchronizer dbSynchronizer : synchronizers) { try { dbSynchronizer.compile(this); } catch (Exception e) { e.printStackTrace(); } } } } |
由于Oracle数据库对象类型较多,而大部分对象的处理逻辑大同小异,只有少部分对象类型同步结构后需要重新编译,因此在设计DefaultOracleObjectSynchronizer类时也可以使用模板方法模式,在其中定义一个钩子方法getCompileable(),由子类决定是否要调用编译逻辑,代码片段如下所示:
public class DefaultOracleObjectSynchronizer extends DBObjectSynchronizer { ...... public void compile(DBSynchronizeManager dbSynchronizeManager) throws Exception { if (getCompileable()){ Database destDB = dbSynchronizeManager.getDestDB(); String[] compileObjs = findAllObjects(destDB); int iLen = compileObjs.length; for (int i = 0; i < iLen; i++) { compileObject(destDB, compileObjs[i]); } } } ...... } |
6. 策略模式
由于表数据的同步方式有三种,分别是增量同步、先Delete后Insert方式、临时表方式,因此可以定义一个同步策略接口DataSynStrategy,并提供三个具体实现类:IncSynStrategy、DelAndInsSynStrategy和TempTableSynStrategy。类图如图8所示:
图8 策略模式实例类图
在图8中,Oracle表同步对象类OracleTableDBSynchronizer充当环境类,DataSynStrategy充当抽象策略类,其子类IncSynStrategy、DelAndInsSynStrategy和TempTableSynStrategy充当具体策略类。
在OracleTableDBSynchronizer中将DataSynStrategy作为方法synSingleTable()的局部变量,因此OracleTableDBSynchronizer与DataSynStrategy为依赖关系,如果为全局变量,则为关联关系。
7. 组合模式、命令模式和职责链模式
在使用临时表方式实现表同步时可以定义一系列命令对象,这些命令封装对数据库的操作,由于有些操作修改了数据库结构,因此传统的JDBC事务控制起不到作用,需要自己实现操作失败后的回滚逻辑。此时可以使用命令模式进行设计,在设计时还可以提供宏命令MacroCommand,用于将一些具体执行数据库操作的命令组合起来,形成复合命令。类图如图9所示(由于原始类图比较复杂,考虑到可读性,图9进行了适当简化,不过简化了之后还是挺复杂的,):
图9 组合模式、命令模式和职责链模式实例类图
(由于涉及到多个模式的联用,此图有点点复杂,)
在图9中,TempTableSynCommand充当抽象命令,MacroCommand充当宏命令类,RenameTableCommand、SynTableDataCommand和RenameTableConstraintCommand充当具体命令,TempTableSynStrategy充当请求调用者,DataSynHelper充当请求接收者,在DataSynHelper中定义了辅助实现临时表方式同步的一些方法,在命令类中将调用这些方法。在TempTableSynCommand中声明了公共的execute()方法,并提供了回滚方法undo(),其子类实现具体的执行操作与恢复操作。DataSynHelper接口声明了进行数据库操作的方法,在其子类DataSynHelperImpl中实现了这些方法。
在TempTableSynCommand中还定义了两个子类型的变量previousCommand、nextCommnad用于保存前一个命令和后一个命令,其中nextCommnad用于在执行完当前命令的业务逻辑后,再执行下一个命令的业务逻辑;而previousCommand用于在出现异常时,调用上一个命令的undo()方法实现恢复操作。此时使用了职责链模式,nextCommnad.execute()实现正向职责链,而previousCommand.undo()加上Java的异常处理机制实现反向职责链。
MacroCommand是宏命令,其代码片段如下所示:
public class MacroCommand extends TempTableSynCommand { TempTableSynCommand lastCommand = this; public void add(TempTableSynCommand tempTableSynCommand) { tempTableSynCommand.setPreviousCommand(lastCommand); lastCommand = tempTableSynCommand; //创建命令链 } protected void execute() throws Exception { …… } protected void undo() throws Exception { …… } } |
在请求调用者类TempTableSynStrategy中通过如下代码片段来调用宏命令对象的execute()方法:
public class TempTableSynStrategy extends DataSynStrategy { public String processSyn() { //其他代码省略 String tempTableName = generateTempTableName(); String backupTableName = "BAK_" + tempTableName; DataSynHelper dataSynHelper = new DataSynHelperImpl(); MacroCommand marcoCommand = new MacroCommand(); marcoCommand.add(new RenameTableConstraintCommand(dataSynHelper, tableName, destDB)); marcoCommand.add(new SynTableDataCommand(dataSynHelper, tableName, tempTableName, srcDB, destDB)); marcoCommand.add(new RenameTableCommand(dataSynHelper, tableName, backupTableName, destDB)); marcoCommand.add(new RenameTableCommand(dataSynHelper, tempTableName, tableName, destDB)); try{ marcoCommand.execute(); try { //其他代码省略 } catch (Exception e) { e.printStackTrace(); } } catch (Exception e){ e.printStackTrace(); } //其他代码省略 } } |
|