GORM专题-crud操作

2022/10/2 gosql

官方文档 (opens new window)

🌙 1.模型定义

数据表定义如下:

CREATE TABLE `goods`
(
    `id`         int            NOT NULL AUTO_INCREMENT COMMENT '自增id,商品id',
    `name`       varchar(30)    NOT NULL COMMENT '商品名',
    `price`      decimal(10, 2) NOT NULL COMMENT '商品价格',
    `type_id`    int            NOT NULL COMMENT '商品类型id',
    `createtime` bigint         NOT NULL DEFAULT '0' COMMENT '创建时间',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1
2
3
4
5
6
7
8
9

转为struct:

type Goods struct {
	Id         int     `gorm:"column:id" db:"id" json:"id" form:"id"`                                 //自增id,商品id
	Name       string  `gorm:"column:name" db:"name" json:"name" form:"name"`                         //商品名
	Price      float64 `gorm:"column:price" db:"price" json:"price" form:"price"`                     //商品价格
	TypeId     int     `gorm:"column:type_id" db:"type_id" json:"type_id" form:"type_id"`             //商品类型id
	Createtime int64   `gorm:"column:createtime" db:"createtime" json:"createtime" form:"createtime"` //创建时间
}
1
2
3
4
5
6
7

默认gorm对struct字段名称使用Snake Case命名风格转换成mysql表字段名(小写字母)

Snake Case: 各个单词之间使用下划线分割,例如:CreateTime ---> create_time

默认情况下,使用ID作为主键,使用结构体名称的 Snake Case 风格的复数形式作为表名,使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间。

🌙 2.模型标签

我们可以使用标签去修改这些默认的定义,gorm多个标签可以使用分号分割,不同类别的标签使用空格分割。

标签定义:

`gorm:"标签内容"`
`gorm:"primaryKey;column:userid" json:"userid" xorm:"userid"`
1
2

gorm常用标签:

标签 说明 例子
column 指定列名 gorm:"column:createtime"
primaryKey 指定主键 gorm:"column:id;PRIMARY_key"

例如:

type Model struct {
	Userid  int64 `json:"userid" xorm:"userid" gorm:"primaryKey;column:userid"`
	Userid1 int64 `json:"userid1" xorm:"userid1" gorm:"column:userid1"`
	Userid2 int64 `json:"userid2" xorm:"userid2" gorm:"column:userid2"`
	Userid3 int64 `json:"userid3" xorm:"userid3" gorm:"column:userid3"`
	Ct      int64 `json:"ct" xorm:"ct" gorm:"column:ct"`
	Ut      int64 `json:"ut" xorm:"ut" gorm:"column:ut"`
}
1
2
3
4
5
6
7
8

🌙 3.表名映射

  • 复数表名,比如结构体User, 默认表名users
  • 实现Tabler接口(TableName不支持动态变化,它会被缓存下来以便后续使用)
type Tabler interface {
    TableName() string
}

// TableName 会被 User 的表名重写为 "user_list"
func (u User) TableName() string {
    return "user_list"
}
1
2
3
4
5
6
7
8
  • 动态表名,使用 Scopes
func UserTable(u user) func (tx *gorm.DB) *gorm.DB {
    return func (tx *gorm.DB) *gorm.DB {
        if user.Admin {
            return tx.Table("admin_user")
        }
        
        return tx.Table("users")
    }
}

db.Scopes(UserTable(*user)).Create(&user)
1
2
3
4
5
6
7
8
9
10
11
  • 直接使用Tables
db.Tables("table_name").Create(&user)
1

🌙 4.Model

  • GORM 自带一个gorm.Model结构体,其包括字段IDCreatedAtUpdatedAtDeletedAt:
type Model struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
}
1
2
3
4
5
6

GORM约定使用CreateAt、UpdatedAt 追踪创建、更新时间。如果定义了这种字段,GORM 在创建、更新时会自动填充当前时间。

要使用不同名称的字段,可以配置 autoCreateTime、autoUpdateTime 标签

如果想要保存 UNIX 秒时间戳,而不是time,只需要简单地将 time.Time 修改为 int 即可。

type User struct {
    CreateAt time.Time // 默认创建时间,0值为当前时间填充
    UpdatedAt int // 默认更新时间,使用时间戳秒填充
    Updated1 int64 `gorm:autoUpdateTime:nano` // 自定义,使用时间戳纳秒更新时间
    Updated2 int64 `gorm:autoUpdateTime:milli` // 自定义,使用时间戳毫秒更新时间
    Created int64 `gorm:autocreateTime` //自定义,使用时间戳秒创建时间
}

1
2
3
4
5
6
7
8

我们在使用的时候可以嵌入到结构体中(表中的字段也需要去定义):

type User struct {
	gorm.Model        // 继承上述字段

	Username   string `gorm:"column:username;json:username"` // `gorm:"column:username"`: Mysql表的字段名为username
	Password   string `gorm:"column:password"`
	Admin      bool   `gorm:"-"`                 // 忽略
}
1
2
3
4
5
6
7

等效于:

type User struct {
    ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"
	
	Username   string `gorm:"column:username" json:"username"` // `gorm:"column:username"`: Mysql表的字段名为username
	Password   string `gorm:"column:password"`
	Admin      bool   `gorm:"-"`                 // 忽略
}
1
2
3
4
5
6
7
8
9
10
  • 对于正常的结构体字段,也可以通过标签embedded将其嵌入,例如:
type Author struct {
    Name string
    Email string
}

type Blog {
    ID int
    Author Author `gorm:"embedded"`
    Likes int32
}

// 等效于
type Blog struct {
    ID int
    Name string
    Email string
    Likes int32
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  • 可以使用标签embeddedPrefix来为db中的字段名称添加前缀。例如:
type Blog {
    ID int
    Author Author `gorm:"embedded;embeddedPrefix:author_"`
    Likes int32
}

// 等效于
type Blog struct {
    ID int
    AuthorName string
    AuthorEmail string
    Likes int32
}
1
2
3
4
5
6
7
8
9
10
11
12
13

🌙 5.数据库连接

GORM 官方支持的数据库类型有:MySQL、PostgreSQL、SQLite、SQL Server。连接数据库主要的两个步骤:

  • 配置DSN(Data Source Name)。
  • 使用gorm.Open连接数据库。

🌙 5.1.DSN

gorm库使用DSN作为链接数据库的参数,DSN即数据源名称,用来描述数据库连接信息。一般包括数据库域名地址、账号、用户名等,格式:

[username[:password]@[protocol][(address)]/dbname?param1=value&...&paramN=valueN

	username= "root" // 用户名
	password= "123456" // 密码
	host= "127.0.0.1" // 域名
	port= 3306 // 端口
	Dbname= "test" // 数据库名

params:?charset=utf8&parseTime=True&loc=Local&timeout=10
   
    charset=utf8mb4 客户端字符集
    parseTime=True 支持把数据库datetime和date数据类型准尉golang的time.Time类型
    loc=Local 使用系统本地时区
    timeout=10 设置10s后连接超时
1
2
3
4
5
6
7
8
9
10
11
12
13
14

🌙 5.2.连接数据库

  • 通过mysql.Open连接数据库
package dao
import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
)

var DB *gorm.DB
func init() {
	// 连接数据库
	username := "root" // 用户名
	password := "123456" // 密码
	host := "127.0.0.1" // 域名
	port := 3306 // 端口
	Dbname := "test" // 数据库名
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=10", username, password, host, port, Dbname)

    // ?charset=utf8&parseTime=True&loc=Local&timeout=10:
    // charset=utf8mb4 客户端字符集
    // parseTime=True 支持把数据库datetime和date数据类型准尉golang的time.Time类型
    // loc=Local 使用系统本地时区
    // timeout=10 设置10s后连接超时
	// https://gorm.io/zh_CN/docs/connecting_to_the_database.html#MySQL
	
	
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
		// 其他配置...
	})

	if err != nil {
		log.Fatalln("db connect failed err", err)
	}
	DB = db
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  • 通过mysql.New创建连接数据库 MySQL 驱动程序提供了一些高级配置,可以在初始化过程中使用,例如:
	db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: dsn, // DSN data source name
		DefaultStringSize: 256, // string 类型字段的默认长度
		DisableDatetimePrecision: true, // 禁用 datetime 精度, MySQL 5.6 之前不支持
		DontSupportRenameIndex: true, // 重名索引时才有删除并新建的方式,MySQL 5.7 之前 和 MariaDB不支持重命名索引
		DontSupportRenameColumn: true, // 用`change`重命名列,MySQL 8 之前 和 MariaDB不支持重命列
		SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
	}), &gorm.Config{})
1
2
3
4
5
6
7
8
  • 自定义MySQL驱动: GORM 允许通过 DriverName 选项自定义 MySQL 驱动:
db, err := gorm.Open(mysql.New(mysql.Config{
    DriverName: "my_mysql_driver",
    DSN: dsn, // DSN data source name
	}), &gorm.Config{})
1
2
3
4

🌙 6.调试模式

打开调试模式,可以在console查看sql的打印:

db.Debug()

// 例如
err := DB.Debug().Create(user).Error
1
2
3
4

🌙 7.连接池配置

sqlDb,_ := DB.DB()
// 设置数据库连接池最大连接数
sqlDb.SetMaxOpenCoons(100)
// 连接池最大允许的空闲连接数,如果没有SQL任务需要执行的连接数大于20.超过的链接会被连接池关闭
sqlDb.SetMaxIdleConns(20)
1
2
3
4
5

🌙 8.增删改查(crud)

🌙 8.1 Create插入数据

🌙 8.1.1指定字段Select

DB.Select("username", "password").Create(&user) 
1

例如:

func SaveUser1(user *User) {
	// 指定插入字段username, password
	result := DB.Select("username", "password").Create(user)
	affected := result.RowsAffected
	fmt.Println("affect rows", affected)

	err := result.Error
	if err != nil {
		log.Panicln("insert user error", err)
	}
}

func SaveUser(user *User) {
	// 连接数据库 DB.Debug()开启debug模式
	err := DB.Debug().Create(user).Error
	if err != nil {
		log.Panicln("insert user error", err)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

🌙 8.1.2忽略字段Omit

// 除了username
DB.Omit("").Create(&user) 
1
2

🌙 8.1.3批量插入

// 多个user
var users = []Users{{Username: "u1"}, {Username: "u2"}, {Username: "u3"}}
DB.Create(&users)
1
2
3

也可以使用CreateInBatches分批创建,可以指定每批的数量:

var users = []Users{{Username: "u1"}, {Username: "u2"},..., {Username: "u10000"}}
// 每次新增100条
DB.CreateInBatches(users, 100)
1
2
3

🌙 8.1.4使用结构体形式

DB.Create(&user)
1
  • 使用map形式:主键不会被填充,需要指定Model
// 单个
DB.Model(&User{}).Create(map[string]interface{}{
    "Name": "u1",
    "Age": 18
})

// 多个
DB.Model(&User{}).Create(map[string]interface{}{
    {"Name": "u1","Age": 18},
    {"Name": "u2","Age": 19},
})  
1
2
3
4
5
6
7
8
9
10
11

🌙 8.1.4sql表达式

// 使用md5加密
// insert into users (username, password) values ("u1", md5("123456"))
DB.Model(&User{}).Create(map[string]interface{}{
    "Name": "u1",
    "Password": clause.Expr{SQL: "md5(?)", Vars:[]interface{}{"123456"}},
})
1
2
3
4
5
6

🌙 8.1.5原生sql语句

// ? 占位符号
DB.Exec("insert into users (username, password) values (?,?)", user.Username, user.Password) 
1
2

一般md5加密可以使用crypto/md5

🌙 8.2 Update更新数据

🌙 8.2.1使用Save先查询再更新

func UpdateGoods() {
	var good Goods
	// Take 赋值
	err := DB.Where("id=?", good.Id).Take(&good).Error
	if err != nil {
		log.Panicln("search goods error", err)
	}
	
	// 修改Price
	good.Price = 5.5
	// 保存
	err = DB.Save(&good).Error
	if err != nil {
		log.Panicln("update goods error", err)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

🌙 8.2.2使用Update更新

Update: 更新单个字段

Updates: 更新多个字段使用结构体或者map

func UpdateGoodById(id int, price string, name string) {
	// 指定Model
	// 更新单列
	err := DB.Model(&Goods{}).Where("id", id).Update("price", price).Error
	// 更新多列 结构体或者map
	err = DB.Model(&Goods{}).Where("id", id).Updates(Goods{
		Name:  name,
		Price: price,
	}).Error
	if err != nil {
		log.Panicln("update goods error", err)
	}
} 
1
2
3
4
5
6
7
8
9
10
11
12
13

🌙 8.2.3更新选定的字段Select

func UpdateGoods1() {
	goods := Goods{
		Id:         1,
		Name:       "apple",
		Price:      "3.5",
		Type:       1,
		Createtime: time.Now().Unix(),
	}

	err := DB.Model(&goods).Select("name").Updates(goods).Error
	if err != nil {
		log.Panicln("update goods error", err)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

🌙 8.2.4排除更新的字段Omit

func UpdateGoods2() {
    goods := Goods{
        Id:         1,
        Name:       "apple",
        Price:      "3.5",
        Type:       2,
        Createtime: time.Now().Unix(),
    }
    
    // 不更新type, id
    err := DB.Model(&goods).Omit("type", "id").Updates(goods).Error
    if err != nil {
        log.Panicln("update goods error", err)
    }
}

// 组合使用
Select("*").Omit("type")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

gorm更新必须带条件进行更新,否则会返回错误gorm.ErrMissingWhereClause,或者启用AllowGlobalUpdate模式

DB.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "text")
1

🌙 8.2.5使用表达式

 DB.Model(&user).Update("age", gorm.Expr("age+1"))
 DB.Model(&user).Update(map[string]interface{"age": gorm.Expr("age+1")})
1
2

🌙 8.2.6子查询更新

goods := Goods{}
DB.Where("id=?", 1).Take(&goods)
Db.Model(&goods).Update("name", DB.Model(&User{}).Select("username")).Where("id=?", 2) 
1
2
3

🌙 8.3 删除数据

删除数据使用DB.Delete, 一般先查询后删除:

func DeleteGoodById(id int) {
	// 默认直接根据主键ID删除
	DB.Delete(&Goods{}, id)
	
	// 推荐:先查询或删除
	goods := Goods{}
	err := DB.Where("id=?", id).Take(&goods).Error
	if err != nil {
		log.Panicln("delete goods error", err)
		return
	}
	DB.Delete(&goods)
}
1
2
3
4
5
6
7
8
9
10
11
12
13

*同样的道理,不带条件进行删除,必须加一些条件,或者使用原生SQL,或者启用AllowGlobalUpdate模式:

DB.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
1

🌙 8.3.1 gorm中软删除和硬删除

在gorm中,删除数据可以使用Delete方法或Unscoped方法。Delete方法用于软删除,即将记录的DeletedAt字段设置为当前时间。Unscoped方法用于硬删除,即直接从数据库中删除记录。

  • 软删除: 软删除是指在数据库中不会真正删除记录,而是将记录的状态标记为已删除。在GORM中,软删除可以通过添加deleted_at字段来实现,当记录被删除时,GORM会将deleted_at字段设置为当前时间戳,表示该记录已被删除。软删除的优点是可以很容易地恢复已删除的记录,而且不会影响到其他相关的记录。
import (
    "gorm.io/gorm"
)

type User struct {
    gorm.Model
    Name      string
    Email     string
    DeletedAt gorm.DeletedAt // 添加deleted_at字段
}

// 软删除用户
func SoftDeleteUser(db *gorm.DB, id uint) error {
    result := db.Delete(&User{}, id)
    if result.Error != nil {
        return result.Error
    }
    if result.RowsAffected == 0 {
        return gorm.ErrRecordNotFound
    }
    return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

+硬删除: 硬删除是指从数据库中真正删除记录。在GORM中,可以使用Delete方法来执行硬删除操作。硬删除的优点是可以释放数据库空间,但缺点是一旦删除就无法恢复,可能会影响到其他相关的记录。

总的来说,软删除和硬删除各有优缺点,具体使用哪种方式取决于具体的业务需求。

// 硬删除用户
func HardDeleteUser(db *gorm.DB, id uint) error {
    result := db.Unscoped().Delete(&User{}, id)
    if result.Error != nil {
        return result.Error
    }
    if result.RowsAffected == 0 {
        return gorm.ErrRecordNotFound
    }
    return nil
}
1
2
3
4
5
6
7
8
9
10
11

需要注意的是,gorm的软删除和硬删除都不会自动更新关联表中的外键,需要手动更新外键或使用级联删除。

🌙 8.3.2 Mysql软删除和硬删除

+软删除: 在MySQL中,软删除是通过添加一个标志位来实现的,通常会在表中添加一个is_deleted字段,用于标识记录是否被删除。软删除的好处是可以通过修改标志位来还原已删除的记录,但是查询时需要加上WHERE is_deleted = 0的条件。

-- 创建表时添加is_deleted字段
CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  email VARCHAR(50) NOT NULL,
  is_deleted BOOLEAN NOT NULL DEFAULT FALSE
);

-- 软删除用户
UPDATE users SET is_deleted = TRUE WHERE id = 1;
1
2
3
4
5
6
7
8
9
10

+硬删除: 在MySQL中,硬删除是指直接从表中删除记录,使用DELETE语句可以执行硬删除操作。硬删除的好处是可以释放数据库空间,但是一旦删除就无法恢复。

下面是执行硬删除的示例SQL:

-- 硬删除用户
DELETE FROM users WHERE id = 1;
1
2

需要注意的是,硬删除会直接从表中删除记录,如果存在外键关联,可能会导致其他表的记录也被删除,因此在执行硬删除时需要谨慎处理。

🌙 8.4 查询数据

🌙 8.4.1 Take 查询一条记录(单个)

var user Users
DB.Where("id=?", 1).Take(&user).Error 
1
2

🌙 8.4.2 First 查询满足条件的第一条数据(单个)

DB.Where("age>?", 18).First(&user).Error 
1

🌙 8.4.3 Last 查询满足条件的最后一条数据(单个)

DB.Where("age>?", 18).Last(&user).Error 
1

🌙 8.4.4 Find 查询满足条件的多条记录(多个)

var users []Users
DB.Where("age>?", 18).Find(&users)
1
2

🌙 8.4.5 Pluck 查询一列值(多个)

var names []string
// 查询那么列
DB.Model(&Users{}).Pluck("name", &names)
1
2
3

当 First、Last、Take 方法找不到记录时,GORM 会返回 ErrRecordNotFound 错误, 可以通过对比 gorm.ErrRecordNotFound 进行判断 或者使用 Find 和 Limit 的组合进行出查询, 找不到记录时会返回nil。

🌙 8.4.6 Where 条件查询

Where方法可以与其他查询方法一起使用,例如Find、First、Last等。

Where方法接收一个字符串作为查询条件,?表示占位符,后面的参数会替换占位符, 也可以使用map或结构体作为查询条件

var users []User

// 1.直接写条件
db.Where("name = ?", "jinzhu").Find(&users)

// 2.使用map作为条件
db.Where(map[string]interface{}{"name": "jeek", "age": 18}).Find(&users)

// 3.使用结构体作为条件
type UserQuery struct {
    Name string
}
db.Where(&UserQuery{Name: "jinzhu"}).Find(&users) 
1
2
3
4
5
6
7
8
9
10
11
12
13

🌙 8.4.7 Order 指定排序

Order方法用于指定查询结果的排序方式。可以按照一个或多个字段进行排序,也可以指定排序方式(升序或降序)。

var users []User
db.Order("age desc").Find(&users)
db.Order("age desc, name asc").Find(&users)
1
2
3

🌙 8.4.8 Limit 指定查询的数量

可以使用Limit方法限制查询结果的数量,从而实现分页查询或限制查询结果的数量。

Limit方法可以与其他查询方法一起使用,例如Find、First、Last、Where、Order等

var users []User
db.Limit(10).Find(&users) 
1
2

🌙 8.4.9 Offset 指定查询的偏移量

var users []User
db.Offset(10).Find(&users) 
1
2

🌙 8.4.10 分页查询

使用Limit和Offset方法来实现分页查询

var users []User
pageNum := 2
pageSize := 10
offset := (pageNum - 1) * pageSize
db.Limit(pageSize).Offset(offset).Find(&users) 
1
2
3
4
5

分页查询封装成一个通用的方法:

type PageInfo struct {
    PageNum  int     `json:"pageNum"`
    PageSize int     `json:"pageSize"`
    Total    int64   `json:"total"`
}

func Paginate(db *gorm.DB, pageNum int, pageSize int, out interface{}) (PageInfo, error) {
    var count int64
    if err := db.Model(out).Count(&count).Error; err != nil {
        return PageInfo{}, err
    }

    offset := (pageNum - 1) * pageSize
    if err := db.Limit(pageSize).Offset(offset).Find(out).Error; err != nil {
        return PageInfo{}, err
    }

    return PageInfo{
        PageNum:  pageNum,
        PageSize: pageSize,
        Total:    count,
    }, nil
}

// 使用:
var users []User
pageInfo, err := Paginate(db, 1, 10, &users)
if err != nil {
    // 处理错误
}
// 将查询结果和分页信息返回给前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

🌙 8.4.11 Group 指定分组

可以按照一个或多个字段进行分组,也可以使用Group方法进行子查询。

var result []struct {
    Age int
    Count int
}
db.Select("age, count(*) as count").Group("age").Find(&result)
1
2
3
4
5

Group方法只能与Select方法一起使用,不能与其他查询方法一起使用

🌙 8.4.12 Joins 进行联表查询

可以使用Joins方法进行内连接、左连接、右连接、全连接等不同类型的联表查询。

var result []struct {
    Name string
    Email string
}
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result)
1
2
3
4
5

🌙 8.4.13 Scan 查询结果扫描

Scan是用于将查询结果扫描到一个结构体中的方法。它可以将查询结果映射到一个结构体中,或者将查询结果映射到一个结构体的切片中。

import (
    "gorm.io/gorm"
)

type User struct {
    ID    uint
    Name  string
    Email string
}

// 查询所有用户
func GetUsersByRawSQL(db *gorm.DB) ([]User, error) {
    var users []User
    // 使用Raw执行原生sql,使用Scan将结果映射到users切片中
    result := db.Raw("SELECT * FROM users").Scan(&users)
    if result.Error != nil {
        return nil, result.Error
    }
    return users, nil
}

// 将查询结果映射到结构体切片
func ScanUsers() {
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("failed to connect database")
    }
    defer db.Close()

    users, err := GetUsersByRawSQL(db)
    if err != nil {
        panic(err)
    }

    for _, user := range users {
        fmt.Println(user.ID, user.Name, user.Email)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38