供稿人:张文秀
插件或应用更新后,经常需要修改老的数据或数据库结构、新增基础数据(如配置、数据字典)等,部署到服务器时,为了实现自动化升级,我们通常在 XxxxBootStrap.groovy 中实现数据的升级,但是,通过 BootStrap 升级存在下面的问题:
- 运行时间晚于 Grails 的数据库结构升级,因此,无法提前对数据进行预处理
- 升级代码需要写很多判断条件(如XX不存在、则新增XX),影响每次启动性能
- 升级代码容易变得过长,不易维护
- 升级代码的版本不好控制
- 如果中间升级失败,不易回滚、定位、重新执行
因此,在 BroFramework 中,新增了一种 Updater 的 Artefact,用于编写代码自动升级;并且将每次升级的插件名、应用名、版本号、升级成功与否等信息记录在数据表 brofwk_update_log 中。
以插件 BroBPM 为例,插件的 grails-app\conf\ 目录下有一个 update 文件夹,包下有多个前缀为 “Updater” 的 groovy 文件,分别是 UpdaterXXXInitiator 和 UpdaterXXX,升级的代码主要写在这些文件。
此外,子文件夹 resource 下面又存放了一些脚本,这些脚本会在 UpdaterXXXInitiator 和 UpdaterXXXX 中被调用执行;这些资源并不是必要的,而是根据情况自行添加、调用。
除了类名前缀外,这些升级器类的属性、方法规范如下面的模板所示:
class UpdaterXxxx {
def initiator = true/false
def version = "4.1.0.92940"
def pluginName = "broBpm"
def description = "......"
def beforeDbUpdate( Sql sqlInst, String dbName, String dbSchema ) {
....
}
def beforeDbUpdate_auditlog( Sql sqlInst, String dbName, String dbSchema ) {
....
}
def beforeBootStrap() {
....
}
def afterBootStrap() {
....
}
}
属性:
- initiator:是否为始化版本
每个应用或插件中只允许有一个 true 的升级器,类名我们通常命名为 UpdaterXXXInitiator,该升级器中主要是一些基础数据初始化代码,并且只是在应用第一次启动的时候执行一次,之后启动就不会再执行。 - version:升级器的版本号
格式为不超过四段的数字,如 w.x.y.z、x.y,前三段每段最多3个字符、最后一段最多10个字符,共22个字符。
目前版本号组成是插件自己的版本号+svn版本号,假如你做了一个新功能,在升级的时候需要初始化一个新的系统参数,比如BPM的版本是4.1.0,提交了代码之后查看svn版本是92940,此时UpdaterXXX版本号就要更新为 4.1.0. 92940。
版本号是-1 时则禁用,需要注意的是,如果修改了升级器类,则必须升级 version,否则新的升级代码在启动时不会被执行的。 - pluginName:插件名称
默认为 app_应用名,即不属于任何插件 - description:升级器描述
事件:
- beforeDbUpdate( Sql sqlInst, String dbName, String dbSchema )
在 Grails 自动更新数据库表结构前执行的。
此方法支持多数据源,beforeDbUpdate 表示更新默认数据源,beforeDbUpdate_auditlog 表示更新名为 auditlog 的数据源,这个是和dataSource中数据源的配置是类似的。
参数Sql sqlInst已初始化的数据库连接,用来执行sql;参数String dbName数据库名称,如数据库是oracle则这个参数的值是oracle,因为不同数据库的sql有差异,通过这个参数就可以判断不同数据库的时候执行不同的sql;参数String dbSchema数据库名,用于拼sql,如表名前缀:select * from ${dbSchema}.abc。 - beforeBootStrap()
在所有的 BootStrap 类和 bootStrapInit 方法之前执行 - afterBootStrap()
在所有的 BootStrap 类和 bootStrapInit 方法之前执行
下面是一个初始化升级器的示例:
package update
class UpdaterAppInitiator {
def initiator = true
def version = "1.0.0.1234"
def description = "初始化应用 Foobar,版本 1.0"
def settingService
def beforeBootStrap() {
settingService.create("foobar.foo.bar", ....).save()
....
}
}
下面是一个非初始化的升级器的示例:
package update
class UpdaterApp11 {
def version = "1.1.0.12345"
def description = "版本 1.0 到 1.1 的升级器"
def beforeDbUpdate( Sql sqlInst, String dbName, String dbSchema ) {
try {
if ( dbName == "oracle" ) {
sqlInst.executeUpdate("ALTER TABLE ...")
} else {
sqlInst.executeUpdate("ALTER TABLE ...")
}
} catch(Exception e) {}
}
def beforeDbUpdate_xxxx( Sql sqlInst, String dbName, String dbSchema ) {
....
}
def beforeBootStrap() {
if ( Xxxx.countByXX() == 0 ) {
new Xxxx(....).save()
}
if ( .... ) {
....
}
}
}