Gorm实践
这次使用Gorm还是遇到不少问题。我常年使用Archlinux,这些年我安装的软件包导致了一些比较特别的问题。
安装数据库
要使用Gorm,就得要一个数据库。我的操作系统是Archlinux,要啥装啥。可是却遇到一点问题。
sudo pacman -S mysql
[sudo] afeather 的密码:
正在解析依赖关系...
正在查找软件包冲突...
:: mysql 与 mariadb 有冲突。删除 mariadb 吗?
啥?mariadb?我没装过啊,删了!
sudo pacman -R mariadb
[sudo] afeather 的密码:
正在检查依赖关系...
错误:无法准备事务处理 (无法满足依赖关系)
:: 删除 mariadb 破坏依赖 'mariadb' (akonadi 需要)
有点不敢删了,于是调查了下akonadi是啥?网上给的是这个:
Akonadi 框架为应用程序提供中心数据库来统一保存、索引和获取用户的个人信息。
这包括邮件、联系人、日历、事件、日志、闹钟和笔记等。在 SC 4.4 中,
KAddressBook成为首个使用Akonadi框架的程序。在SC4.7中,KMail,
KOrganizer, KJots等也开始更新使用Akonadi。此外,一些plasma部件也使
用 Akonadi 保存和获取日历事件、笔记等。
所以,mariadb是kde5配置的一个数据库,它和mysql冲突。又去查了下mariadb,发现它是mysql的一个开源实现,功能相似。但是kde5是我的Linux桌面,mariadb实在不敢动,就继续用它吧。
使用数据库
在终端输入mysql后,提示了mysql: Deprecated program name. It will be removed in a future release, use '/usr/bin/mariadb' instead
,这意味着推荐的做法是改用mariadb来启动数据库服务端。输入一些简单的SQL试试功能:
Mariadb [(None)] show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| test |
+--------------------+
2 rows in set (0.000 sec)
Mariadb [(None)]
是Prompt,也就是提示我当前用的是哪个数据库的。显示None的原因是,我没有选择一个数据库。
安装Gorm与Mysql支持
在项目中执行命令:
go get gorm.io/gorm
go get gorm.io/driver/mysql
第一个命令的作用就是安装Gorm,第二个就是安装Mysql驱动。因为Gorm本身是对数据库的一个封装,它本身不是数据库,所以必须要装数据库驱动。
使用Gorm连接数据库
按照视频教程里的,写下如下代码
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Test struct {
ID int
Message string
}
func (self Test) TableName() string {
return "Test"
}
func main() {
dsn := "user:pwd@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
}
执行结果是:
[error] failed to initialize database, got error Error 1045 (28000): Access denied for user 'user'@'localhost' (using password: YES)
啥?没有权限?有没有可能本地套接字可以用?
执行了sudo netstat -anp | grep mysql
后,看到了这一行:
unix 3 [ ] STREAM CONNECTED 60973 8412/mysqld /run/user/1000/akonadi/mysql.socket
行,改成/run/user/1000/akonadi/mysql.socket
试试
[error] failed to initialize database, got error dial tcp: lookup /run/user/1000/akonadi/mysql.socket: no such host
好吧,提示我说denited for user
,有没有可能它还分用户的。一查就发现了,还真是这样的。看看有哪些用户:
MariaDB [(none)]> select user, host from mysql.user;
+-------------+-----------+
| User | Host |
+-------------+-----------+
| | localhost |
| mariadb.sys | localhost |
| mysql | localhost |
| root | localhost |
+-------------+-----------+
5 rows in set (0.001 sec)
没有用户,创建一个douyin@0.0.0.0。
MariaDB [(none)]> create USER 'douyin'@'0.0.0.0' IDENTIFIED BY '...';
又不行。一番折腾后发现,0.0.0.0
和localhost
被当作两个不同的主机。尝试了%
后,还是不行,没办法,改用localhost
。给新建的用户授权:
grant select, create, drop, insert, alter, delete, update on douyin.* to 'douyin'@'localhost';
再次执行,终于连接成功了,剩下的语句执行的也很顺利。
使用Gorm
数据库操作一般是增删改查,Gorm自然也能够执行这些操作。
-
自动建立表
AutoMigrate会根据传入的结构体创建表。如果表已经存在,则不会创建
type Test struct {
ID int
Message string
}
func (self Test) TableName() string {
return "Test"
}
db.AutoMigrate(&Test{})
但是值得注意的是,结构体内想要作为列名的,必须要首字母大写。如果有ID字段,它将会被当成主键。不过我们可以在后面加上一个字符串来告诉Gorm这个字段是什么。
-
增
使用Create即可。
test := Test{ID: 123, Message: "wdnmd"}
db.Create(&test)
但是有时候主键有冲突,可以使用Cluases来解决
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&test)
如果想不存在就创建,否则就更新,可以使用Save
db.Save(&test)
-
查
查询可以用First查询单个结果
// 查询符合条件的
db.First(&test, "Message = ?", "wdnmd")
// 查询某个主键
db.First(&test, 1)
// 使用结构体查询
db.First(&test, &Test{Message: "wdnmd"})
// 使用map查询
db.First(&test, map[string]interface{}{"Message": "wdnmd"})
有两点要注意:
- 如果没有查到,会返回ErrRecordNotFound。查不到的情况可能存在,所以必须要注意。
- 如果有零值,比如0、空字符串、false等,要使用map,或者第一种方法,因为Gorm会跳过空值。比如
db.First(&test, "Message = ?", "")
// 返回错误
db.First(&test, &Test{Message: ""})
// 相当于db.First(&test)
db.First(&test, map[string]interface{}{"Message": ""})
// 返回错误
也可以用Where().Find()来查询多个结果,这个更加常用。如果没查到,长度为0了。
recv := make([]*Test, 0)
db.Where("message = ?", "wdnmd").Find(&recv)
result := db.Where("message = ?", "wdnmd").Find(&recv)
fmt.Println("查询到", result.RowsAffected, "个结果,", "错误信息是", result.Error)
for _, item := range recv {
fmt.Println(item.ID, item.Message)
}
同样,也许要注意零值问题
- 改 虽然有Exec、Save,但是Gorm有其方法。 db.Model在GORM中相当于选择要操作的表,用于后续的增删改查语句。传入的参数是一个结构体指针。同时,它还会根据传入的结构体的值进行筛选。 选择表后,再执行Update(s)
db.Model(&Test{}).Where("id = ?", 1).Update("message", "awsl")
如果改成Updates,会更新所有id为1的
-
删
有软删除和硬删除的区别
- 硬删除
db.Delete(&User{}, "message = ? and id = ?", "wdnmd", 1)
db.Where("message = ? and id = ?", "wdnmd", 1).Delete(&User{})
- 软删除
由于软删除需要一个DeleteAt字段,所以需要在结构体中加上该字段。删除时使用的还是Delete(),其实现本质是Update
type Test struct {
ID int
Message string
DeleteAt gorm.DeleteAt
}
接下来进行删除就和硬删除一样了。默认情况下,进行查询时会跳过软删除后的表项,但如果需要查询软删除的部分,可以使用这样的语句:
db.Unscoped().Find(&Test) ...
-
Gorm事务
虽然有Begin方法可以直接创建事务,但是推荐的方法是使用Transaction,这是为了避免忘记Commit和Rollback。
if err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&Test{Message: "gorm"})); err != nil {
return err
// 自动执行Rollback
}
// 自动执行Commit
}); err != nil {
return
}
-
Gorm Hook
它是在创建、更新、查询、删除操作执行前、执行后的函数,如果它们返回失败,那么就会自动回滚。本质上是结构体的两个函数
func (t *Test) BeforeCreate(tx* gorm.DB) (err error) {
if t.Message == "" {
return errors.New("空字符串")
}
return
}
func (t *Test) BeforeCreate(tx* gorm.DB) (err error) {
return tx.Create(&Log{Message: "在*时间进行了*操作"}).Error
}
-
一些坑
-
Gorm使用链式调用来拼接SQL语句,但是如果执行了Update、Create、Where、Find、Save、Exec后,不能够再拼接条件语句。
-
要注意可能出现零值的地方。
-