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自動同步表結構和自動裝填數據的方式,主要代碼都貼出來了。希望能夠給需要的朋友提供一種思路。大家如果有好的方法還望不吝賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章