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语句执行过程:

  1. 客户端对SQL语句的占位符进行替换,得到完整的SQL语句。
  2. 客户端发送完整的SQL语句到MySQL的服务端。
  3. MySQL服务端执行完整的SQL语句并将结果返回给客户端。

预处理执行过程:

  1. 把SQL语句分为两部分:命名部分和数据部分。
  2. 把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
  3. 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
  4. MySQL服务端执行完整的SQL语句并返回结果。

为什么预处理?

  1. 优化Mysql服务器重复执行sql的方法,可以提升服务器的性能,提前让服务器编译,一次编译多次执行,节省后续编译成本。
  2. 避免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"
	}
}

字段名的 蛇形 作为列名,并使用 CreatedAtUpdatedAtDeleted_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示例

}

posted @   学习记录13  阅读(42)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示