Golang使用 GORM连接数据库进行增删改查的实践实战 | 青训营

104 阅读8分钟

Gorm

在Go语言中,我们可以使用GORM库来简化数据库操作

Gorm 是一个已经迭代了10年+的功能强大的 ORM 框架,在字节内部被广泛使用并且拥有非常丰富的开源扩展。

Gorm引入

引入Gorm的包和引入Gorm的对应的数据库的驱动

go get -u gorm.io/gorm //Gorm的包
go get -u gorm.io/driver/mysql //数据库的驱动

用GORM连接数据库

连接数据库需要

DSN:"username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True"

GORM的数据库驱动

//连接MySQL数据库 
dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
db, err := gorm.Open( 
    mysql.Open(dsn), //MySQL的连接 
    &gorm.Config{}, //Gorm的上下文 
)
if err != nil { 
    return 
}

创建操作的数据库

创建数据库和数据库表

先创建库再创建表

CREATE DATABASE `gormtest`
CREATE TABLE `product` (
  `id` int(100) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `code` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `price` int(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

基本操作

Gorm进行操作数据库的基本操作实现,包含增加数据、删除数据、修改数据、查询数据

操作数据的表之前需要创建一个与表对应的结构体对象存储数据

package main 

import ( 
    "fmt" 
    "gorm.io/driver/mysql" 
    "gorm.io/gorm" 
) 

type Product struct { 
    Id int 
    Code string 
    Price int 
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 

//连接MySQL数据库 
dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
db, err := gorm.Open( mysql.Open(dsn), //MySQL的连接 
&gorm.Config{}, //Gorm的上下文 
) 

if err != nil { 
    return 
} 

//Create 
db.Create(&Product{Code: "200", Price: 200}) 

//Read 
var product Product 
db.First(&product, 1) 
db.First(&product, "id = ?", 2) 
fmt.Println(product) 

//Update 
db.Model(&product).Update("Price", 100) 
db.Model(&product).Updates(Product{Code: "500", Price: 10}) 
db.Model(&product).Updates(map[string]interface{}{"Code": "100", "Price": 50}) 

//Delete 
db.Delete(&product, 1) 

}

Gorm的约定(默认)

Gorm 使用名为 ID 的字段作为主键

使用结构体的 蛇形负数作为表名

字段名的蛇形作为列名

使用 CreatedAt、UpdatedAt 字段作为创建、更新时间

GORM 支持的数据库

GORM 目前支持 MySQL、SQLServer、PostgreSQL、SQLite

GORM 通过驱动来连接数据库,如果需要连接其它类型的数据库,可以复用/自行开发驱动。

创建数据

创建数据或者插入数据的详细的操作

type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 
    //连接MySQL数据库 
    dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
    db, err := gorm.Open( 
        mysql.Open(dsn), //MySQL的连接 
        &gorm.Config{}, //Gorm的上下文 
    ) 
    if err != nil { 
        return 
    } 
    //Create 
    res := db.Create(&Product{Code: "200", Price: 200}) 
    fmt.Println(res.Error) 
    //创建多条数据 
    pl := []*Product{{Code: "201", Price: 200}, {Code: "202", Price: 200}} 
    db.Create(pl) 
}

使用 clause.OnConflict 处理数据冲突

p := Product{Code: "200", Price: 100} 
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&p)

通过使用 default 标签为字段定义默认值

type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code ,default:200"` 
    Price int `gorm:"column:price,default:500"` 
}

查询数据

进行数据查询详细操作

type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 
    //连接MySQL数据库 
    dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
    db, err := gorm.Open( 
        mysql.Open(dsn), //MySQL的连接 
        &gorm.Config{}, //Gorm的上下文 
    ) 
    if err != nil { 
        return 
    } 
    
    //查询一条数据 
    p := Product{} 
    db.First(&p) 
    fmt.Println(p) 
    
    //查询多条数据 
    ps := make([]*Product, 0) 
    res := db.Where("code = ?", "200").Find(&ps) 
    fmt.Println(res.RowsAffected) 
    fmt.Println(res.Error) 
    fmt.Println(ps[0], ps[1]) 
    
    //in查询 
    db.Where("id in ?", []int{3, 4}).Find(&ps) 
    
    //like查询 
    db.Where("code LIKE ?", "%20%").Find(&ps) 
    
    //And的查询 
    db.Where("code=? And id=?", "%20%", 4).Find(&ps) 
    db.Where("code=?", "%20%").Where("id=?", 4).Find(&ps) 
    
    //使用对象(Int=0的时候当作空值) 
    db.Where(&Product{Id: 4, Code: "200"}).Find(&p) 
    
    //使用map来查询 
    db.Where(&map[string]interface{}{"id": 4, "code": "200"}).Find(&p)
}

First 的使用踩坑

使用 First 时,需要注意查询不到数据会返回 ErrRecordNotFound.使用 Find 查询多条数据,查询不到数据不会返回错误。

使用结构体作为查询条件

当使用结构作为条件查询时,GORM只会查询非零值字段。这意味着如果您的字段值为 0、"、false 或其他 零值该字段不会被用于构建查询条件,使用Map 来构建查询条件。

更新数据

进行数据更新详细操作

type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 
    //连接MySQL数据库 
    dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
    db, err := gorm.Open( 
        mysql.Open(dsn), //MySQL的连接 
        &gorm.Config{}, //Gorm的上下文 
    ) 
    if err != nil { 
        return 
    } 

    //条件更新单个列 
    db.Model(&Product{}).Where("code = ?", "201").Update("code", "500") 
    db.Model(&Product{Code: "200"}).Update("code", "500") 

    //更新多个列 
    //根据 struct更新属性,只会更新非零值的字段 
    db.Model(&Product{Code: "500"}).Updates(Product{Code: "100", Price: 600}) 

    //根据 map更新属性 
    db.Model(&Product{Code: "500"}).Updates(map[string]interface{}{"code": "100", "price": 600}) 

    //更新选定字段 
    db.Model(&Product{Code: "500"}).Select("code").Updates(map[string]interface{}{"code": "100", "price": 600}) 

    //SQL 表达式更新 
    db.Model(&Product{Code: "500"}).Update("code", gorm.Expr("code + ? * ? ", 10, 2))


}

使用 Struct 更新时,只会更新非零值,如果需要更新零值可以使用 Map 更新或使用Select 选择字段

删除数据

GORM 提供了 gorm.DeletedAt 用于帮助用户实现软删

拥有软删除能力的 Model 调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间,并且你不能再通过正常的查询方法找到该记录。

使用 Unscoped 可以查询到被软删的数据

物理删除

物理删除详细操作


type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 
    //连接MySQL数据库 
    dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
    db, err := gorm.Open( 
        mysql.Open(dsn), //MySQL的连接 
        &gorm.Config{}, //Gorm的上下文 
    ) 
    if err != nil { 
        return 
    } 


    //DELETE FROM users WHERE id = 10; 
    db.Delete(&Product{}, 4) 
    db.Delete(&Product{}, "4") 

    // DELETE FROM users WHERE d IN (1,2,3); 
    db.Delete(&Product{}, []int{4, 5, 6}) 

    // DELETE from users where code LIKE "%20%"; 
    db.Where("code LIKE ?", "%20%").Delete(&Product{}) 
    db.Delete(&Product{}, "code LIKE ?", "%20%")


}

软的删除

软的删除详细操作

不是实际删除只是设置一个属性的值为true标识虚拟删除

虚拟删除需要添加Deleted gorm.DeletedAt的属性才可以实现虚拟删除


type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
    Deleted gorm.DeletedAt
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 
    //连接MySQL数据库 
    dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
    db, err := gorm.Open( 
        mysql.Open(dsn), //MySQL的连接 
        &gorm.Config{}, //Gorm的上下文 
    ) 
    if err != nil { 
        return 
    } 


    // 删除一条 
    p := Product{Id: 4} db.Delete(&p) 

    // 批量删除 
    db.Where("code=?", "200").Delete(&Product{}) 

    //在查询时会忽略被软删除的记录 
    db.Where("code=?", "200").Find(&Product{}) 

    //在查询时不会忽略被软删除的记录 
    db.Unscoped().Where("code=?", "200").Find(&Product{})


}

事务

Gorm 提供了 Begin、Commit、 Rollback 方法用于使用事务

需要使用db生成的tx来操作数据库才可以进入事务


type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
    Deleted gorm.DeletedAt
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 
    //连接MySQL数据库 
    dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
    db, err := gorm.Open( 
        mysql.Open(dsn), //MySQL的连接 
        &gorm.Config{}, //Gorm的上下文 
    ) 
    if err != nil { 
        return 
    } 


    tx := db.Begin() 
    //开始事务 
    //在事务中执行一些 db 操作 (从这里开始,您应该使用tx’ 而不是db) 
    if err = tx.Create(&Product{Code: "200", Price: 60}).Error; err != nil { 
        //遇到错误时回滚事务 
        tx.Rollback() 
        return 
    } 
    if err = tx.Create(&Product{Code: "205", Price: 65}).Error; err != nil { 
        //遇到错误时回滚事务 
        tx.Rollback() 
        return 
    } 
    //提交事务 
    tx.Commit() 
    return


}

Gorm 提供了 Tansaction 方法用于自动提交事务,避免用户漏写 Commit、Rollbcak

防止错写漏写Tansaction只需要返回err就可以进行回滚操作返回nil就可以进行提交操作


type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
    Deleted gorm.DeletedAt
} 

func (p *Product) TableName() string { 
    return "product" 
} 

func main() { 
    //连接MySQL数据库 
    dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
    db, err := gorm.Open( 
        mysql.Open(dsn), //MySQL的连接 
        &gorm.Config{}, //Gorm的上下文 
    ) 
    if err != nil { 
        return 
    } 


    if err := db.Transaction(func(tx *gorm.DB) error { 
        if err = tx.Create(&Product{Code: "200", Price: 60}).Error; err != nil { 
            return err 
        } 
        if err = tx.Create(&Product{Code: "205", Price: 65}).Error; err != nil { 
            //遇到错误时回滚事务 
            tx.Rollback() 
            return err 
        } 
        return nil 
    }); err != nil { 
        return 
    }


}

Hook

数据库表操作的拦截器绑定在需要操作的数据表映射的结构体对象的身上

GORM 在 提供了 CURD 的 Hook 能力。

删除等操Hook 是在创建、查询、更新作之前、之后自动调用的函数

如果任何 Hook 返回错误,GORM 将停止后续的操作并回滚事务


type Product struct { 
    Id int `gorm:"primaryKey"` 
    Code string `gorm:"column:code"` 
    Price int `gorm:"column:price"` 
    Deleted gorm.DeletedAt
} 

func (p *Product) TableName() string { 
    return "product" 
} 


func (p *Product) BeforeCreate(tx *gorm.DB) (err error) { 
    tx.Create(&Product{Code: "200", Price: 80}) 
    //操作 
    return nil 
} 
func (p *Product) AfterCreate(tx *gorm.DB) (err error) { 
    tx.Create(&Product{Code: "200", Price: 80}) 
    //操作 
    return nil 
}


性能提高

对于写操作 (创建、更新、删除),为了确保数据的完整性,GORM 会将它们封装在事务内运行但这会降低性能,你可以使用 SkipDefaultTransaction 关闭默认事务

使用 PrepareStmt 缓存预编译语句可以提高后续调用的速度,本机测试提高大约 35 %左右

//连接MySQL数据库 
dsn := "root:123456@tcp(127.0.0.1:3306)/gormtest?charset=utf8mb4&parseTime=True" 
db, err := gorm.Open( 
    mysql.Open(dsn), //MySQL的连接 
    &gorm.Config{
        SkipDefaultTransaction: true, 
        PrepareStmt: true,    
    }, //Gorm的上下文 
)
if err != nil { 
    return 
}