在系统提供访问能力,数据库是必不可少
使用原生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 脚本创建的表试试水
添加一个用户信息到数据库
- 使用 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 获取参数的方法
-
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
根据用户 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
注意点
如果使用 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 源码或者文档