【Go新手向】MySQL数据库基本CRUD

题图:本文作者拍摄于武汉·沙湖公园

Go 语言中有成熟的 ORM 库可以非常方便地进行数据库读写,各种 ORM 库的底层都是基于原生 SQL 语句来操作数据库,掌握原生 SQL 是根本。

下面来学习下 Go 语言如何使用 go-sql-driver/sql 结合原生 SQL 语句对 MySQL 数据库进行基本的 CRUD 操作。

数据库初始化

启动 MySQL 服务

使用 Docker 启动 MySQL 服务,前提是需要先安装 Docker

bash 复制代码
docker pull mysql:5.7 # 拉取 5.7 版本的 MySQL 镜像
docker run -itd --name mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql:5.7 # 启动 MySQL 容器实例

启动实例成功

这里设置的 MySQL 链接密码是:123456,默认用户名是:root,服务启动在 http://localhost:3306

创建数据库

通过 Navicat 等软件连接数据库服务,创建数据库、数据表。

连接数据库

新建数据库


数据库名就是数据库的名称,与后面代码连接数据库直接相关。

字符集、排序规则是 MySQL 处理和比较中非常重要组成部分,字符集直接决定了能够存储的字符范围 一般就选择最全的 utf8mb4

排序规则决定了字符的比较顺序,包括大小写敏感或不敏感、重音符号的处理等,不过多介绍,如图配置就行。

创建数据表

按照表设计规范定义通用字段:

  • id:自增 ID
  • del_flag:逻辑删除标记
  • reserved1:备用字段 1
  • reserved2:备用字段 2
  • reserved3:备用字段 3
  • create_time:创建时间
  • update_time:更新时间
  • remarks:备注

业务字段定义为如下:

  • mer_no:商户号
  • app_id:渠道号
  • app_name:渠道名称

数据库连接

编写 Go 代码连接数据库并创建连接实例。

安装 MySQL 数据库驱动依赖并注册

bash 复制代码
go get -u github.com/go-sql-driver/mysql

注册驱动

go 复制代码
import (
	_ "github.com/go-sql-driver/mysql"
)

连接数据库

go 复制代码
dsn := "root:123456@tcp(localhost:3306)/c2b_baffle?charset=utf8mb4&parseTime=True&loc=Local"
db, err := sql.Open("mysql", dsn)
if err != nil {
    log.Fatal(err)
}

这里 dsn 是数据库连接地址,完整的规范写法是这样的:[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN],其说明在这里查看

使用 Ping() 测试连接成功

CRUD 代码实现

通用代码

  • 连接数据库

    main 入口函数中连接数据库,数据库连接对象保存到 db 变量

go 复制代码
db, err := sql.Open("mysql", "root:123456@tcp(localhost:3306)/c2b_baffle?charset=utf8&parseTime=True&loc=Local")

if err != nil {
    panic(err)
}

db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)

err = db.Ping()

if err != nil {
    panic(err)
}
  • 定义结构体、类型描述上述数据表
go 复制代码
type AppItemAll struct {
	Id         uint      `json:"id"`
	MerNo      string    `json:"merNo"`
	AppId      string    `json:"appId"`
	AppName    string    `json:"appName"`
	Reserved1  string    `json:"reserved1"`
	Reserved2  string    `json:"reserved2"`
	Reserved3  string    `json:"reserved3"`
	CreateTime time.Time `json:"createTime"`
	UpdateTime time.Time `Json:"updateTime"`
	DelFlag    string    `json:"-"`
	Remarks    string    `json:"remarks"`
}
type AppItem struct {
	Id      uint   `json:"id"`
	MerNo   string `json:"merNo"`
	AppId   string `json:"appId"`
	AppName string `json:"appName"`
}
type AppList []AppItem
  • 启动 http 服务

删除分为物理删除逻辑删除

物理删除:通过 delete 语句删除 MySQL 数据记录;逻辑删除:标记 del_flag 为删除;批量删除是用来实现 SQL 语句的事务。

各方法的具体实现见下文。

新增记录

新增记录 SQL

sql 复制代码
INSERT INTO c2b_app_channel (mer_no, app_id, app_name, del_flag, create_time, update_time, remarks) VALUES (?,?,?,?,?,?,?)

Go 代码实现

go 复制代码
st, err := db.Prepare("INSERT INTO c2b_app_channel (mer_no, app_id, app_name, del_flag, create_time, update_time, remarks) VALUES (?,?,?,?,?,?,?)")

result, err := st.Exec(appItem.MerNo, appItem.AppId, appItem.AppName, appItem.DelFlag, appItem.CreateTime, appItem.UpdateTime, appItem.Remarks)
if err != nil {
    rw.WriteError(err)
    return
}

id, _ := result.LastInsertId()
appItem.Id = uint(id)

rw.WriteSuccess(appItem)

appItem 是从 POST 请求 body 中解析而来。

查询列表

查询列表 SQL

sql 复制代码
SELECT mer_no,app_id,app_name,id FROM c2b_app_channel WHERE del_flag != 1 ORDER BY id DESC

这里没有添加分页查询参数

Go 代码实现

go 复制代码
row, err := db.Query("SELECT mer_no,app_id,app_name,id FROM c2b_app_channel WHERE del_flag != 1 ORDER BY id DESC")
if err != nil {
    return nil, err
}
defer row.Close()

var list AppList

for row.Next() {
    var col1 string
    var col2 string
    var col3 string
    var col4 uint

    if err = row.Scan(&col1, &col2, &col3, &col4); err != nil {
        fmt.Println("scan fail", err)
        return nil, err
    }
    list = append(list, AppItem{
        Id:      col4,
        MerNO:   col1,
        AppId:   col2,
        AppName: col3,
    })
}

使用 db.Query 查询数据表,再使用 for row.Next() 遍历结果转换为 AppList 切片结构。

查询详情

查询详情 SQL

sql 复制代码
SELECT id,mer_no,app_id,app_name,remarks,create_time,update_time FROM c2b_app_channel WHERE id = ?

Go 代码实现

go 复制代码
st, err := db.Prepare("SELECT id,mer_no,app_id,app_name,remarks,create_time,update_time FROM c2b_app_channel WHERE id = ?")
if err != nil {
    rw.WriteError(err)
    defer st.Close()
    return
}

rows, err := st.Query(requestParam.Id)
if err != nil {
    rw.WriteError(err)
    return
}

var appItemAll = AppItemAll{}

var id uint
var merNo string
var appId string
var appName string
var remarks string
var createTime time.Time
var updateTime time.Time

for rows.Next() {
    rows.Scan(&id, &merNo, &appId, &appName, &remarks, &createTime, &updateTime)
    appItemAll.Id = id
    appItemAll.MerNo = merNo
    appItemAll.AppId = appId
    appItemAll.AppName = appName
    appItemAll.Remarks = remarks
    appItemAll.CreateTime = createTime
    appItemAll.UpdateTime = updateTime
}

rw.WriteSuccess(appItemAll)

使用 db.Query 查询数据表,再使用 for rows.Next() 遍历结果转换为 AppItemAll 结构体。

更新记录

更新记录 SQL

sql 复制代码
UPDATE c2b_app_channel SET mer_no = ?, app_id = ?, app_name = ?, remarks = ? where id = ?

Go 代码实现

go 复制代码
st, err := db.Prepare("UPDATE c2b_app_channel SET mer_no = ?, app_id = ?, app_name = ?, remarks = ? where id = ?")
if err != nil {
    rw.WriteError(err)
    return
}

result, err := st.Exec(requestParam.MerNo, requestParam.AppId, requestParam.AppName, requestParam.Remarks, requestParam.Id)
if err != nil {
    rw.WriteError(err)
    return
}
i, _ := result.RowsAffected()
if i != 1 {
    rw.WriteError(errors.New("更新失败"))
    return
}

rw.WriteSuccess(nil)

使用 st.Exec 执行 SQL,根据返回 result.RowsAffected 代表的影响记录数判断是否更新成功。

逻辑删除

逻辑删除 SQL

sql 复制代码
UPDATE c2b_app_channel SET del_flag = 1 WHERE id = ?

逻辑删除是通过更新 del_flag 标记为 1 实现

Go 代码实现

go 复制代码
st, err := db.Prepare("UPDATE c2b_app_channel SET del_flag = 1 WHERE id = ?")
if err != nil {
    rw.WriteError(err)
    defer st.Close()
    return
}

result, err := st.Exec(requestParam.Id)
if err != nil {
    rw.WriteError(err)
    return
}

affected, _ := result.RowsAffected()

if affected != 1 {
    rw.WriteError(errors.New("删除失败"))
    return
}

rw.WriteSuccess(nil)

使用 st.Exec 执行 SQL,根据返回 result.RowsAffected 代表的影响记录数判断是否逻辑删除成功。

物理删除

物理删除 SQL

sql 复制代码
DELETE FROM c2b_app_channel WHERE id = ?

使用主键 id 直接删除记录

Go 代码实现

go 复制代码
st, err := db.Prepare("DELETE FROM c2b_app_channel WHERE id = ?")
if err != nil {
    rw.WriteError(err)
    defer st.Close()
    return
}

result, err := st.Exec(requestParam.Id)
if err != nil {
    rw.WriteError(err)
    return
}

if affected, _ := result.RowsAffected(); affected > 1 {
    rw.WriteError(errors.New("删除失败"))
    return
}

rw.WriteSuccess(nil)

使用 st.Exec 执行 SQL,根据返回 result.RowsAffected 代表的影响记录数判断是否物理删除成功。

批量逻辑删除(事务)

批量逻辑删除 SQL

sql 复制代码
UPDATE c2b_app_channel SET del_flag = 1 WHERE id in [?,?,?]

这是 SQL 批量逻辑删除,并非事务

事务逻辑删除 SQL

sql 复制代码
BEGIN;
	UPDATE c2b_app_channel SET del_flag = 1 WHERE app_id = ?;
	UPDATE c2b_app_channel SET del_flag = 1 WHERE app_id = ?;
	UPDATE c2b_app_channel SET del_flag = 1 WHERE app_id = ?;
COMMIT;

执行结果如下:

通过 Go 代码实现事务

go 复制代码
ctx, err := db.Begin()
if err != nil {
    rw.WriteError(err)
    return
}

for _, id := range requestParam.Ids {
    result, err := ctx.Exec("UPDATE c2b_app_channel SET del_flag = 1 WHERE id = ?", id)
    if err != nil {
        ctx.Rollback()
        rw.WriteError(err)
        break
    }
    affected, err := result.RowsAffected()
    if err != nil || affected > 1 {
        ctx.Rollback()
        if err == nil {
            rw.WriteError(errors.New(fmt.Sprintf("删除记录 %d 失败", id)))
        } else {
            rw.WriteError(err)
        }
        break
    }
}

ctx.Commit()
rw.WriteSuccess(nil)
  1. 将 POST 请求参数解析为 requestParam ,然后对其 ids 属性进行遍历;
  2. 遍历开始前用 db.Begin() 开启事务
  3. 遍历每一步执行单条逻辑删除,若报错用 ctx.Roolback() 回滚事务并终止遍历
  4. 遍历完成用 ctx.Commit() 提交事务

使用事务可保证,要么同时执行成功,要么同时执行失败,不会存在部分成功。

总结

本文从零到一搭建了一个 MySQL 数据库,并实现了基本的数据库增删改查功能,同时结合业务实现了逻辑删除、事务操作的功能。

所有这些接口也都使用 HTTP 服务提供为接口服务,对 Go 新手上手 Web 服务端开发很友好,可是尝试练习一下。

文中项目完整代码在这里:github.com/JohnieXu/de...

参考资料

相关推荐
尚学教辅学习资料3 分钟前
基于SpringBoot的医药管理系统+LW示例参考
java·spring boot·后端·java毕业设计·医药管理
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
monkey_meng1 小时前
【Rust中的迭代器】
开发语言·后端·rust
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
余衫马1 小时前
Rust-Trait 特征编程
开发语言·后端·rust
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
monkey_meng1 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel