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自动同步表结构和自动装填数据的方式,主要代码都贴出来了。希望能够给需要的朋友提供一种思路。大家如果有好的方法还望不吝赐教。

posted @ 2023-05-09 13:54  柒零壹  阅读(268)  评论(0编辑  收藏  举报