优秀的编程知识分享平台

网站首页 > 技术文章 正文

GORM使用——AutoMigration篇(go gorm xorm)

nanyue 2024-09-09 04:57:33 技术文章 7 ℃

GORM 是 Go 语言的 ORM 包,功能强大,调用方便。GORM 有两个版本,V1和V2。遵循用新不用旧的原则,推荐使用最新的 V2 版本。

本文主要介绍使用GORM的 Auto Migration包括创建数据库,数据表,字段,字段类型,索引等操作。使用Auto Migration功能可以使项目启动时自己完成数据库的初始化操作。避免运维频繁修改数据库。公司如果有DBA或者只有一个数据库的不建议使用此方式。

创建数据库

以mysql为例:

使用标准库sql执行sql进行数据库创建,该sql先判断了是否存在数据库,不存在时创建,存在时则跳过。

注意:要执行创建数据库操作,分配的mysql用户需要具有相应权限。

//获取mysql连接信息
var dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/", config.Config.Mysql.UserName,
		config.Config.Mysql.Password,
		config.Config.Mysql.Host,
		config.Config.Mysql.Port)
//创建数据库sql语句
createSql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", config.Config.Mysql.DBName)
	// 创建数据库
	if err = createDatabase(dsn, "mysql", createSql); err != nil {
		log.WithFields(log.Fields{"err": err.Error()}).Panic(DriverMysql + "数据库创建失败")
		return
	}
//创建数据库
func createDatabase(dsn string, driver string, createSql string) error {
	db, err := sql.Open(driver, dsn)
	if err != nil {
		return err
	}
	defer func(db *sql.DB) {
		err = db.Close()
		if err != nil {
			fmt.Println(err)
		}
	}(db)
	if err = db.Ping(); err != nil {
		return err
	}
	_, err = db.Exec(createSql)
	return err
}

连接数据库

使用上一步骤创建的数据库做mysql连接

//数据库驱动
	dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		config.Config.Mysql.UserName,
		config.Config.Mysql.Password,
		config.Config.Mysql.Host,
		config.Config.Mysql.Port,
		config.Config.Mysql.DBName,
	)
	db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.WithFields(log.Fields{"err": err.Error()}).Panic(DriverMysql + "数据库连接失败")
		return
	}

	// 获取通用数据库对象 sql.DB ,然后使用其提供的功能
	sqlDB, err := db.DB()
	if err != nil {
		log.WithFields(log.Fields{"err": err.Error()}).Panic(DriverMysql + "数据库连接失败")
		return
	}
	err = sqlDB.Ping()
	if err != nil {
		log.WithFields(log.Fields{"err": err.Error()}).Panic(DriverMysql + "数据库连接失败")
		return
	}

	// SetMaxIdleConns 设置MySQL的最大空闲连接数。
	sqlDB.SetMaxIdleConns(config.Config.Mysql.MinConns)
	// SetMaxOpenConns 设置MySQL的最大连接数。
	sqlDB.SetMaxOpenConns(config.Config.Mysql.MaxConns)
	// SetConnMaxLifetime 设置了连接可复用的最大时间。
	sqlDB.SetConnMaxLifetime(time.Second * 30)
	log.Info("init db success")

初始化表

首先定义一个 GORM 模型(Model),Model 是标准的 Go struct,用来代表数据库中的一个表结构。我们可以给 Model 添加 TableName 方法,来告诉 GORM 该 Model 映射到数据库中的哪张表。Model 定义如下:

type BaseModel struct {
	ID        uint           `gorm:"primaryKey" json:"id"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
	DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// Role 角色表
type Role struct {
	BaseModel
	Name   string `json:"name" gorm:"column:name;comment:名称;type:varchar(64);NOT NULL;"`
	Status int    `json:"status" form:"status" gorm:"column:status;default:1;comment:状态;type:tinyint(3);"`
}

func (Role) TableName() string {
	return "role"
}

BaseModel跟gorm.Model基本一致

注册表:

需要注意,如果需要使用事务,则设置db.Set("gorm:table_options", "ENGINE=InnoDB")。创建InnoDB类型的表,否则默认创建MyISAM引擎表,不支持事务。

func registerTables(db *gorm.DB) {
	err := db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(
		//系统模块表
		entity.Role{},
		entity.User{},
		entity.Config{},
		entity.SysLog{},
		//自动化模块表

	)
	if err != nil {
		log.WithFields(log.Fields{"err": err.Error()}).Panic("注册数据库表失败")
	}

}

GORM 的 AutoMigrate 方法,为了安全只对新增的字段或索引进行变更。后续添加字段只需要在Model结构体新写一个字段即可。每次程序启动时都会自动比对表结构,对新增的进行变更。

初始化表数据

有些数据需要在创建系统的时候就存在,好比说角色信息。可以在此处进行添加。大体步骤,先判断是否已存在数据,已存在则跳过;不存在则插入数据。

先声明一个初始化表数据接口,里面提供两个方法

type InitTable interface {
	DataInserted(*gorm.DB) bool          //表数据是否已经插入
	InitializeData(*gorm.DB) (err error) //初始化表数据
}

主函数:

func initTableData(db *gorm.DB) (err error) {

	var initTables []InitTable
	initTables = append(initTables,
		//此处添加需要初始化的实现表
		InitRole{},
		InitUser{},
		InitConfig{},
	)

	for _, init := range initTables {
		if init.DataInserted(db) {
			//表数据是否已存在
			continue
		}
		//初始化表数据
		if err = init.InitializeData(db); err != nil {
			return err
		}
	}
	//更新数据,目前只有一条,在此单独写入
	err = InitConfig{}.Update(db)
	return
}

每个需要初始化数据的表,单独实现InitTable接口。这里以Role表为例:

type InitRole struct {
}

// DataInserted
// @description: 数据是否已插入
// @param:
// @author: GJing
// @email: gjing1st@gmail.com
// @date: 2022/10/31 18:19
// @success:
func (i InitRole) DataInserted(db *gorm.DB) bool {
	if errors.Is(db.Where(entity.Role{}.TableName()+".id = ?", dict.RoleIdAdmin).First(&entity.Role{}).Error, gorm.ErrRecordNotFound) { // 判断是否存在数据
		return false
	}
	return true
}

// InitializeData
// @description: 初始化数据
// @param:
// @author: GJing
// @email: gjing1st@gmail.com
// @date: 2022/10/31 17:26
// @success:
func (i InitRole) InitializeData(db *gorm.DB) (err error) {
	if db == nil {
		return global.DBNullErr
	}
	entities := []entity.Role{
		{Name: "超管", BaseModel: entity.BaseModel{ID: dict.RoleIdSuperAdmin}},
		{Name: "管理员", BaseModel: entity.BaseModel{ID: dict.RoleIdAdmin}},
		{Name: "操作员", BaseModel: entity.BaseModel{ID: dict.RoleIdOperator}},
	}
	if err = db.Create(&entities).Error; err != nil {
		return global.InitDataErr
	}
	return
}


至此,对数据库的创建以及表和表结构的变动,全部由gorm完成。免去了部署和升级对数据库的运维。

创建复合唯一索引

有时我们需要创建唯一索引字段,但是用了DeletedAt软删除,再次添加相同的字段值时会报错唯一索引错误。

gorm提供跟DeletedAt组合的唯一索引,使用方式如下:

import (
	"gorm.io/plugin/soft_delete"
	"time"
)
// User 用户表
type User struct {
	ID            uint                  `gorm:"primaryKey" json:"id"`
	Name          string                `json:"name" gorm:"column:name;comment:用户名;uniqueIndex:user_name;type:varchar(64);NOT NULL;"`
	CreatedAt     time.Time             `json:"created_at"`
	UpdatedAt     time.Time             `json:"updated_at"`
	DeletedAt     soft_delete.DeletedAt `gorm:"uniqueIndex:user_name" json:"-"`
}

使用name用户名和DeletedAt作为唯一索引。此时DeletedAt是uint类型

GORM 官方文档

Tags:

最近发表
标签列表