Archlinux与Kde桌面下使用GORM的实践记录 | 青训营

213 阅读6分钟

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.0localhost被当作两个不同的主机。尝试了%后,还是不行,没办法,改用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"})

有两点要注意:

  1. 如果没有查到,会返回ErrRecordNotFound。查不到的情况可能存在,所以必须要注意。
  2. 如果有零值,比如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
}
  • 一些坑

    1. Gorm使用链式调用来拼接SQL语句,但是如果执行了Update、Create、Where、Find、Save、Exec后,不能够再拼接条件语句。

    2. 要注意可能出现零值的地方。