Gin 集成 xorm 快速使用、简化操作数据 | Go 主题月

3,571 阅读5分钟

在系统提供访问能力,数据库是必不可少

使用原生database\sql   操作还是有些复杂 Go 操作 Mysql 数据库| Go 主题月

在 go 语言中,有很多封装了对数据库操作

orm 框架 gorm 和 xorm 简化了对数据库操作

Gin 集成 xorm

xorm 可以操作很多类型数据库,Mysql、Postgres、Tidb、Oracle  ...

安装

go get xorm.io/xorm

// 安装 msyql
go get -u github.com/go-sql-driver/mysql

集成到 gin web 服务框架中

项目基于moo-go 基础上开发

思路

  • 创建 orm 对象

  • 在服务器时,就建立数据库连接,初始化 orm 对象

  • 对于数据库配置可以从配置文件中读取

开始上手操作

创建读取配置

moose.json

  • 数据库连接参数
{
  "app_name": "moose",
  "app_mode": "debug",
  "driver_name": "mysql",
  "username": "root",
  "password": "123456",
  "host": "127.0.0.1",
  "port": "3306",
  "database": "moose"
}
  • 封装解析参数结构体

AppInfo

package model

type AppInfo struct {
	AppName string `json:"app_name"`
	AppMode string `json:"app_mode"`
	DriverName string `json:"driver_name"`
	UserName string `json:"username"`
	Password string `json:"password"`
	Host string `json:"host"`
	Port string `json:"port"`
	DataBase string `json:"database"`
}
  • 封装一个解析 json 方法 ,在服务启动的时候从文件中读取
package util

import (
	"bufio"
	"encoding/json"
	"moose-go/model"
	"os"
)

var _cfg *model.AppInfo = nil

func GetConfig() *model.AppInfo {
	return _cfg
}

func ParseConfig(path string) (*model.AppInfo, error) {

	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}

	defer file.Close()

	reader := bufio.NewReader(file)
	decoder := json.NewDecoder(reader)

	if err = decoder.Decode(&_cfg); err != nil {
		return nil, err
	}
	return _cfg, nil
}

  创建初始化 orm

  • orm 提供创建方法 xorm.NewEngine
func xorm.NewEngine(driverName *string*, dataSourceName *string*) (*xorm.Engine, *error*)
  • 需要传入一个驱动名,和访问数据库地址,返回一个操作数据引擎对象(可以拿这个操作数据)
  • 保存这个对象 -- > 使用一个结构体来保存
   创建目录 engine -> orm_engine.go
package engine

import (
	"fmt"
	"moose-go/model"

	_ "github.com/go-sql-driver/mysql"

	"xorm.io/xorm"
)

var _dbEngine *Orm

type Orm struct {
	*xorm.Engine
}

func GetOrmEngine() *Orm {
	return _dbEngine
}

func NewOrmEngine(appInfo *model.AppInfo) (*xorm.Engine, error) {
	url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8", appInfo.UserName, appInfo.Password, appInfo.Host, appInfo.Port, appInfo.DataBase)
	engine, err := xorm.NewEngine(appInfo.DriverName, url)

	fmt.Println(appInfo)

	if err != nil {
		return nil, err
	}

	// 创建表
	// Sync2 synchronize structs to database tables
	err = engine.Sync2(new(model.UserInfo))
	if err != nil {
		return nil, err
	}

	orm := new(Orm)
	orm.Engine = engine
	_dbEngine = orm

	return engine, nil
}

服务启动初始化数据库连接

修改 main.go

  • 读取配置文件
  • 在创建 orm 对象时,传入解析配置文件对象结构体
...
	config, err := util.ParseConfig("./config/moose.json")
	if err != nil {
		log.Fatal(err.Error())
		return
	}

	_, err = engine.NewOrmEngine(config)
	if err != nil {
		log.Fatal(err.Error())
		return
	}

..,

启动服务验证

  • 能够读取到配置文件
  • 启动没有报错
➜  moose-go git:(master) ✗ go run main.go
&{moose debug mysql root 123456 127.0.0.1 3306 moose}
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /api/v1/user/list         --> moose-go/controller/v1.(*UserController).List-fm (3 handlers)
[GIN-debug] GET    /socket.io/*any           --> github.com/gin-gonic/gin.WrapH.func1 (3 handlers)
[GIN-debug] POST   /socket.io/*any           --> github.com/gin-gonic/gin.WrapH.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on 0.0.0.0:8090

验证

  • 使用 SQL 脚本创建的表试试水

Go 操作 Mysql 数据库| Go 主题月

添加一个用户信息到数据库

  • 使用 gin 提供一个接口,提交用户名,接收用户名,并保存

UserInfo 结构体

package model

type UserInfo struct {
	UserId      int64  `json:"userId" xorm:"user_id"`
	UserName    string `json:"userName" xorm:"username"`
	AccountId   int64  `json:"accountId" xorm:"account_id"`
	AccountName string `json:"accountName" xorm:"account_name"`
	Phone       string `json:"phone" xorm:"phone"`
	Gender      string `json:"gender" xorm:"gender"`
	Email       string `json:"email" xorm:"email"`
	Address     string `json:"address" xorm:"address"`
	Description string `json:"description" xorm:"description"`
	Avatar      string `json:"avatar" xorm:"avatar"`
	CreateTime  string `json:"createTime" xorm:"<- create_time"`
	UpdateTime  string `json:"updateTime" xorm:"<- update_time"`
}

UserController 定义接口

func (uc *UserController) RegisterRouter(engine *gin.Engine) {
	group := engine.Group("/api/v1/user")
	group.POST("/add", uc.Add)
}
func (uc *UserController) Add(c *gin.Context) {
	userName, _ := c.GetQuery("userName")
	userService := service.UserService{}
	row, err := userService.AddUser(userName)
	if err == nil && row > 0 {
		common.Success(c, 1, "添加用户成功")
		return
	}
	log.Fatal(err)
	common.Failed(c, "添加用户失败")
}
  • c.GetQuery("userName"), gin 获取提交 userName 参数

  • gin 获取参数的方法

    image-20210327151247599
  • web 遵循 MVC 原则,controller 访问控制视图 、service 处理业务逻辑、dao 数据访问(操作数据库)

    • 把 controller 获取到的参数..., 交给 UserService 处理
  • common.Failed \ common.Success 使用 gin 封装返回参数

    package common
    
    import (
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    )
    
    func Success(c *gin.Context, data interface{}, message string) {
    	c.JSON(http.StatusOK, gin.H{
    		"code":    http.StatusOK,
    		"data":    data,
    		"message": message,
    	})
    	c.Abort()
    }
    
    func Failed(c *gin.Context, message interface{}) {
    	c.JSON(http.StatusOK, gin.H{
    		"code":    http.StatusBadRequest,
    		"message": message,
    	})
    }
    
    

UserService

提供 AddUser 来处理添加用户逻辑
package service

import (
	"moose-go/dao"
	"moose-go/engine"
	"moose-go/model"
)

type UserService struct {
}

func (us *UserService) AddUser(userName string) (int64, error) {
	userInfo := model.UserInfo{
		UserName:    userName,
		UserId:      1,
		AccountId:   1,
		AccountName: "JiangJing",
		Gender:      "1",
		Phone:       "15798980298",
		Avatar:      "https://www.gitee.com/shizidada",
		Email:       "jiangjing@163,com",
		Address:     "中国",
		Description: "我是江景啊",
	}
	userDao := dao.UserDao{DbEngine: engine.GetOrmEngine()}
	return userDao.InsertUser(&userInfo)
}

UserDao

  • dao 持有操作数据库对象(DbEngine)
  • 调用 .DbEngine.InsertOne(userInfo) 插入一条数据

提供保存数据函数

package dao

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"moose-go/engine"
	"moose-go/model"
)

type UserDao struct {
	DbEngine *engine.Orm
}

// 添加用户
func (ud *UserDao) InsertUser(userInfo *model.UserInfo) (int64, error) {
	result, err := ud.DbEngine.InsertOne(userInfo)
	fmt.Println(result, err)
	if err != nil {
		return 0, nil
	}
	return result, err
}

启动服务访问

POST http://localhost:8090/api/v1/user/add?userName=Jiangjing

image-20210327153935098

image-20210327154022271

根据用户 id 查询用户

controller
group.GET("/get", uc.GetUser)
func (uc *UserController) GetUser(c *gin.Context) {
	userId := c.GetInt64("userId")
	userService := service.UserService{}
	common.Success(c, userService.GetUserByUserId(userId), "获取用户")
}
service
func (us *UserService) GetUserByUserId(userId int64) *model.UserInfo {
	userDao := dao.UserDao{DbEngine: engine.GetOrmEngine()}
	return userDao.QueryByUserId(userId)
}
dao
func (ud *UserDao) QueryByUserId(userId int64) *model.UserInfo {
	userInfo := model.UserInfo{
		UserId: userId,
	}
	has, err := ud.DbEngine.Get(&userInfo)
	if err != nil {
		log.Panicln(err)
		return nil
	}
	fmt.Println(has)
	return &userInfo
}

GET http://localhost:8090/api/v1/user/get?userId=1

image-20210327154335473

注意点

如果使用 xorm 自动同步表字段,使用 Sync2

err = engine.Sync2(new(model.UserInfo))
  if err != nil {
  return nil, err
}

UserInfo 结构体中需要定义字段映射规则

栗子:

type OrderInfo struct {
    Id     int64 `xorm:"pk autoincr notnull comment('订单id')"`
    Amount float64     `xorm:"float(8,2)"`
}
name当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。
pk是否是Primary Key,如果在一个struct中有多个字段都使用了此标记,则这多个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型,复合主键支持这7种Go的数据类型的组合。
autoincr是否是自增
[not ]null 或 notnull是否可以为空
unique或unique(uniquename)是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引
index或index(indexname)是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引
extends应用于一个匿名成员结构体或者非匿名成员结构体之上,表示此结构体的所有成员也映射到数据库中,extends可加载无限级
-将不进行字段映射
->只写入到数据库而不从数据库读取
<-只从数据库读取,而不写入到数据库
created在Insert时自动赋值为当前时间
updated在Insert或Update时自动赋值为当前时间
deleted在Delete时设置为当前时间,并且当前记录不删除
version在insert时默认为1,每次更新自动加1
default 0或default(0)设置默认值,紧跟的内容如果是Varchar等需要加上单引号
json表示内容将先转成Json格式,然后存储到数据库中,数据库中的字段类型可以为Text或者二进制
comment设置字段的注释(当前仅支持mysql)

Xorm 表前缀映射规则

xorm 默认使用结构体名字 + 下划线 UserInfo --> user_info

修改,在初始化 orm 添加

如果表面是手动创建的,带前缀,需要设置

prefix := names.NewPrefixMapper(names.SnakeMapper{}, "t_")
engine.SetTableMapper(prefix)

TODO

更多操作查看 xorm 源码或者文档

Demo 源码地址  moose-go