golang中xorm自动维护表结构自动导入数据的实现
Xorm简介
Go 标准库提供的数据库接口database/sql比较底层,使用它来操作数据库非常繁琐,而且容易出错。因而社区开源了不少第三方库,有各式各样的 ORM (Object Relational Mapping,对象关系映射库),如gorm和xorm。其中xorm是一个简单但强大的ORM 库,使用它可以大大简化我们的数据库操作,笔者尤其看中了其实现了从Struct到数据库表的正向工程,而且可以实现修改功能。
Xorm特性
- 支持Struct和数据库表之间的灵活映射,并支持自动同步
- 事务支持
- 同时支持原始SQL语句和ORM操作的混合执行
- 使用连写来简化调用
- 支持使用Id, In, Where, Limit, Join, Having, Table, SQL, Cols等函数和结构体等方式作为条件
- 支持级联加载Struct
- Schema支持(仅Postgres)
- 支持缓存
- 支持根据数据库自动生成xorm的结构体
- 支持记录版本(即乐观锁)
- 内置SQL Builder支持
- 通过EngineGroup支持读写分离和负载均衡
支持的数据库
- Mysql: github.com/go-sql-driver/mysql
- MyMysql: github.com/ziutek/mymysql/godrv
- Postgres: github.com/lib/pq
- Tidb: github.com/pingcap/tidb
- SQLite: github.com/mattn/go-sqlite3
- MsSql: github.com/denisenkom/go-mssqldb
- MsSql: github.com/lunny/godbc
- Oracle: github.com/mattn/go-oci8
- ql: github.com/cznic/ql
Xorm的表结构同步功能
- 根据表名,自动检测创建表。
- 根据字段名,自动检测和新增表中的字段,同时对表中多余的字段给出警告信息
- 根据索引的一个或多个字段名,自动检测,创建和删除索引和唯一索引。注意不是根据索引名。
- 自动转换varchar字段类型到文本字段类型
- 自动警告其它字段类型在模型和数据库之间不一致的情况。
- 自动警告字段的默认值,是否为空信息在模型和数据库之间不匹配的情况
注意:
- 模型名称和数据库表名,已经模型成员名和数据库表字段名,有着映射关系,用户可以设置和实现自己的映射方法
- 要看到上面描述中的这些警告信息需要将
engine.ShowWarn
设置为true
。
自动表结构同步及导入数据的实现
xorm提供的表结构同步方法
Sync2是Sync的升级版,但是按照官方说法,二者是一样的。我还是选用Sync2,用法很简单
err := engine.Sync2(new(User), new(Group))
自动同步的思路
定义好模型struct,具体参见Xorm官方文档。下面给个例子
// attachment.go
// 附件表
type Attachment struct {
ID int `xorm:"id serial pk not null" json:"id" toml:"id" form:"id"`
Name string `xorm:"'name' varchar(128) index(name) not null" json:"name" toml:"name" form:"name" header:"附件名"`
Filepath string `xorm:"'filepath' varchar(255) index not null" json:"filepath" toml:"filepath" form:"filepath" header:"文件路径"`
Filename string `xorm:"'filename' varchar(50) index not null" json:"filename" toml:"filename" form:"filename" header:"文件名"`
Oriname string `xorm:"'oriname' varchar(128) index(oriname) not null" json:"oriname" toml:"oriname" form:"oriname" header:"原文件名"`
Mime string `xorm:"varchar(255) 'mime' default('') not null" json:"mime" toml:"mime" form:"mime" header:"Mime类型"`
Size int `xorm:"int 'size' not null default(0)" json:"size" toml:"size" form:"size" header:"大小"`
Uri string `xorm:"varchar(255) 'uri' default('') not null" json:"uri" toml:"uri" form:"uri" header:"uri Path"`
Note string `xorm:"varchar(255) 'note' default('') not null" json:"note" toml:"note" form:"note" header:"备注"`
OwnerType string `xorm:"varchar(50) owner_type default('') index(object)" json:"owner_type" toml:"owner_type" form:"owner_type" header:"属主类型"`
OwnerID int `xorm:"int owner_id default(0) index(object)" json:"owner_id" toml:"owner_id" form:"owner_id" header:"属主ID"`
CreateAt time.Time `xorm:"'create_at' created" json:"create_at" toml:"create_at" form:"create_at"`
UpdateAt time.Time `xorm:"'update_at' updated" json:"update_at" toml:"update_at" form:"update_at"`
}
在模型包目录(我这里是models)中创建0.init.db(起这个文件名主要就是为了排序在最上面_), 里面定义模块初始化方法init, 方法中登记所有的模型Struct。
package models
import (
"errors"
"strings"
"golang.org/x/exp/slog"
)
var Tables map[string]any
var TableHeaders map[string]string
/**
* 登记数据表及其中文名信息
*/
func init() {
Tables = map[string]any{
"attachments": &Attachment{},
"authorizations": &Authorization{},
"contracts": &Contract{},
"customers": &Customer{},
"dict_genders": &DictGender{},
......
"users": &User{},
"users_roles": &UserRole{},
}
TableHeaders = map[string]string{
"attachments": "附近",
"authorizations": "授权信息",
"contracts": "合同",
"customers": "客户",
"dict_genders": "性别",
......
"users": "用户",
"users_roles": "用户角色",
}
}
数据库连接后,同步表结构
// 同步表结构?
func syncTables(db *xorm.EngineGroup) error {
var err error
keys := make([]string, 0, len(models.Tables))
for k := range models.Tables {
keys = append(keys, k)
}
for _, tn := range keys {
tblStru := models.Tables[tn]
slog.Info("开始同步表结构", "table", tn)
err = db.Engine.Sync2(tblStru)
if err != nil {
slog.Error("同步表结构错误!", err, "table", tn)
return err
}
}
return nil
}
必要时导入数据
数据文件以JSON格式保存
{"initData":
[
{"table":"dict_genders",
"data":[
{"name":"未知","code": "0"},
{"name":"男","code": "1"},
{"name":"女","code": "2"}
]},
{
"table":"roles",
"data":[
{"code":"admin","name":"管理员"},
{"code":"guest","name":"来宾"}
]
},
{
"table":"dict_nations",
"data":[
{"name":"汉族","code":"01"},
{"name":"壮族","code":"02"},
{"name":"回族","code":"03"},
{"name":"维吾尔族","code":"04"},
{"name":"彝族","code":"05"},
{"name":"苗族","code":"06"},
{"name":"满族","code":"07"},
{"name":"藏族","code":"08"},
{"name":"哈萨克族","code":"09"},
{"name":"白族","code":"10"},
{"name":"布依族","code":"11"},
{"name":"土家族","code":"12"},
{"name":"朝鲜族","code":"13"},
{"name":"蒙古族","code":"14"}
]
}
]
}
装填数据
func loadDefaultDatas(db *xorm.EngineGroup) error {
dataFile := "./data/init_data.json"
exists, err := utils.IsFileExist(dataFile)
if err != nil {
slog.Error("[装载数据]读取数据装载文件失败", err)
return err
}
if !exists {
slog.Info("[装载数据]未找到数据文件!")
return nil
}
buf, err := os.ReadFile(dataFile)
if err != nil {
slog.Error("[装载数据]读取数据装载文件失败", err)
return err
}
initDs := gjson.Get(string(buf), "initData").Array()
slog.Info("------------------")
slog.Info("[装载数据] start 执行默认数据装载...")
for _, td := range initDs {
//slog.Info(td)
tname := td.Get("table").String()
ds := td.Get("data").Array()
cc, err := db.Table(tname).Count()
if err == nil {
slog.Info("正在检查", "table", tname, "rowcount", cc)
if cc > 0 {
slog.Info("已有数据,跳过。")
}
blankInst := models.Tables[tname]
if cc == 0 && len(ds) > 0 {
slog.Info("没有数据,开始装填默认数据...")
ic := 0
for _, d := range ds {
inst, errD := utils.DeepClone(blankInst)
if errD != nil {
slog.Error("生成新结构体错误,", errD)
}
slog.Info(" going to unmarshal to inst=>", inst)
dataStr := d.String()
slog.Info("data row string=>%s", dataStr)
errU := json.Unmarshal([]byte(dataStr), &inst)
if errU != nil {
slog.Info("Unmarshal stru error: ", errU.Error(), inst)
}
_, err := db.InsertOne(inst)
if err != nil {
slog.Info("插入数据失败!", err.Error(), inst)
} else {
ic++
}
}
if ic > 0 {
slog.Info("共插入数据", "rows", ic)
}
}
} else {
slog.Info("counting table [", tname, "] rows error: ", err.Error())
return err
}
}
slog.Info("[装载数据] end 数据装载完成")
slog.Info("------------------")
return nil
}
总结
上面就是我所实现的Xorm自动同步表结构和自动装填数据的方式,主要代码都贴出来了。希望能够给需要的朋友提供一种思路。大家如果有好的方法还望不吝赐教。