场景描述
对于一个系统往往不止一个客户使用(注意:不是用户,是客户,例如A大学是一个客户、B大学是另一个客户),那么这时,对于系统中的数据结构(例如表设计、字段设计等等)往往是需要更改、完善的,那为了不一一去各个数据库执行相关sql,只想配置一次,从而做到各个数据库都可以更新到相关的数据结构,如何进行除了?
解决思路
首先,客户需要用到数据,一定是进入到系统的,而进入到系统,就一定会经过登入这一操作。
因此,可以通过设置代码全局静态版本号,与数据库存入的版本号进行比对,如果不一致,则进行数据更新,如果一致,则直接进行登入。(这样处理的一个好处就是,每一次的系统数据结构的更迭,只需要一个用户登入一次系统,触发一次数据更新即可)
解决方法
一般来说,对于检查数据结构的更改有:检查表是否存在、检查表字段是否存在、检查表字段是否升级(字段类型变化、长度变化、默认值变化、检查数据是否需要初始化)
此时需要注意:
1. 对于检查是否需要更新的数据结构需要配置在代码中; 1. 对于检测是否更新可以做一个标识,标识当前是否满足了数据结构,不满足则进行更新(例如:对于检测表结构,这个标识可以取最近最新添加的表进行配置,检测数据库中是否存在该表,如果不存在,则进入表升级配置里面进行表升级); 1. 在配置相关的sql时,可以统一进行一个底层方法的执行,这样就可以避免一个数据结构的更改创建一次连接执行一次sql了。
实现方式如下:
图3-1
代码实现举例:
//代码3.1检测版本->检测结构->得到返回的对象->进行sql执行而升级 //检测版本号从而判断是否需要升级 String updateVersion = iSelectSQLService.acquireUpdateVersion(dbName);//获取数据库存储的版本号 iUpdateLog.saveStep(maxLastUpdateIndex, dbName, "检查账套升级版本号和上次升级成功版本号是否一致?");//保存进度、日志 if (StringUtils.isNotEmpty(updateVersion) && updateVersion.equals(UpdateVersion.JavaUpdateVersion)) { sMsg = "账套数据库[" + request.getAccDBName() + "]升级版本号与上次一致, 无需升级"; iUpdateLog.saveStep(maxLastUpdateIndex, dbName, sMsg);//保存进度、日志 result.put("result", true); return success(sMsg, result); } sMsg = "升级版本号不一致,需要升级"; iUpdateLog.saveStep(maxLastUpdateIndex, dbName, sMsg);//保存进度、日志 //检测数据库是否需要升级 iUpdateLog.saveStep(maxLastUpdateIndex, dbName, "检测数据库是否需要升级?");//保存进度、日志 Boolean aBoolean = iAutoUpdateDB_acc.checkIsNeedUpdate_Acc(iUpdateLog, maxLastUpdateIndex, dbName);//检测表结构是否需要升级 统一进行相关检测 如下 3.2 if (aBoolean) {//进行表升级 sMsg = "账套数据库[" + request.getAccDBName() + "]需要升级"; iUpdateLog.saveStep(maxLastUpdateIndex, dbName, sMsg);//保存进度、日志 msgList.append(sMsg + "\n"); if (iAutoUpdateDB_acc.update_AccDB()) {//进入表升级sql配置中,获取sql进行处理 3.4 sMsg = "账套数据库[" + request.getAccDBName() + "]升级成功"; iUpdateLog.saveStep(maxLastUpdateIndex, dbName, sMsg); } else { sMsg = "账套数据库[" + request.getAccDBName() + "]升级失败!"; iUpdateLog.saveStep(maxLastUpdateIndex, dbName, sMsg);//保存进度、日志 aBoolean = false; } } else { sMsg = "账套数据库[" + request.getAccDBName() + "]无需升级!"; iUpdateLog.saveStep(maxLastUpdateIndex, dbName, sMsg);//保存进度、日志 }
//代码3.2 统一进行相关检测 public static boolean checkIsNeedUpdate_Acc(IUpdateLog iUpdateLog,int maxLastUpdateIndex,String dbName) throws UnsupportedEncodingException { //1 iUpdateLog.saveStep(maxLastUpdateIndex,dbName,"检测数据表是否需要升级?");//保存进度、日志 if (checkIsNeedUpdateTable_Acc()){//进入配置(最新的需要升级的表)检测是否要更新 3.3 iUpdateLog.saveStep(maxLastUpdateIndex,dbName,"数据表需要升级");//保存进度、日志 return true; }else{ iUpdateLog.saveStep(maxLastUpdateIndex,dbName,"数据表不需要升级");//保存进度、日志 } //2 iUpdateLog.saveStep(maxLastUpdateIndex,dbName,"检测表字段是否需要升级?"); if (checkIsNeedUpdateField_Acc()){ iUpdateLog.saveStep(maxLastUpdateIndex,dbName,"表字段需要升级"); return true; }else{ iUpdateLog.saveStep(maxLastUpdateIndex,dbName,"表字段不需要升级"); } . . . //3 表字段的长度需要升级 //4 表字段是否需要删除 //5 检测表字段类型是否需要升级 //6 检查数据是否需要更新 return false; }
//代码3.3 进入配置(最新的需要升级的表)检测是否要更新 /** * 检查数据库表是否存在 * * @return */ private static boolean checkIsNeedUpdateTable_Acc() { return !UOperateCheckDB.isSysObjExists("表1名字")//检测当前数据库是否存在该表 . . . || !UOperateCheckDB.isSysObjExists("表9名字") || !UOperateCheckDB.isSysObjExists("表10名字"); } //!!!一般配置10个当前系统最新的需要升级的表,即如果最新的都存在,则以前的需要升级的表就存在。这样就不用每一个表都去检测了。
//代码3.4 进入表升级sql配置中,获取sql进行处理 public Boolean update_AccDB() throws Exception { UpdateTable.updateAllTable();//表升级的统一配置类 对应代码 3.5 AlterField.updateAllField();//表更改的统一配置类 return true; }
//代码3.5 进行表升级sql配置 public static void updateAllTable() throws Exception{ UpdateTable2021.updateTable(); . . . UpdateTable2023.updateTable();//对应代码 3.5 } //此处又进行了一次区分,目的是为了将不同年度或者模块区分,因为检测底层是否需要升级表时,或将底层需要升级的表的配置都走一边,所以,当系统使用到一定程度,有些表结构已经全部更新到了所有客户数据库中,那么此时,在这里就可以就行注释或使用统一进行管理。 //与此同时,如果想所有更新表的sql一并更新,可以试试在此类中做处理
//代码3.5 如果是按年度进行区分,那么此类里面是按模块进行区分的,以便更好的进行管理 public static void updateTable(){ UpdateModelTable_A.updateTable();//该模块sql对应代码3.6 ... UpdateModelTable_G.updateTable(); }
//代码3.6 如果配置了更新表sql,都要在这里留下记录。 //代码逻辑是先检测是否,存在则在相对应的类的方法中获取到sql public static void updateTable() { if (!UOperateCheckDB.isSysObjExists("表1名")) { XXX.updateSQLTran(表1名所在类.updateTableSql());//获取sql(对应代码3.7),并进行相应的处理 } ... if(!UOperateCheckDB.isSysObjExists("表2名")){ XXX.updateSQLTran(表1名所在类.updateTableSql()); } }
//代码3.7 配置相关sql 创建以及更改表述 public static String updateTable(){ return "CREATE TABLE [表1名] (\n" + "[字段1] uniqueidentifier default newid() not null,\n" + "[字段2] money default 0 not null,\n" + "[字段3] int default 0 not null,\n" + "PRIMARY KEY CLUSTERED ([字段1])\n" + ")\n" + "EXEC sp_addextendedproperty 'MS_Description', '表描述...', 'SCHEMA', 'dbo', 'TABLE', '表1名'\n" + "EXEC sp_addextendedproperty 'MS_Description', '主键', 'SCHEMA', 'dbo', 'TABLE', '表1名', 'COLUMN','字段1'\n" + "EXEC sp_addextendedproperty 'MS_Description', '字段2描述', 'SCHEMA', 'dbo', 'TABLE', '表1名', 'COLUMN','字段2'\n" + "EXEC sp_addextendedproperty 'MS_Description', '字段3描述', 'SCHEMA', 'dbo', 'TABLE', '表1名', 'COLUMN','字段3'"; }
注意,以上代码并没有指明获取相关sql之后需要怎么去执行(看了代码应该是获取到一条sql执行一次),但是我个人的建议是,统一获取全部相关sql一并执行或者将sql存入记录表中然后之后统一取出再执行。
问题
-
万一对象内容出错,对象sql语句不完整如何处理?
如上代码所示,其实每进行一步,都会进行进度的保存、情况的保存、sql的保存,虽然不涉及sql的处理,但是在一定程度上会起到日志一样的作用。
因此,其实对于这个过程的执行,我个人是比较建议将对应的sql进行统一的封装或存储,然后进行统一的获取和执行。
-
可以先升级字段再升级表吗?
不能,有一种情况一定需要考虑。即如果进行字段升级时,对应的表不存在,则此时进行的字段sql一定会出错,因而,必须先进行表升级,继而进行字段升级。