GORM连接数据库实践 | 青训营

100 阅读6分钟

连接数据库

  Mysql

  • 导入gorm库和mysql驱动包
import (     
    "gorm.io/driver/mysql"     
    "gorm.io/gorm" 
)
  • 编写dsn连接数据库
dsn:="<user>:<password>@tcp(<ip such as 127.0.0.1>:<port such as 3306>)/<dbname>?charset=utf8mb4&parseTime=True&loc=Local" 
db,err:=gorm.Open(mysql.Open(dsn))

  Sqlite

  • 导入gorm库和sqlite驱动
import (     
    "gorm.io/driver/sqlite"     
    "gorm.io/gorm" 
)
  • 连接数据库
db,err:=gorm.Open(sqlite.Open("<DB filename such as hello.db>"))

  gorm包的路径比较简单,gorm用得也比较多,其包路径最好记住,gorm相关包都位于gorm.io,一般使用gorm的话就是gorm.io/gorm,需要使用自动生成工具就导入gorm.io/gen,需要数据库驱动就导入gorm.io/driver/<数据库名字>。

迁移模型

迁移模型的主要作用就是根据你编写的go结构体生成对应的数据库表结构。

下面是一个简单的例子,用于根据User结构体生成对应的数据库表users.为了简单和方便,我们使用sqlite数据库作为示范。

import (
    "fmt"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "time"
)

type User struct {
    ID       uint64
    Username string
    Password string
    CreateAt time.Time
    UpdateAt time.Time
}

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"))
    if err != nil {
       fmt.Println(err)
    }
    db.AutoMigrate(User{})
}

其中,AutoMigrate方法会进行模型的迁移。我们执行上面这段代码,会发现当前项目下会创建一个test.db的文件。

我们通过安装的sqlite3数据库打开这个文件。在确定操作系统上已经安装sqlite3数据库后执行以下命令打开数据库:

sqlite3 test.db

接着输入以下命令:

.tables;

可以看到以下输出:

我们发现gorm已经在sqlite中自动创建了一个名叫users的表,我们发现它给User结构体自动加了个s用于表示一个复数的数据库名。

我们接着输入以下命名:

.schema

可以看到以下输出:

我们发现gorm自动创建了一张users表,并且其字段和我们定义的结构体一样,并将我们的id作为了主键。

添加记录

前面我们已经创建了一个数据库,并且通过自动迁移的功能根据User结构体自动创建了一张users表,现在让我们来向数据库中添加一条记录吧!

我们在原来的主函数的末尾添加以下代码:

if tx:=db.Create(User{Username: "tom", Password: "123456"});tx.Error!=nil{
    fmt.Println(tx.Error)
}

运行它,我们发现报错了:

怎么回事呢?

原来是因为我们传给Create的User不是地址。

好的,我们修改下代码成下面的样子:

if tx := db.Create(&User{Username: "tom", Password: "123456"}); tx.Error != nil {
    fmt.Println(tx.Error)
}

接着运行它,我们发现没有什么问题,应该是成功了,我们在命令行窗口输入以下语句,查看下数据库记录:

记录添加成功了,我们修改下Create函数里面的内容在执行下,接着查看下数据库:

我们发现,当我们不指定ID的值时,它会进行自增。但是CreateAt和UpdateAt的零值不是我们想要的,怎么办呢?

我们尝试给sqlite添加一个触发器,在数据库层面完成数据的自动更新:

CREATE TRIGGER update_timestamp
    AFTER INSERT ON users
    FOR EACH ROW
BEGIN
    UPDATE users
    SET create_at= DATETIME('now')
    WHERE id = NEW.id;
END;

也可以在插入记录的时候指定CreateAt字段的值为time.Now(),或者你也可以给创建语句添加一个狗子,在创建成功后修改create_at和update_at的值,但是个人不太推荐,我们通过指定字段time.Now()值来添加它的时间,比如下面这样:

if tx := db.Create(&User{Username: "Thomas", Password: "7777", CreateAt: time.Now(), UpdateAt: time.Now()}); tx.Error != nil {
    fmt.Println(tx.Error)
}

我们发现,成功添加上了时间:

删除记录

上面我们已经学会了如何通过gorm往数据库中添加记录,接下来我们尝试删除前面添加的某条记录:

if tx:=db.Delete(User{},"id = ?",2);tx.Error!=nil{
    fmt.Println(tx.Error)
}

上面Delete语句第一个参数需要指定数据模型,对应数据库表,后面接需要删除的记录的筛选条件,这里我们删除id为2的记录。

我们查看下数据库:

发现第2条记录成功删除了。

查找记录

前面我们都是通过sql语句查找数据库的记录,接下来我们通过gorm代码来查找数据库记录。

var data []User
if tx := db.Find(&data); tx.Error != nil {
    fmt.Println(tx.Error)
}
fmt.Println(data)

我们发现,成功查找到了所有记录:

这里需要注意的是Find函数的第一个参数是一个地址,如果只是一个结构体的地址的话只会查找一条记录,如果是个切片的地址则会查找全部的记录,最后会将数据写入该地址。同时也能在Find的可选参数里面指定查询条件,这里省略了。

修改记录

下面我们尝试修改第一条记录中tom的密码位555.

if tx := db.Model(User{}).Where("id = ?", 1).Update("password", "555"); tx.Error != nil {
    fmt.Println(tx.Error)
}

我们发现,它似乎变复杂了点,首先我们需要调用Model方法指定需要修改的表,接着我们需要通过Where语句进行筛选,后面的Update语句,指定需要更新的column和其更新后的值。在这里Where语句不能省略,如果去掉,gorm会拒绝执行。

实际上,我们还可以通过另外一种更简单的方式来更新数据库,那就是通过Updates方法,该方法使用的时候需要指定User的id,它会根据id去自动查找记录,然后将其他字段替换成你的结构体中的字段的值。

if tx := db.Updates(User{ID: 4, Username: "zhangsna", Password: "9999"}); tx.Error != nil {
    fmt.Println(tx.Error)
}

比如上面的代码会将id为1的记录的其他字段替换成新的值,我们输出数据库表数据发现是这样的:

实际上,没有指定的CreatAt和UpdateAt都被忽略了,因为我们没有指定这两个字段值,则他们会被赋值为零值,而零值会被gorm忽略,在执行查询和更新操作时。

零值问题

使用结构体进行查询和更新,零值会被忽略,如果本身需要查询的值就是零值,则应该使用map进行查询和更新。

默认表名

gorm的默认表明是结构体的复数表示,但是你也可以指定表名,只需要给结构体添加一个TableNam()方法

func (u *User) TableName() string {
    return "users"
}

事务

我们可以通过以下的方式开启一个事务

if err:=db.Transaction(func(tx *gorm.DB) error {
    if err:=tx.Updates(User{ID: 1,Username: "lisi"}).Error;err!=nil{
       return err
    }

    if err:=tx.Updates(User{ID: 3,Password: "123"}).Error;err!=nil{
       return err
    }
    return nil
});err!=nil{{
    fmt.Println(err)
}}

其中,当最后返回的结果是nil的时候事务会成功生效,当返回了任意的error,则事务会失败,gorm会自动进行回滚,在这里特别需要注意的是,一定要使用tx而不是db.