实现一个数据库迁移的案子。有些知识点值得记录。
技术框架
github.com/go-xorm/xorm:数据库操作
github.com/denisenkom/go-mssqldb:sqlserver驱动
github.com/go-sql-driver/mysql:mysql驱动
方案设计
使用 sql 语句查询原数据库数据,再插入新数据库。
查询新数据库表最后一条记录。
根据条件是否创建新数据表,再查询新数据库最后一条记录的 ID 值,以此为起点查询旧数据库,因为迁移只需要从已导入的最后一条开始即可,如果表不存在,则从 0 开始。
使用回调函数,获取旧数据库,处理数据(或舍弃,或修改,等),再插入到新数据库,直接用 xorm 的结构体即可。xorm 可批量插入,但是旧数据库无法做到批量获取,查询并存储到切片中,到达一定数量(如3000条),再插入新数据库。
选择记录
原数据库为 sqlserver,表和列部分有中文,不符合 xorm 要求。只能使用 sql 语句操作。
新数据库为 mysql,全英文,可用 xorm 结构体映射,查询、插入较方便。
试过将 xorm 的结构体成员变量改为中文,在 Golang 中使用中文作为变量名是可以的,只是反射不成功。
实践过程及知识点
数据库连接
1 | // mysql |
注意,使用github.com/denisenkom/go-mssqldb
时,需要添加encrypt=disable;
,否则加不上。
xorm小知识
设置时区:
1 | engine.DatabaseTZ = time.UTC // time.Local |
是否显示 sql 语句:
1 | //engine.ShowSQL(true) |
xorm结构体映射
xorm 使用结构体与数据库字段映射,各种操作十分方便。
设置完全映射:
1 | engine.SetMapper(core.SameMapper{}) |
定义示例:
1 | type TheData struct { |
第一成员带 id,在数据库即为此名,第二成员不带,将映射为 Money。
结构体成员需大写,否则反射失败。
新数据库创建
一般创建表使用其它的方法,但为了方便使用,直接在代码中创建。
1 | var sqlstr string |
代码先判断是否存在数据表,不存在再调用engine.Sync2
创建,该函数可同时创建多个数据表。不过似乎没有直接的 API 接口判断是否存在数据表,只好用 sql 语句查询,判断其返回值。
数据库为空值
如果读取到数据库中的空值,会返回错误:
1 | Scan failed: sql: Scan error on column index 26, name "测试空值": converting driver.Value type string ("NULL") to a float32: invalid syntax |
此问题可用 sql 包提供的类型解决。如sql.NullString
、sql.NullFloat64
等。这些类型实际是结构体,包括了Valid
成员,通过该值可判断。
代码重用
由于迁移过程是完全一样的,只是数据库、表名、字段等不同。可以将共用部分抽象成函数,具体操作使用回调函数解决。
至于不同之处,则由调用者将其传递到共用函数中即可。
回调函数定义和使用
1 | type DataCb func (rows *sql.Rows, engine *xorm.Engine, totalCnt int64) int |
在回调函数中,根据旧数据库字段扫描,示例如:
1 | for rows.Next() { |
注意,Scan 参数个数和数据库的列数相同,否则出错。可直接用新数据库结构体成员,如果不需要,可以使用其它变量。
结构体映射
结构体用指针传递,这样可进行通用处理,即 MigrateDB 不再与具体结构体关联。
1 | // 获取新表结构体名称 |
功能有二:获取结构体名称,因为 sql 语句需要使用该名称。获取第 0 个字段名称,结合实际情况,数据库第 0 个字段为 ID,故设计上,将结构体第 0 个字段置为 ID。
注意,这里使用结构体指针形式传递,不需要额外声明结构体变量,代码较整洁。