go操作数据库
go操作mysql
go语言中的database/sql
包中提供了保证SQL或者类SQL数据库的通用接口,并不提供具体的数据库驱动。在使用database/sql
包时必须注入(至少)一个数据库驱动。
database/sql
原生支持连接池,是并发安全的。这个包没有具体实现,只是列出了一些第三方需要实现的接口。
下载依赖
go get -u github.com/go-sql-driver/mysql // 将第三方的依赖默认保存在`$GOPATH/src/`
使用Mysql驱动
Open
函数打开一个driverName指定的数据库,dataSourceName制定数据源,一般包括用户名密码数据库名称等。
func Open(driverName, dataSourceName string) (*DB, error)
// 示例:
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql" // 执行init()
)
func main() {
// 数据库信息
dsn := "root:123@tcp(127.0.0.1:3306)/jobs?charset=utf8"
// 连接数据库
d, err := sql.Open("mysql", dsn) // 不会校验用户名和密码是否正确,只会校验字符串格式是否正确
if err != nil {
log.Fatal(err, " sql.open")
}
fmt.Printf("d: %v\n", d)
defer d.Close()
err2 := d.Ping() // 尝试和数据库建立连接,验证密码
if err2 != nil {
log.Fatal(err2, "ping")
}
}
sql.DB的设置
var db *sql.DB
// 设置与数据库建立连接的最大数目, 默认为0无限制。
db.SetMaxOpenConns(n int)
// 设置连接池中最大闲置连接数
db.SetMaxIdelConns(n int)
// 示例:
func main(){
db.SetMaxOpenConns(10)
// 与数据库最大连接数
for i := 0; i < 11; i++ {
sqlStr = "select * from user"
row = db.QueryRow(sqlStr) // 只调用QueryRow()未调用Scan()会阻塞
fmt.Printf("i: %v\n", i)
}
}
查询单条数据
使用db.QueryRow()
方法执行一次查询。并最多返回一条结果。QueryRow()
总是返回非nil值,直到Scan()
被调用。
db.QueryRow()
会从连接池中拿一个连接出来进行查询操作。
Scan()
方法被调用才会释放与数据库连接。
格式:
func (db *sql.DB) QueryRow(query string, args ...interface{}) *Row // query表示sql语句,args表示占位符参数
示例:
func foo(){
var u user // 接收查询结果
// 方法一:
sqlStr := “select * from user”
err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age) // QueryRow()之后调用Scan()方法,否则持有的数据库连接不会被释放。
if err != nil{
fmt.Println("scan field")
return
}
// 方法二: 带占位符
sqlStr = "select * from user where id = ?"
err := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.name, &u.age)
fmt.Println(u.id, u.name, u.age)
}
查询多条数据
多行查询db.Query()
返回多行结果。一般用于执行select
命令。
格式:
func (db *sql.DB) Query(query string, args ...interface{}) (*Rows, error)
示例:
// 2. 查询多条记录
func main(){
sqlStr = "select * from user"
rows, err2 := db.Query(sqlStr)
if err2 != nil {
fmt.Println("query failed")
return
}
defer rows.Close() // 需要手动释放连接,这里与QueryRow()不同。
for rows.Next() {
var u1 User
err := rows.Scan(&u1.id, &u1.name, &u1.age) // 这里的scan不能自动释放连接,不同于Row.Scan
if err != nil {
fmt.Println("rows scan failed")
return
}
fmt.Printf("u1: %v\n", u1)
}
}
插入,更新和删除
插入,更新和删除都是使用一个方法
func (db *sql.DB) Exec(query string, args ...interface{}) (Result, error)
// Result的方法
Result.RowsAffected() // 影响行数
Reuslt.LastInsertId() // 最后一次插入的id
示例:
func main(){
// 插入数据
sqlStr = "insert into user (name, age) values(?, ?)"
ret, err3 := db.Exec(sqlStr, "qq", 10)
if err3 != nil {
fmt.Println("inert failed")
return
}
i, _ := ret.RowsAffected() // 插入的行数
fmt.Printf("i: %v\n", i)
i2, _ := ret.LastInsertId()
fmt.Printf("i2: %v\n", i2) // 最后插入的id
// 更新数据
sqlStr = "update user set name = ? where id = ?"
ret, err4 := db.Exec(sqlStr, "fznzone", 1)
if err4 != nil {
fmt.Println("update failed")
return
}
fmt.Println(ret.RowsAffected())
// 删除数据
sqlStr = "delete from user where id > ?"
ret, _ = db.Exec(sqlStr, 4)
fmt.Println(ret.RowsAffected()) // 查看影响的行数。
}
完整案例:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB // 是一个连接池对象, 代表一个具有零到多个底层连接的连接池。
type User struct {
id int
name string
age int
}
func initDB() (err error) {
// 初始化db
// 数据库信息
dsn := "root:123@tcp(127.0.0.1:3306)/sql_test?charset=utf8"
// 连接数据库
db, err = sql.Open("mysql", dsn) // 不会校验用户名和密码是否正确,只会校验格式是否正确
if err != nil {
return err
}
err2 := db.Ping() // 尝试和数据库建立连接
if err2 != nil {
return err2
}
// 设置连接池最大连接数
db.SetMaxOpenConns(10)
// 设置最大空闲连接数
db.SetMaxIdleConns(5)
//
return
}
func main() {
err := initDB()
if err != nil {
fmt.Println("init db failed.", err)
return
}
// 1. 查询单条记录
var u User
sqlStr := "select * from user" // 查询单条记录
row := db.QueryRow(sqlStr)
sqlStr = "select * from user where id = ?" // 查询带占位符的单条记录。其中?为占位符
row = db.QueryRow(sqlStr, 1)
row.Scan(&u.id, &u.name, &u.age) // 必须对row调用scan方法,因为该方法会释放数据库连接。
fmt.Printf("u: %v\n", u)
// 2. 查询多条记录
sqlStr = "select * from user"
rows, err2 := db.Query(sqlStr)
if err2 != nil {
fmt.Println("query failed")
return
}
defer rows.Close() // 需要手动释放连接,这里与QueryRow()不同。
for rows.Next() {
var u1 User
err := rows.Scan(&u1.id, &u1.name, &u1.age)
if err != nil {
fmt.Println("rows scan failed")
return
}
fmt.Printf("u1: %v\n", u1)
}
// 插入数据
sqlStr = "insert into user (name, age) values(?, ?)"
ret, err3 := db.Exec(sqlStr, "qq1", 10)
if err3 != nil {
fmt.Println("inert failed")
return
}
i, _ := ret.RowsAffected() // 插入的行数
fmt.Printf("i: %v\n", i)
i2, _ := ret.LastInsertId()
fmt.Printf("i2: %v\n", i2) // 最后插入的id
// 更新数据
sqlStr = "update user set name = ? where id = ?"
ret, err4 := db.Exec(sqlStr, "fznzone", 1)
if err4 != nil {
fmt.Println("update failed")
return
}
fmt.Println(ret.RowsAffected())
// 删除数据
sqlStr = "delete from user where id > ?"
ret, _ = db.Exec(sqlStr, 4)
fmt.Println(ret.RowsAffected())
}
Mysql的预处理
普通SQL语句执行过程:
- 客户端对SQL语句的占位符进行替换,得到完整的SQL语句。
- 客户端发送完整的SQL语句到MySQL的服务端。
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
预处理执行过程:
- 把SQL语句分为两部分:命名部分和数据部分。
- 把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
- 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
- MySQL服务端执行完整的SQL语句并返回结果。
为什么预处理?
- 优化Mysql服务器重复执行sql的方法,可以提升服务器的性能,提前让服务器编译,一次编译多次执行,节省后续编译成本。
- 避免SQL注入。
go中的预处理:
func (db *sql.DB)Prepare(query string) (*Stmt, error) // Stmt返回状态
示例:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
type User struct {
id int
name string
age int
}
func initDB() (err error) {
dsn := "root:123@tcp(127.0.0.1:3306)/sql_test?charset=utf8"
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
err3 := db.Ping()
if err3 != nil {
return err3
}
return
}
func main() {
err := initDB()
if err != nil {
fmt.Println("init db failed")
return
}
fmt.Printf("db: %v\n", db)
// 查询操作 预处理
sqlStr := "select * from user where id > ?"
stmt, err2 := db.Prepare(sqlStr)
if err2 != nil {
fmt.Println("parpare failed..")
return
}
defer stmt.Close()
rows, err := stmt.Query(1) // args参数
if err != nil {
fmt.Println("query failed")
}
defer rows.Close()
var u User
for rows.Next() {
rows.Scan(&u.id, &u.name, &u.age)
fmt.Printf("u: %v\n", u)
}
// 插入
sqlStr = "insert into user(name, age) values(?,?)"
s, err3 := db.Prepare(sqlStr)
if err3 != nil {
fmt.Println("prepare failed")
return
}
defer s.Close()
ret, _ := s.Exec("王五", 20)
fmt.Println(ret.RowsAffected())
// 更新
sqlStr = "update user set age = ? where id = ?"
s2, err4 := db.Prepare(sqlStr)
if err4 != nil {
fmt.Println("prepare failed")
return
}
defer s2.Close()
ret, _ = s2.Exec(1000, 5)
fmt.Println(ret.LastInsertId())
// 删除
sqlStr = "delete from user where id > ?"
s3, _ := db.Prepare(sqlStr)
defer s3.Close()
ret, _ = s3.Exec(4)
i, _ := ret.RowsAffected()
fmt.Printf("i: %v\n", i)
}
MySQL的事务处理
事务相关方法
func (db *sql.DB) Begin() (*Tx, error) // 开始事务
func (tx *Tx) Commit() error // 提交事务
func (tx *Tx) Rollback() error // 回滚事务
示例:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB
func initDB() (err error) {
// 初始化db
// 数据库信息
dsn := "root:123@tcp(127.0.0.1:3306)/sql_test?charset=utf8"
// 连接数据库
db, err = sql.Open("mysql", dsn) // 不会校验用户名和密码是否正确,只会校验格式是否正确
if err != nil {
return err
}
err2 := db.Ping() // 尝试和数据库建立连接
if err2 != nil {
return err2
}
// 设置连接池最大连接数
db.SetMaxOpenConns(10)
// 设置最大空闲连接数
db.SetMaxIdleConns(5)
//
return
}
type User struct {
id int
name string
age int
}
func main() {
err := initDB()
if err != nil {
fmt.Println("init failed")
return
}
// 开启事务
tx, err2 := db.Begin()
if err2 != nil {
fmt.Println("begin failed err:%v\n", err2)
return
}
// 执行多个操作
sqlStr := "update user set age = age -2 where id = ?"
_, err3 := tx.Exec(sqlStr, 1)
if err3 != nil {
// 回滚
tx.Rollback()
fmt.Println("执行回滚操作")
return
}
sqlStr = "update xxx set age = age +2 where id = ?" // 执行错误,两次都回滚了
_, err3 = tx.Exec(sqlStr, 2)
if err3 != nil {
tx.Rollback()
fmt.Println("执行混滚操作。。")
return
}
// 提交本次事务
err4 := tx.Commit()
if err4 != nil {
fmt.Println("提交事务错误")
return
}
fmt.Println("执行成功。")
}
sqlx包的使用
安装包
go get github.com/jmoiron/sqlx
示例:
// sqlx和go原生sql只有在查询时候有区别。
package main
import (
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/go-sql-driver/mysql"
)
var db1 *sqlx.DB
func initDB() (err error) {
dsn := "root:123@tcp(127.0.0.1:3306)/sql_test?charset=utf8"
db1, err = sqlx.Connect("mysql", dsn) // 连接数据库并ping
if err != nil {
fmt.Println("connect failed")
return err
}
db1.SetMaxOpenConns(10)
return
}
type User struct {
ID int
Name string
Age int
}
func main() {
err := initDB()
if err != nil {
fmt.Println("init db failed, err :%v\n", err)
return
}
fmt.Printf("db: %v\n", db1)
var u User
// 查询操作, 查询一条记录
sqlStr := "select * from user where id = 1"
err2 := db1.Get(&u, sqlStr)
if err2 != nil {
fmt.Println("query failed err :%v\n", err2)
return
}
fmt.Printf("u: %v\n", u)
// 查询多条记录
var userList []User
sqlStr = "select id, name, age from user"
err3 := db1.Select(&userList, sqlStr) // 这里切片也需要传入指针
if err3 != nil {
fmt.Println("query failed, ERR:%v\n", err3)
return
}
fmt.Printf("userList: %v\n", userList)
}
// 插入数据
func insertRowDemo() {
sqlStr := "insert into user(name, age) values (?,?)"
ret, err := db.Exec(sqlStr, "沙河小王子", 19)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theID, err := ret.LastInsertId() // 新插入数据的id
if err != nil {
fmt.Printf("get lastinsert ID failed, err:%v\n", err)
return
}
fmt.Printf("insert success, the id is %d.\n", theID)
}
// 更新数据
func updateRowDemo() {
sqlStr := "update user set age=? where id = ?"
ret, err := db.Exec(sqlStr, 39, 6)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("update success, affected rows:%d\n", n)
}
// 删除数据
func deleteRowDemo() {
sqlStr := "delete from user where id = ?"
ret, err := db.Exec(sqlStr, 6)
if err != nil {
fmt.Printf("delete failed, err:%v\n", err)
return
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("delete success, affected rows:%d\n", n)
}
SQL注入
package main
import (
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/go-sql-driver/mysql"
)
var db2 *sqlx.DB
func initDB() (err error) {
dsn := "root:123@tcp(127.0.0.1:3306)/sql_test?charset=utf8"
db2, err = sqlx.Connect("mysql", dsn)
if err != nil {
return err
}
db2.SetMaxOpenConns(10)
return
}
type User struct {
ID int
Name string
Age int
}
func main() {
err := initDB()
if err != nil {
fmt.Println("init db failed err:%v\n", err)
return
}
fmt.Printf("db2: %v\n", db2)
name := "xxx' or 1=1#"
sqlStr := fmt.Sprintf("select id, name,age from user where name='%s'", name)
fmt.Printf("sqlStr: %v\n", sqlStr) // select id, name,age from user where name='xxx' or 1=1#'
var users []User
err2 := db2.Select(&users, sqlStr)
if err2 != nil {
fmt.Println("query failed", err2)
return
}
for _, v := range users {
fmt.Printf("v: %v\n", v)
}
}
gorm操作mysql
安装:
go get -u gorm.io/gorm // gorm
要连接数据库首先要导入驱动程序:
import _ "github.com/go-sql-driver/mysql"
为了方便,grom包装一些驱动:
"gorm.io/driver/mysql" // mysql,可以不使用上面那个了
"gorm.io/driver/postgres" // postgres
"gorm.io/driver/sqlite" // sqlite
go get -u gorm.io/driver/mysql
简单示例:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type UserInfo struct {
ID int // 默认使用作为主键
Name string
Gender int
Address string
}
func main() {
dsn := "root:123@(localhost:3306)/learn_gorm?charset=utf8mb4&parseTime=true"
d := mysql.Open(dsn)
db, err := gorm.Open(d)
if err != nil {
panic(err)
}
// 自动迁移
db.AutoMigrate(&UserInfo{})
// 插入数据
db.Create(&UserInfo{
1, "zs", 0, "beijing",
})
var u UserInfo
// 查询第一条数据
db.First(&u)
fmt.Printf("u: %v\n", u)
// 更新数据
db.Model(&u).Update("address", "上海")
// 删除
db.Delete(&u)
}
主键、表名、列名约定
默认情况下,GORM 使用 ID
作为主键。
type User struct{
ID string // 默认ID作为主键
Name string
}
type Animal struct{
AnimalID int64 `gorm:"PRIMARY_KEY"` // 使用AnimalID作为主键
Name string
}
使用结构体名的 蛇形复数
作为表名,例如:对于结构体 User
,根据约定,其表名为 users
。
type Animal struct {
ID int64
AnimalID int64 `gorm:"PRIMARY_KEY"`
Name string
}
func (Animal) TableName() string { // 修改表名为new_table_name_animal
return "new_table_name_animal"
}
func (u User) TabelName() string { // 设置表名
if u.Role == "admin" {
return "admin_users"
} else {
return "users"
}
}
字段名的 蛇形
作为列名,并使用 CreatedAt
、UpdatedAt
、Deleted_At
字段追踪创建、更新时间。
type User struct{
ID int64
Name string
Age int64 `gorm:"column:age123"` // 指定字段名为:age123
MemberNumber string // 创建的字段名为:member_number
}
grom.Model结构体
可以将它嵌入到您的结构体中,以包含这几个字段。
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// 示例2:继承gorm.Model的例子
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct{ // 不使用gorm.Model定义模型
ID int
Name string
}
type Product struct { // 使用gorm.Model定义模型,嵌入到我们结构体中。包含ID, Created_At, Updated_At, Deleted_At。
gorm.Model
Code string
Price uint
Status int8
}
func main() {
// 想要正确的处理 time.Time ,您需要带上 parseTime 参数
dsn := "root:123@tcp(localhost:3306)/learn_gorm?charset=utf8mb4&parseTime=True&loc=Local"
d := mysql.Open(dsn)
db, err := gorm.Open(d, &gorm.Config{})
if err != nil {
fmt.Printf("%v\n", err.Error())
return
}
// 迁移schema,执行建表之类操作
db.AutoMigrate(&Product{})
// 1. insert新记录
db.Create(&Product{
Code: "200",
Price: 101,
Status: 0,
})
// 2. 查询记录
var product Product
db.First(&product) // 默认查找条件,查找id第一个。
db.First(&product, 1) // id为1记录存在时,同上,查找id为1的记录。
fmt.Printf("product: %+v\n", product)
// 根据条件查询记录
db.Find(&product, "code=?", "100") // 如果有多条记录,查询第一条记录
fmt.Printf("product: %+v\n", product)
// 3. 更新记录
db.Model(&product).Update("status", 1)
fmt.Printf("product: %+v\n", product)
db.Model(&product).Updates(Product{Price: 200, Code: "code200"}) // 更新多个字段
db.Model(&product).Updates(map[string]interface{}{"Price": 300, "Code": "Code300"}) // 同上
// // 4. 删除操作
db.Delete(&product, 2) // 删除id=2的记录,只是更新deleted_at字段(逻辑删除)
fmt.Printf("product: %+v\n", product)
}
结构体字段标签
结构体标记(tag) | 说明 |
---|---|
Column | 指定列名 |
Type | 指定列数据类型 |
Size | 指定列长度,默认255 |
PRIMARY_KEY | 设置为主键 |
UNIQUE | 指定列唯一 |
DEFAULT | 指定列默认值 |
PRECISION | 指定列精度 |
NOT NULL | 列非空 |
AUTO_INCREMENT | 自增 |
INDEX | 创建索引 |
UNIQUE_INDEX | 创建唯一索引 |
EMBEDDED | 将结构体设置为嵌入 |
EMBEDDED_PREFIX | 设置嵌入结构的前缀 |
- |
忽略这个字段,不会在数据库中创建 |
示例:
package main
import (
"database/sql"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
Age sql.NullInt64 // 可以为null的int64类型, 因为sql的null不能转为int
Birthday *time.Time
Email string `gorm:"type:varchar(100);unique_index"` // 类型varchar, 唯一索引
Role string `gorm:"size:255;comment:'角色'"` // 设置字段长度为255, 添加注释
MemberNumber *string `gorm:"unique;not null"` // 设置字段唯一,不能为空
Num int `gorm:"AUTO_INCREMENT"` // 设置字段自增
Address string `gorm:"index:addr"` // 添加字段索引,索引名addr
IgnoreMe int `gorm:"-"` // 忽略此字段,不会创建在数据库中
}
type Animal struct {
ID int64
AnimalID int64 `gorm:"PRIMARY_KEY"`
Name string
}
func main() {
dsn := "root:123@(localhost:3306)/learn_gorm?charset=utf8mb4&parseTime=true"
d := mysql.Open(dsn)
db, err := gorm.Open(d, &gorm.Config{})
if err != nil {
panic(err)
}
db.AutoMigrate(&User{})
db.AutoMigrate(&Animal{})
}
插入操作
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 1. 定义模型
type User struct {
ID int64
Name string
Age int64
}
func main() {
dsn := "root:123@(localhost:3306)/learn_gorm?charset=utf8mb4&parseTime=true"
d := mysql.Open(dsn)
db, err := gorm.Open(d, &gorm.Config{})
if err != nil {
panic(err)
}
// 2. 模型与数据库中表对应
db.AutoMigrate(&User{})
// 3. 创建记录
var u = User{Name: "zs", Age: 19}
db.Create(&u) // 传&u。 否则报panic
}
// 示例2. 设置默认值
type User struct {
ID int64
Name string `gorm:"default:李四"`
Age int64
}
func main(){
var u2 = User{Age: 21}
db.Create(&u2)
}
// 示例3: 设置default的tag之后,零值不会插入到数据库。
type User struct {
ID int64
Name string `gorm:"default:李四"`
Age int64
}
var u2 = User{Age: 21, Name: ""} // 同User{Age: 21},数据库会插入【李四】,而不是被空字符覆盖。
db.Debug().Create(&u2) // 在语句之前调用Debug()打印sql语句。
// 示例4:使用指定字段插入数据
type User struct {
ID int64
Name *string `gorm:"default:李四"`
Age *int64 `gorm:"default:18"`
Address string
}
name := "zs"
var age int64 = 100
u := User{Name: &name, Age: &age, Address: "bj"}
db.Debug().Select("Name", "Address").Create(&u) // INSERT INTO `users` (`name`,`address`) VALUES ('zs','bj')
/** 数据库中:
3 | zs | 18 | bj |
**/
// 示例5: 批量插入
type User3 struct {
ID int64
Name string
Age int64
}
var users = []User3{User3{Name: "zs"}, User3{Name: "ls"}, User3{Name: "ww"}}
db.Debug().Create(&users) // 一条sql插入
var users = []User3{User3{Name: "zs_new"}, User3{Name: "ls_new"}, User3{Name: "ww_new"}, User3{Name: "zl"}, User3{Name: "heihei"}}
db.Debug().CreateInBatches(&users, 2) // 多条sql,一条sql插入2条记录
// 示例6: 使用map插入
user := map[string]interface{}{
"Name": "gaga", "Age": 19,
}
db.Model(&User3{}).Create(&user)
// map批量插入
users := []map[string]interface{}{
{"Name": "gaga_new1", "Age": 29},
{"Name": "gaga_new2", "Age": 18},
}
db.Model(&User3{}).Create(&users)
注意:
-
对于声明了默认值的字段,在插入新记录的时候,会忽略零值(0,"",false, nil等)的字段。需要使用指针类型或 实现Scanner/Valuer接口 来避免这个问题。
// 方法一: 声明指针类 type User struct{ ID int64 Name *string `gorm:"default:李四"` // 为*string类型 Age *int64 `gorm:"default:18"` } func main(){ var u User = User{Name:new(string), Age:new(int64)} // 插入零值 db.Create(&u) } /** | id | name | age | | 1 | | 0 | **/ // 方法二:使用实现了Scanner/Valuer接口的结构体 type User2 struct { ID int64 Name sql.NullString `gorm:"default:李四"` Age sql.NullInt64 `gorm:"default:18"` } func main(){ var u2 = User2{Name: sql.NullString{String: "", Valid: true}, Age: sql.NullInt64{0, true}} db.Create(&u2) } // 如果不想再数据库中设置默认值 type User2 struct { ID int64 Name sql.NullString `gorm:"default:李四"` Age sql.NullInt64 `gorm:"default:18"` }
-
如果在迁移时候,跳过默认值定义,可以在tag中添加
default:(-)
查询操作
一般查询
var user User
// 1. 查询id排序的第一条记录
db.First(&user) // select * from users order by id limit 1;
fmt.Printf("user: %+v\n", user)
// 2. 查询第一条记录
db.Take(&user) // select * from users limit 1;
fmt.Printf("user: %+v\n", user)
// 3. 查询id逆序第一条记录
db.Last(&user)
fmt.Printf("user: %+v\n", user) // select * from users order by id desc limit 1;
result := db.First(&user)
fmt.Printf("result.RowsAffected: %v\n", result.RowsAffected) // 找到的记录数
fmt.Printf("result.Error: %v\n", result.Error) // return errors or nil
}
// 4. 查询所有记录
var users = make([]User, 0, 10)
db.Find(&users) // select * from users;
fmt.Printf("users: %v\n", users)
// 5. 查询制定id的记录
var u User
db.First(&u, 3) // select * from users where id = 3;
fmt.Printf("u: %v\n", u)
条件查询
// 1. 查询指定条件的第一条记录
var user User
db.Where("name=?", "zs").First(&user) // select * from users where name="zs" order by id limit 1;
fmt.Printf("user: %+v\n", user)
// 2. 查询制定条件的所有记录
var users = make([]User, 0, 10)
db.Where("name=?", "zs").Find(&users) // select * from users where name="zs"
fmt.Printf("users: %+v\n", users)
// 3. <> 不等于
users = make([]User, 0, 10)
db.Where("name<>?", "zs").Find(&users) // select * from users where name<>'zs'
fmt.Printf("users: %+v\n", users)
// 4. in
users = make([]User, 0, 10)
db.Where("name in (?)", []string{"zs", "ls"}).Find(&users) // select * from users where name in ("zs", "ls");
fmt.Printf("users: %+v\n", users)
// 5. like语句
users = make([]User, 0, 10)
db.Where("name like ?", "%zs%").Find(&users) // select * from users where name like %zs%;
fmt.Printf("users: %+v\n", users)
// 6. and语句
users = make([]User, 0, 10)
db.Where("name=? and age=?", "zs", 18).Find(&users) // select * from users where name=zs and age = 18
fmt.Printf("users: %+v\n", users)
// 7. >语句
lastWeek := time.Date(2022, 9, 13, 1, 1, 1, 1, time.Local).Format("2006-01-02 15:04:05")
fmt.Printf("lastWeek: %v\n", lastWeek) // 2022-09-13 01:01:01
users = make([]User, 0, 10)
db.Where("updated_at > ?", lastWeek).Find(&users) // select * from users where updated_at > "2022-09-13 01:01:01"
fmt.Printf("users: %+v\n", users)
// 8. between 语句
users = make([]User, 0, 10)
db.Where("age between ? and ?", 10, 20).Find(&users) // select * from users where age between 10 and 20;
fmt.Printf("users: %+v\n", users)
struct和map查询
// 1. 结构体作为查询条件
var user User
db.Where(&User{Name: "zs", Age: 18}).First(&user) // select * from users where name = zs and age = 18
fmt.Printf("user: %+v\n", user)
// 2. map作为查询条件
var users = make([]User, 0, 10)
db.Where(map[string]interface{}{"Name": "zs", "Age": 18}).Find(&users) // select * from users where name = zs and age = 18
fmt.Printf("users: %v\n", users)
// 3. 主键id作为查询条件
users = make([]User, 0)
db.Where([]int64{1, 3, 5}).Find(&users) // select * from users where id in (1, 3, 5)
fmt.Printf("users: %v\n", users)
注意:
-
在使用结构体查询的时候,GORM只会通过非零字段,一些字段的零值不会用于到查询条件中。 map不受影响。
var user User db.Debug().Where(&User{Name: "zs", Age: 0}).First(&user) // select * from users where name = zs, 零值的Age不会用于查询条件 fmt.Printf("user: %+v\n", user) // 解决办法: 使用指针 或者 实现Scanner/Valuer接口。 // 法一:使用指针 type User struct{ Name string Age *int64 } var age int64 = 0 db.Where(&User{Name: "zs", Age: &age}).First(&user) // select * from users where name = zs and age = 0 // 法二:使用scanner和Valuer接口的类型 type User struct{ Age sql.NullInt64 } db.Where(&User{Name: "zs", Age: sql.NullInt64{0, true}}).First(&user) // select * from users where name = zs and age = 0
Not条件
// 1. 基本使用
var users = make([]User, 0)
db.Not("name=?", "zs").Find(&users) // select * from users where not name = "zs"
fmt.Printf("users: %v\n", users)
// 2. struct。 在not中使用and
db.Not(User{Name: "zs", Age: 18}).Find(&users) // select * from users where name <> zs and age <> 18;
fmt.Printf("users: %v\n", users)
// 3. map。在not中使用and
db.Not(map[string]interface{}{"Name": "zs", "Age": 18}).Find(&users) // select * from users where name <> zs and age <> 18;
fmt.Printf("users: %v\n", users)
Or条件
// 1. 基本使用
var users = make([]User, 0)
db.Where("name=?", "zs").Or("age=?", 18).Find(&users) // SELECT * FROM `users` WHERE (name='zs' OR age=18)
fmt.Printf("users: %v\n", users)
// 2. struct。 在or中使用and
db.Where("name=?", "zs").Or(User{Name: "zs-new", Age: 18}).Find(&users) // SELECT * FROM users WHERE (name='zs' OR (name = 'zs-new' AND age = 18))
fmt.Printf("users: %v\n", users)
// 3. map
users = make([]User, 0)
db.Where("name=?", "zs").Or(map[string]interface{}{"Name": "zs-new", "Age": 18}).Find(&users) // SELECT * FROM users WHERE (name='zs' OR (name = 'zs-new' AND age = 18))
fmt.Printf("users: %v\n", users)
指定字段
// 1. 只查询指定的字段
users = make([]User, 0)
db.Select("name", "age").Find(&users) // select name,age from users。
fmt.Printf("users: %+v\n", users) // 注意: 此时name和age赋值给users的值,其余字段为零值。
// 2. 只查询指定的字段, 同上
db.Debug().Select([]string{"name", "age"}).Find(&users) // select name,age from users
fmt.Printf("users: %v\n", users)
Order
// 1. 基本使用
users = make([]User, 0)
db.Order("name desc, age").Find(&users) // select * from users order by name desc, age;
fmt.Printf("users: %v\n", users)
// 2. 同上
db.Order("name desc").Order("age").Find(&users) // select * from users order by name desc, age;
fmt.Printf("users: %v\n", users)
limit 和 offset
users = make([]User, 0)
db.Debug().Limit(1).Offset(2).Where("name=?", "zs").Find(&users) // select * from users where name = "zs" limit 1 offset 2;
fmt.Printf("users: %v\n", users)
// 示例2
var users = make([]User, 0)
db.Limit(5).Offset(-1).Find(&users) // Offset设置为-1或0,取消offset。 SELECT * FROM `users` LIMIT 5
fmt.Printf("users: %+v\n", users)
注意:使用count
不能用Offset
,或者是将Offset
值设为 -1
。
group和having
// 1. 定义Result的结构体
type Result struct {
Name string
TotalSum int64
}
func main(){
var result = make([]Result, 0, 10)
// 注意:1.需要指定表,db.Model() ; 2. Select中的字段名和结构体Result要对应。
db.Debug().Model(&User{}).Select("name, sum(age) as total_sum").Group("name").Find(&result)
fmt.Printf("result: %v\n", result)
}
Distinct
// 1. 返回结果赋值给User结构体
var users = make([]User, 0, 10)
db.Debug().Distinct("name", "age").Find(&users)
fmt.Printf("users: %v\n", users)
// 2. 返回结果赋值给新的结构体
type Result struct {
Name string
Age int64
}
func main(){
var results = make([]Result, 0, 10)
db.Debug().Model(&User{}).Distinct("name", "age").Find(&results) // 需要调用db.Model指定表名 ***
fmt.Printf("users: %v\n", users)
}
Count
会执行sql查询操作。类似Find()
var count int64
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count) // SELECT count(1) FROM users WHERE name = 'jinzhu';
db.Table("deleted_users").Count(&count) // SELECT count(1) FROM deleted_users;
db.Model(&User{}).Distinct("name").Count(&count) // SELECT COUNT(DISTINCT(`name`)) FROM `users`
db.Table("deleted_users").Select("count(distinct(name))").Count(&count) // SELECT count(distinct(name)) FROM deleted_users
// 注意:
db.Debug().Find(&users).Count(&count) // 会产生两条sql, 一条Find,一条Count
db.Model(&User{}).Count(&count) // 产生一条sql,查询count
db.Debug().Model(&User{}).Limit(2).Count(&count) // SELECT count(*) FROM `users` LIMIT 2。 查询出来的结果为count(*),再limit 2。
db.Debug().Model(&User{}).Limit(2).Offset(2).Count(&count) // SELECT count(*) FROM `users` LIMIT 2 OFFSET 2。查询出来的结果为count(*),就一行,在offset 2,就没有结果了。
更新操作
// 1. 更新所有字段
type User struct{
gorm.Model
Name string
Age int64
Active bool
}
func main(){
u := User{}
db.First(&u)
u.Name = "张三"
u.Age = 100
db.Save(&u) // 默认会将user的所有字段都进行update操作,如果新对象(没有插入)则进行insert操作。 UPDATE `users` SET `created_at`='2022-09-15 11:50:38.67',`updated_at`='2022-09-15 20:34:10.821',`deleted_at`=NULL,`name`='张三',`age`=50,`active`=true ;
}
// 2. 更新指定字段Update
db.Model(&u1).Update("name", "world") // 更新u1记录的name为world
db.Model(&u1).Where("active=?", true).Update("name", "world") // 更新u1记录【带条件】的name为world
// 3. 更新多个字段Updates。 使用struct和map
db.Model(&user).Updates(User{Name: "王五", Age: 18, Active: false}) // 注意当使用struct,【存在零值不会更新的问题】
db.Model(&user).Updates(map[string]interface{}{"name": "王五", "age": 18, "active": false}) // 使用map更新多个字段
// 4. 更新选定的字段。
db.Model(&user).Select("name", "age").Updates(User{Name: "王五1", Age: 118, Active: true}) // 传进来的是struct, 但只更新name和age
db.Model(&user).Select("name", "age").Updates(map[string]interface{}{"name": "王五2", "age": 128, "active": false}) // 传进来的是map, 但只更新name和age
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "王五3", "age": 10, "active": true}) // 【忽略name字段】。更新除了name字段之外的字段
// 5. 批量更新记录
db.Model(&User{}).Where("active=?", false).Updates(User{Name: "hello", Age: 11}) // 批量更新记录
db.Table("users").Where("active=?", false).Updates(User{Name: "world", Age: 18}) // 对表users批量更新记录
删除操作
// 1. 删除记录
u := User{}
u.ID = 1
db.Delete(&u) // 删除id为1的记录
u = User{}
u.Name = "zs"
db.Debug().Where("name=?", "zs").Delete(&u) // 批量删除
// 2. 根据主键删除
db.Delete(&User{}, 3) // 删除id=3记录
db.Delete(&User{}, "3") // 同上
db.Delete(&User{}, []int{1, 2, 3}) // 删除多个id的记录
// 3. 批量删除
db.Where("name = ?", "zs").Delete(&User{})
db.Delete(&User{}, "name=?", "ls")
// 4. 物理删除
db.Debug().Unscoped().Where("name is not null").Delete(&User{}) // 物理删除
注意:
- 删除记录时候,【确保主键字段(有值)】,gorm会通过主键删除记录,如果主键字段为空,会删除所有记录。
go操作redis
应用场景
- 缓存系统,减轻主数据库MySQL的压力
- 计数场景,比如微博,抖音中的关注数和粉丝数。
- 热门排行榜,特别适合使用
ZSET
。 - 利用
LIST
实现队列的功能。
下载依赖
go get -u github.com/go-redis/redis
连接redis
package main
import (
"fmt"
"github.com/go-redis/redis"
)
var redisdb *redis.Client
func initRedis() (err error) {
redisdb = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
})
s, err2 := redisdb.Ping().Result()
if err2 != nil {
return err2
}
fmt.Printf("s: %v\n", s)
return
}
func main() {
err := initRedis()
if err != nil {
fmt.Println("connected redis failed", err)
return
}
fmt.Printf("redisdb: %v\n", redisdb)
}
操作
package main
import (
"fmt"
"github.com/go-redis/redis"
)
var redisdb *redis.Client
func initRedis() (err error) {
redisdb = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
})
s, err2 := redisdb.Ping().Result() // pong
if err2 != nil {
return err2
}
fmt.Printf("s: %v\n", s)
return
}
func main() {
err := initRedis()
if err != nil {
fmt.Println("connected redis failed", err)
return
}
fmt.Printf("redisdb: %v\n", redisdb)
// set/get示例
err2 := redisdb.Set("score", 100, 0).Err()
if err2 != nil {
fmt.Println("set failed")
return
}
val, err3 := redisdb.Get("score").Result()
if err3 != nil {
fmt.Println("get failed")
return
}
fmt.Printf("val: %v\n", val)
val1, err := redisdb.Get("name1abc").Result()
if err == redis.Nil {
fmt.Println("key name does not exists")
} else if err != nil {
fmt.Println("get failed")
return
} else {
fmt.Println("name:", val1)
}
// zset示例
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南