【Java转Go】快速上手学习笔记(五)之Gorm篇

go往期文章笔记:

【Java转Go】快速上手学习笔记(一)之环境安装篇

【Java转Go】快速上手学习笔记(二)之基础篇一

【Java转Go】快速上手学习笔记(三)之基础篇二

【Java转Go】快速上手学习笔记(四)之基础篇三


这篇我们来讲讲Go中的orm框架:Gorm

首先我们要先去下载gorm和mysql依赖。

go get命令

1、go get命令无响应问题

例如我下载gorm

go get -u gorm.io/gorm

控制台输出服务器没有响应,连接失败的提示

go: module gorm.io/gorm: Get "https://proxy.golang.org/gorm.io/gorm/@v/list": dial tcp 172.217.163.49:443: connectex: A connection attempt failed because the connected party did not properly respond after
 a period of time, or established connection failed because connected host has failed to respond.

这个时候我们需要设置代理,打开cmd窗口,输入:

go env -w GOPROXY=https://goproxy.cn

执行完后,再去执行go get命令,就可以下载了。

2、Unresolved dependency错误

下载完之后,我们发现在go.mod文件里,require中列出的项目依赖报错,鼠标放上去,提示:Unresolved dependency(未解决的依赖关系)

这时,我们在 File-->Setting ,然后搜索 Go Modules ,勾选 Enable Go modules integration 然后再点击 ok 就可以了。

在这里插入图片描述
设置好后,Gorm依赖就可以正确引入了,那么接下来就是下载数据库的依赖

// mysql 的依赖
go get gorm.io/driver/mysql

// sqlite的依赖
// go get -u gorm.io/driver/sqlite

下载好依赖后,那么go.mod文件的配置应该是

module go-web1

go 1.21

require (
	github.com/go-sql-driver/mysql v1.7.0 // indirect
	github.com/jinzhu/inflection v1.0.0 // indirect
	github.com/jinzhu/now v1.1.5 // indirect
	gorm.io/driver/mysql v1.5.1 // indirect
	gorm.io/gorm v1.25.3 // indirect
)

连接数据库

引入了mysql依赖,接下来我们就可以来连接数据库了。我们先创建一个go文件,用来连接数据库的

连接.go

package sql

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

// 声明全局DB变量,后续可以用这个来操作数据库
var DB *gorm.DB

// 数据库连接
func Init() {
	username := "root"   //账号
	password := "root"   //密码
	host := "127.0.0.1"  //数据库地址,可以是Ip或者域名
	port := 3306         //数据库端口
	Dbname := "gorm_demo" //数据库名
	timeout := "10s"     //连接超时,10秒

	// sql全局日志
	var sqlLogger logger.Interface
	sqlLogger = logger.Default.LogMode(logger.Info)

	// root:root@tcp(127.0.0.1:3306)/gorm?
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)
	//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		// 跳过默认事务:为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这样可以获得60%的性能提升
		//SkipDefaultTransaction: true,
		Logger: sqlLogger,
		NamingStrategy: schema.NamingStrategy{
			TablePrefix:   "sys_", // 表名前缀
			SingularTable: true,   // 单数表名
			NoLowerCase:   false,  // 关闭小写转换
		},
	})
	if err != nil {
		panic("连接数据库失败, error=" + err.Error())
	}
	DB = db
}

写好了连接,接下来就是测试连接是否成功

main.go

package main

import (
	"fmt"
	"go-demo1/sql"
)

func main() {
	sql.Init()
	fmt.Println("连接成功:", sql.DB)
}

操作数据库

数据库连上了,就可以来操作数据库了。

创建表

创建表的话,我们需要一个和数据库表对应的结构体。

/*
字段标签:`gorm:""` 格式,然后在双引号里面设置,多个标签用分号 ; 分隔。
	type 定义字段类型
	size 定义字段大小
	column 自定义列名
	primaryKey 将列定义为主键
	unique 将列定义为唯一键
	default 定义列的默认值
	not null 不可为空
	embedded 嵌套字段
	embeddedPrefix 嵌套字段前缀
	comment 注释
*/
// 创建一个gorm测试结构体
type Gorm struct {
	ID         uint      `gorm:"type:bigint;common:主键"` // 默认使用ID作为主键,gorm会自动识别 ”ID“
	Name       string    `gorm:"type:varchar(20);not null;common:名称"`
	DeptId     int       `gorm:"column:dept_id;type:bigint;common:部门id"`
	DeptName   string    `gorm:"-"`                           // gorm:"-" 表示忽略和数据库表字段的映射,相当于Java里的 transient 或 @TableField(exist = false)
	Email      *string   `gorm:"type:varchar(100);common:邮箱"` // 这里用指针是为了存null值,而不是空字符串
	State      int       `gorm:"type:tinyint(1);common:状态(1 正常 2 禁用)"`
	CreateTime time.Time `gorm:"type:datetime;common:创建时间;default:CURRENT_TIMESTAMP"`
}

// 创建一个role(角色)测试结构体
type Role struct {
	ID         uint
	Name       string
	Key        string
	Remark     *string
	CreateTime time.Time
}

// 创建一个dept(部门)测试结构体
type Dept struct {
	ID         uint
	Name       string
	ParentId   int
	Level      int
	Sort       int
	CreateTime time.Time
}

type Test struct {
	Name     string
	DeptId   int
	DeptName string
	Email    string
}

// 存放分组查询结果
type Group struct {
	DeptId int    // 以部门id进行分组
	Count  int    `gorm:"column:count(id)"`
	Name   string `gorm:"column:group_concat(name)"`
}

func main() {
	sql.Init()
	// 可以放多个
	sql.DB.AutoMigrate(&Gorm{})
}

新增数据

main 函数 里面直接调用 insert() 函数即可

// 数据添加
func insert() {
	// 单条数据添加
	user := Gorm{
		Name:   "符华",
		DeptId: 1,
		State:  1,
	}
	// Create接收的是一个指针,而不是值
	sql.DB.Create(&user)

	// 批量新增,新增10条数据
	var gormList []Gorm
	for i := 0; i < 10; i++ {
		email := "jiqiren" + fmt.Sprintf("%d", i) + "@qq.com"
		gormList = append(gormList, Gorm{
			Name:   fmt.Sprintf("机器人%d号", i+1),
			DeptId: 3,
			State:  2,
			Email:  &email, // 指针类型是为了更好的存null类型,但是传值的时候,也记得传指针
		})
	}
	sql.DB.Create(&gormList)
}

更新数据

// 数据更新
func update() {
	var user Gorm
	email := "fuhua@163.com"
	user.Email = &email
	//更新指定字段
	sql.DB.Model(&Gorm{}).Where("name", "符华").Update("name", "云墨")
	sql.DB.Model(&Gorm{}).Where(1).Updates(Gorm{Name: "符华", Email: &email}) // 是结构体,默认不会更新零值
	sql.DB.Model(&Gorm{}).Where("name = ?", "机器人1号").Updates(Gorm{DeptId: 4, Email: &email, State: 1})

	// Save()会更新全部字段,即使字段值是零值也会更新
	sql.DB.Save(&user)                        // 更新全部字段
	sql.DB.Select("email").Save(&user) // 如果不想更新所有字段,可以通过Select指定要更新的字段
	// Omit() 可以忽略字段
}

删除数据

// 数据删除
func delete() {
	var user Gorm
	//根据主键ID删除
	sql.DB.Delete(&user, 11) // DELETE FROM `sys_gorm` WHERE `sys_gorm`.`id` = 1
	sql.DB.Delete(&Gorm{}, []int{1,2,3})
	// 根据其他条件删除
	sql.DB.Where("name like ?", "%李四%").Delete(Gorm{}) // DELETE FROM `sys_gorm` WHERE name like '%李四%'
	sql.DB.Debug().Delete(Gorm{}, "name like ?", "%李四%") // DELETE FROM `sys_gorm` WHERE name like '%李四%'
	// 如果模型有DeletedAt字段,将自动获得软删除功能! 在调用Delete时不会从数据库中永久删除,而是只将字段DeletedAt的值设置为当前时间。软删除的记录将在查询时被忽略。
	//使用Unscoped查找软删除的记录
	//sql.DB.Unscoped().Where("name=?", "张三").Find(&user)
	// 使用Unscoped永久删除记录
	//sql.DB.Unscoped().Delete(&user)

	// 删除查询到的切片列表
	var gormList []Gorm
	sql.DB.Delete(&gormList)
}

查询数据

单表查询

查询数据比较多,分为单表查询和多表查询,下面这个 query1() 是单表查询的方法。

除了基本的根据各条件查询,还有去重、分组、子查询和分页查询,然后还有可以把A结构体的查询结果存入B结构体,或者是直接把查询结果放入map中。

// 数据查询(单表)
func query1() {
	// 查询单条数据
	//var user Gorm
	//var test Test
	//sql.DB.Take(&user) // SELECT * FROM `sys_gorm` LIMIT 1
	//fmt.Println(user)
	//sql.DB.First(&user) // SELECT * FROM `sys_gorm` ORDER BY `id` LIMIT 1
	//fmt.Println(user)
	//sql.DB.Last(&user) // SELECT * FROM `sys_gorm` ORDER BY `id` DESC LIMIT 1
	//fmt.Println(user)

	// 根据主键查询,Take的第二个参数,默认会根据主键查询,可以是字符串,可以是数字
	//sql.DB.Take(&user, 2)
	//fmt.Println(user)
	//user = Gorm{} // 重新赋值
	//sql.DB.Take(&user, "4")
	//fmt.Println(user)

	// 根据其他条件查询
	//sql.DB.Take(&user, "name = ?", "机器人7号") // 使用 ? 号作为占位符,将查询内容放入 ?
	//sql.DB.Where(&Gorm{Name: "机器人10号"}).First(&user)
	//fmt.Println(user)

	// 根据struct查询
	//user.ID = 2
	//user.Name = "机器人1号"
	//sql.DB.Take(&user)
	//data, _ := json.Marshal(user) // 由于email是指针类型,所以看不到实际的内容,但是序列化之后,会转换为我们可以看得懂的方式
	//fmt.Println(string(data))
	//fmt.Println(user)

	// 通过 Scan 方法,可以将查询的结果存入另一个结构体中
	//sql.DB.Select("name", "dept_id").Find(&user).Scan(&test) // 这种方式会查询两次,不推荐使用
	//sql.DB.Model(&Gorm{}).Select("name", "dept_id").Scan(&test) // 这种方式就只查询一次了
	//sql.DB.Table("sys_gorm").Select("name", "dept_id").Scan(&test) // 这种方式也是只查询一次
	//fmt.Println(test)

	// 根据map查询
	//sql.DB.Where(map[string]any{"name": "符华", "dept_id": 1}).Find(&user)
	//fmt.Println(user)

	// 查询多条
	//var gormList []Gorm
	//var testList []Test
	//var count int64 // 获取查询结果的总数量
	//sql.DB.Find(&gormList)
	//for _, u := range gormList {
	//	data, _ := json.Marshal(u) // 序列化
	//	fmt.Println(string(data))
	//}

	// 根据多个主键查询
	//sql.DB.Not([]int64{4, 5, 6}).First(&gormList) // not in 主键
	//sql.DB.Find(&gormList, []int{1, 3, 5, 7})
	//sql.DB.Find(&gormList, []string{"1", "3", "5", "7"})
	//sql.DB.Find(&gormList, 1, 3, 5, 7) // 一样的
	//sql.DB.Find(&gormList, "1", "3", "5", "7")
	//fmt.Println(gormList)
	// 根据多个其他条件查询
	//sql.DB.Find(&gormList, "name in ?", []string{"符华", "机器人10号"})
	//sql.DB.Where("name in (?) ", []string{"机器人1号", "机器人9号"}).Find(&gormList) // in
	//sql.DB.Where("name<>?", "机器人10号").Find(&gormList) // 不等于
	//sql.DB.Where("name like ?", "%机器人%").Find(&gormList) // like
	//sql.DB.Where("name =? and email=?", "机器人10号", "jiqiren@qq.com").Find(&gormList) // and
	//sql.DB.Where("name =?", "机器人10号").Where("email=? ", "jiqiren@qq.com").Find(&gormList) // and
	//sql.DB.Where("name=?", "符华").Or("email=?", "jiqiren1@qq.com").Find(&gormList)         // or
	//sql.DB.Where(Gorm{Name: "符华"}).Or(Gorm{DeptId: 1}).Find(&gormList) // or
	//sql.DB.Not("name", "符华").Find(&gormList)                                              // not条件查询
	//sql.DB.Not("name", []string{"机器人1号", "机器人9号"}).Find(&gormList).Count(&count)       // not in
	//sql.DB.Debug().Order("id desc").Find(&gormList).Count(&count) // order by排序
	//fmt.Println("查询的总数量:", count)
	//fmt.Println("查询的结果集:", gormList)

	// 查询指定的列(使用Select会查询两次,分别用于获取指定的字段和获取完整的模型数据。)
	//sql.DB.Select("name", "email").Find(&gormList, "name in ?", []string{"符华", "机器人10号"}).Count(&count)
	//sql.DB.Select([]string{"name", "email"}).Find(&gormList) // 一样的效果
	//fmt.Println(count, gormList)

	// 使用原生sql查询
	//sql.DB.Raw("SELECT id, name FROM sys_gorm WHERE id = ?", 1).Find(&gormList)
	//fmt.Println(gormList)

	// 去重
	//var emailList []string
	//sql.DB.Table("sys_gorm").Select("email").Distinct("email").Scan(&emailList)
	//sql.DB.Table("sys_gorm").Select("distinct email").Scan(&emailList)
	//fmt.Println(emailList)

	// 分组查询
	//var idList []string
	//sql.DB.Table("sys_gorm").Select("count(id)").Group("dept_id").Scan(&idList) // 根据部门id进行分组
	//fmt.Println(idList)
	//var groupList []Group
	//sql.DB.Table("sys_gorm").Select("count(id)", "dept_id", "group_concat(name)").Group("dept_id").Scan(&groupList) // 根据部门名称进行分组
	//fmt.Println(groupList)

	// 查询结果查询到map中
	//var res []map[string]any
	//sql.DB.Table("sys_gorm").Find(&res)
	//fmt.Println(res)

	// 分页
	//pageSize := 5                      // 每次查询条数
	//pageNum := 2                       // 当前页
	//offset := (pageNum - 1) * pageSize // 计算跳过的记录数
	//sql.DB.Offset(offset).Limit(pageSize).Find(&gormList) // 分页查询,根据offset和limit来查询
	//sql.DB.Offset(offset).Limit(pageSize).Find(&gormList).Scan(&testList) // 通过Scan方法,将查询的结果集存入另一个list
	//sql.DB.Model(&Gorm{}).Count(&count)                                   // 获取总数量(这个数量不是每一页的数量)
	//fmt.Println("查询的总数量:", count)
	//fmt.Println("查询的结果集gormList:", gormList)
	//fmt.Println("查询的结果集testList:", testList)

	// 子查询
	//var gromList []Gorm
	// SELECT * FROM `sys_gorm` WHERE id in (SELECT `id` FROM `sys_gorm` WHERE `state` = 2)
	//sql.DB.Model(Gorm{}).Where("id in (?)", sql.DB.Model(Gorm{}).Select("id").Where("state", 2)).Find(&gromList)
}

多表查询

多表关联查询:根据grom表的deptId字段,关联dept表,然后还需要关联角色表查询(一个用户可以有多个角色)

Gorm结构体我们需要改一下:

// 创建一个gorm测试结构体
type Gorm struct {
	ID         uint      `gorm:"type:bigint;common:主键"` // 默认使用ID作为主键,gorm会自动识别 ”ID“
	Name       string    `gorm:"type:varchar(20);not null;common:名称"`
	DeptId     int       `gorm:"column:dept_id;type:bigint;common:部门id"`
	DeptName   string    `gorm:"-"`                           // gorm:"-" 表示忽略和数据库表字段的映射,相当于Java里的 transient 或 @TableField(exist = false)
	Email      *string   `gorm:"type:varchar(100);common:邮箱"` // 这里用指针是为了存null值,而不是空字符串
	State      int       `gorm:"type:tinyint(1);common:状态(1 正常 2 禁用)"`
	CreateTime time.Time `gorm:"type:datetime;common:创建时间;default:CURRENT_TIMESTAMP"`
	Dept       Dept      `gorm:"foreignKey:DeptId"` // 使用 foreignKey 并不会要求在数据库表里面有这个外键约束,这个标签仅仅是用来指定在进行关联查询时应该使用的外键字段的名字。
	Roles      []*Role   `gorm:"many2many:user_role;"`
}

Roles []*Role gorm:"many2many:user_role;" 这一句中的 user_role 就是用户和角色的关联关系表。

// 数据查询(多表关联)
func query2() {
	var gormList []Gorm
	// 关联查询部门
	//sql.DB.Preload("Dept").Find(&gormList)
	//for _, u := range gormList {
	//	fmt.Printf("用户 %v 所在部门是 %v\n", u.Name, u.Dept.Name)
	//}

	// 关联查询角色
	//var user Gorm
	//sql.DB.Preload("Roles").First(&user)
	//for _, role := range user.Roles {
	//	fmt.Println(user.Name, "的角色是", role.Name)
	//}

	// 多个关联
	sql.DB.Preload("Dept").Preload("Roles").Find(&gormList)
	for _, u := range gormList {
		var role string
		for i, r := range u.Roles {
			if i > 0 {
				role += ","
			}
			role += r.Name
		}
		fmt.Printf("用户 %v 所在部门是 %v,她的角色是 [%v]\n", u.Name, u.Dept.Name, role)
	}
}

用到的数据库表

DROP TABLE IF EXISTS `sys_dept`;
CREATE TABLE `sys_dept`  (
  `id` bigint NOT NULL,
  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `parent_id` bigint NULL DEFAULT NULL COMMENT '父部门id',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '部门名称',
  `level` tinyint(1) NULL DEFAULT NULL COMMENT '层级(1 根目录 2 单位 3 部门)',
  `sort` bigint NULL DEFAULT NULL COMMENT '序号',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `name`(`name` ASC) USING BTREE,
  INDEX `parent_id`(`parent_id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '部门管理' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES (1, '2023-08-18 15:39:57', 0, '太虚派', 1, 1);
INSERT INTO `sys_dept` VALUES (2, '2023-08-18 15:40:00', 0, '天命科技有限公司', 1, 2);
INSERT INTO `sys_dept` VALUES (3, '2023-08-18 15:40:57', 1, '心部', 2, 1);
INSERT INTO `sys_dept` VALUES (4, '2023-08-18 15:41:07', 1, '形部', 2, 2);
INSERT INTO `sys_dept` VALUES (5, '2023-08-18 15:44:19', 1, '意部', 2, 3);
INSERT INTO `sys_dept` VALUES (6, '2023-08-18 15:44:22', 1, '魂部', 2, 4);
INSERT INTO `sys_dept` VALUES (7, '2023-08-18 15:44:28', 1, '神部', 2, 5);
INSERT INTO `sys_dept` VALUES (8, '2023-08-18 15:44:58', 2, '天命总部', 2, 1);
INSERT INTO `sys_dept` VALUES (9, '2023-08-18 15:45:00', 2, '极东支部', 2, 2);

-- ----------------------------
-- Table structure for sys_gorm
-- ----------------------------
DROP TABLE IF EXISTS `sys_gorm`;
CREATE TABLE `sys_gorm`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `dept_id` bigint NULL DEFAULT NULL,
  `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `state` tinyint(1) NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `DeptId`(`dept_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 12 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_gorm
-- ----------------------------
INSERT INTO `sys_gorm` VALUES (1, '符华', 1, 'fuhua@163.com', 1, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (2, '机器人1号', 3, 'jiqiren0@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (3, '机器人2号', 3, 'jiqiren1@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (4, '机器人3号', 3, 'jiqiren2@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (5, '机器人4号', 3, 'jiqiren3@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (6, '机器人5号', 3, 'jiqiren4@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (7, '机器人6号', 3, 'jiqiren5@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (8, '机器人7号', 3, 'jiqiren6@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (9, '机器人8号', 3, 'jiqiren7@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (10, '机器人9号', 3, 'jiqiren8@qq.com', 2, '2023-08-18 16:02:44');
INSERT INTO `sys_gorm` VALUES (11, '机器人10号', 3, 'jiqiren9@qq.com', 2, '2023-08-18 16:02:44');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` bigint NOT NULL COMMENT '角色ID',
  `key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色权限字符',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色名称',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
  `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色管理' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'CJGLY', '超级管理员', NULL, '2022-09-14 14:25:07');
INSERT INTO `sys_role` VALUES (2, 'PTYH', '普通用户', NULL, '2022-09-14 14:38:35');
INSERT INTO `sys_role` VALUES (3, 'BMFZR', '部门负责人', NULL, '2023-08-18 15:58:15');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `gorm_id` bigint NOT NULL,
  `role_id` bigint NOT NULL,
  PRIMARY KEY (`gorm_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);
INSERT INTO `sys_user_role` VALUES (1, 3);
INSERT INTO `sys_user_role` VALUES (2, 2);
INSERT INTO `sys_user_role` VALUES (3, 2);
INSERT INTO `sys_user_role` VALUES (4, 2);
INSERT INTO `sys_user_role` VALUES (5, 3);
INSERT INTO `sys_user_role` VALUES (6, 2);
INSERT INTO `sys_user_role` VALUES (7, 3);
INSERT INTO `sys_user_role` VALUES (8, 3);
INSERT INTO `sys_user_role` VALUES (9, 2);

原生SQL

关于原生sql

Go中,执行原生sql是用 Raw() 方法,然后它好像不支持从外部文件调用sql语句。(找了下没找到相关资料)

也就是说不能像Java那样,有专门的xml来放原生sql,然后用到的时候直接调用对应的方法就能执行方法里面的sql。(SQL和业务代码分离,易于维护)

如果只是较简单的sql还好,但是实际项目中,碰到的业务查询是不可能只有一两张表、两三张表关联查询就能解决的。

关联的表很多、子查询很多、逻辑复杂,往往需要通过原生sql来实现。

然后我目前想到一个方案,就是能不能将sql以字符串的形式,放到变量里,然后这些存放sql语句的变量,统一写在go文件中(相当于Java的一个xml)

  • 不同业务的go文件放在不同包中,比如:用户相关的放在user包里,部门相关放在dept包里(实际项目中,不同业务肯定是要分包的,不会全部放在一个包或一个文件中,不然的话会很乱)

  • 当我们需要用到某个sql,通过包名点出存放要执行的sql的变量,然后放到 Raw 中执行,这样是不是就可以实现 SQL和业务代码分离?

  • 比如:user 包下有个 sql.go,这个go里面有个存放查询用户列表sql的变量叫 queryUserList,当要查询用户列表时:db.Raw(user.queryUserList) 这样来实现。

当然以上只是我一个想法,因为我也是刚入门Go语言,不知道还没有更好的办法可以实现 SQL和业务代码分离

完整代码

main.go 的完整代码 ,大家可自行复制测试。

package main

import (
	"fmt"
	"go-demo1/sql"
	"time"
)

/*
字段标签:`gorm:""` 格式,然后在双引号里面设置,多个标签用分号 ; 分隔。
	type 定义字段类型
	size 定义字段大小
	column 自定义列名
	primaryKey 将列定义为主键
	unique 将列定义为唯一键
	default 定义列的默认值
	not null 不可为空
	embedded 嵌套字段
	embeddedPrefix 嵌套字段前缀
	comment 注释
*/
// 创建一个gorm测试结构体
type Gorm struct {
	ID         uint      `gorm:"type:bigint;common:主键"` // 默认使用ID作为主键,gorm会自动识别 ”ID“
	Name       string    `gorm:"type:varchar(20);not null;common:名称"`
	DeptId     int       `gorm:"column:dept_id;type:bigint;common:部门id"`
	DeptName   string    `gorm:"-"`                           // gorm:"-" 表示忽略和数据库表字段的映射,相当于Java里的 transient 或 @TableField(exist = false)
	Email      *string   `gorm:"type:varchar(100);common:邮箱"` // 这里用指针是为了存null值,而不是空字符串
	State      int       `gorm:"type:tinyint(1);common:状态(1 正常 2 禁用)"`
	CreateTime time.Time `gorm:"type:datetime;common:创建时间;default:CURRENT_TIMESTAMP"`
	Dept       Dept      `gorm:"foreignKey:DeptId"` // 使用 foreignKey 并不会要求在数据库表里面有这个外键约束,这个标签仅仅是用来指定在进行关联查询时应该使用的外键字段的名字。
	Roles      []*Role   `gorm:"many2many:user_role;"`
}

// 创建一个role(角色)测试结构体
type Role struct {
	ID         uint
	Name       string
	Key        string
	Remark     *string
	CreateTime time.Time
}

// 创建一个dept(部门)测试结构体
type Dept struct {
	ID         uint
	Name       string
	ParentId   int
	Level      int
	Sort       int
	CreateTime time.Time
}

type Test struct {
	Name     string
	DeptId   int
	DeptName string
	Email    string
}

// 存放分组查询结果
type Group struct {
	DeptId int    // 以部门id进行分组
	Count  int    `gorm:"column:count(id)"`
	Name   string `gorm:"column:group_concat(name)"`
}

func main() {
	sql.Init()
	//fmt.Println("连接成功:", sql.DB)
	// 可以放多个
	//sql.DB.AutoMigrate(&Gorm{})

	//insert() // 新增数据
	//update() // 更新数据
	//delete() // 删除数据
	//query1() // 查询数据
	query2() // 查询数据
}

// 数据添加
func insert() {
	// 单条数据添加
	user := Gorm{
		Name:   "符华",
		DeptId: 1,
		State:  1,
	}
	// Create接收的是一个指针,而不是值
	sql.DB.Create(&user)

	// 批量新增,新增10条数据
	var gormList []Gorm
	for i := 0; i < 10; i++ {
		email := "jiqiren" + fmt.Sprintf("%d", i) + "@qq.com"
		gormList = append(gormList, Gorm{
			Name:   fmt.Sprintf("机器人%d号", i+1),
			DeptId: 3,
			State:  2,
			Email:  &email, // 指针类型是为了更好的存null类型,但是传值的时候,也记得传指针
		})
	}
	sql.DB.Create(&gormList)
}

// 数据更新
func update() {
	//var user Gorm
	//email := "fuhua@163.com"
	//user.Email = &email
	//更新指定字段
	//sql.DB.Model(&Gorm{}).Where("name", "符华").Update("name", "云墨")
	//sql.DB.Model(&Gorm{}).Where(1).Updates(Gorm{Name: "符华", Email: &email}) // 是结构体,默认不会更新零值
	//sql.DB.Model(&Gorm{}).Where("name = ?", "机器人1号").Updates(Gorm{DeptId: 4, Email: &email, State: 1})

	// Save()会更新全部字段,即使字段值是零值也会更新
	//sql.DB.Save(&user)                        // 更新全部字段
	//sql.DB.Select("email").Save(&user) // 如果不想更新所有字段,可以通过Select指定要更新的字段
	// Omit() 可以忽略字段
}

// 数据删除
func deletes() {
	//var user Gorm
	//根据主键ID删除
	//sql.DB.Delete(&user, 11) // DELETE FROM `sys_gorm` WHERE `sys_gorm`.`id` = 1
	//sql.DB.Delete(&Gorm{}, []int{1,2,3})
	// 根据其他条件删除
	//sql.DB.Where("name like ?", "%李四%").Delete(Gorm{}) // DELETE FROM `sys_gorm` WHERE name like '%李四%'
	//sql.DB.Debug().Delete(Gorm{}, "name like ?", "%李四%") // DELETE FROM `sys_gorm` WHERE name like '%李四%'
	// 如果模型有DeletedAt字段,将自动获得软删除功能! 在调用Delete时不会从数据库中永久删除,而是只将字段DeletedAt的值设置为当前时间。软删除的记录将在查询时被忽略。
	//使用Unscoped查找软删除的记录
	//sql.DB.Unscoped().Where("name=?", "张三").Find(&user)
	// 使用Unscoped永久删除记录
	//sql.DB.Unscoped().Delete(&user)

	// 删除查询到的切片列表
	//var gormList []Gorm
	//sql.DB.Delete(&gormList)
}

// 数据查询(单表)
func query1() {
	// 查询单条数据
	//var user Gorm
	//var test Test
	//sql.DB.Take(&user) // SELECT * FROM `sys_gorm` LIMIT 1
	//fmt.Println(user)
	//sql.DB.First(&user) // SELECT * FROM `sys_gorm` ORDER BY `id` LIMIT 1
	//fmt.Println(user)
	//sql.DB.Last(&user) // SELECT * FROM `sys_gorm` ORDER BY `id` DESC LIMIT 1
	//fmt.Println(user)

	// 根据主键查询,Take的第二个参数,默认会根据主键查询,可以是字符串,可以是数字
	//sql.DB.Take(&user, 2)
	//fmt.Println(user)
	//user = Gorm{} // 重新赋值
	//sql.DB.Take(&user, "4")
	//fmt.Println(user)

	// 根据其他条件查询
	//sql.DB.Take(&user, "name = ?", "机器人7号") // 使用 ? 号作为占位符,将查询内容放入 ?
	//sql.DB.Where(&Gorm{Name: "机器人10号"}).First(&user)
	//fmt.Println(user)

	// 根据struct查询
	//user.ID = 2
	//user.Name = "机器人1号"
	//sql.DB.Take(&user)
	//data, _ := json.Marshal(user) // 由于email是指针类型,所以看不到实际的内容,但是序列化之后,会转换为我们可以看得懂的方式
	//fmt.Println(string(data))
	//fmt.Println(user)

	// 通过 Scan 方法,可以将查询的结果存入另一个结构体中
	//sql.DB.Select("name", "dept_id").Find(&user).Scan(&test) // 这种方式会查询两次,不推荐使用
	//sql.DB.Model(&Gorm{}).Select("name", "dept_id").Scan(&test) // 这种方式就只查询一次了
	//sql.DB.Table("sys_gorm").Select("name", "dept_id").Scan(&test) // 这种方式也是只查询一次
	//fmt.Println(test)

	// 根据map查询
	//sql.DB.Where(map[string]any{"name": "符华", "dept_id": 1}).Find(&user)
	//fmt.Println(user)

	// 查询多条
	//var gormList []Gorm
	//var testList []Test
	//var count int64 // 获取查询结果的总数量
	//sql.DB.Find(&gormList)
	//for _, u := range gormList {
	//	data, _ := json.Marshal(u) // 序列化
	//	fmt.Println(string(data))
	//}

	// 根据多个主键查询
	//sql.DB.Not([]int64{4, 5, 6}).First(&gormList) // not in 主键
	//sql.DB.Find(&gormList, []int{1, 3, 5, 7})
	//sql.DB.Find(&gormList, []string{"1", "3", "5", "7"})
	//sql.DB.Find(&gormList, 1, 3, 5, 7) // 一样的
	//sql.DB.Find(&gormList, "1", "3", "5", "7")
	//fmt.Println(gormList)
	// 根据多个其他条件查询
	//sql.DB.Find(&gormList, "name in ?", []string{"符华", "机器人10号"})
	//sql.DB.Where("name in (?) ", []string{"机器人1号", "机器人9号"}).Find(&gormList) // in
	//sql.DB.Where("name<>?", "机器人10号").Find(&gormList) // 不等于
	//sql.DB.Where("name like ?", "%机器人%").Find(&gormList) // like
	//sql.DB.Where("name =? and email=?", "机器人10号", "jiqiren@qq.com").Find(&gormList) // and
	//sql.DB.Where("name =?", "机器人10号").Where("email=? ", "jiqiren@qq.com").Find(&gormList) // and
	//sql.DB.Where("name=?", "符华").Or("email=?", "jiqiren1@qq.com").Find(&gormList)         // or
	//sql.DB.Where(Gorm{Name: "符华"}).Or(Gorm{DeptId: 1}).Find(&gormList) // or
	//sql.DB.Not("name", "符华").Find(&gormList)                                              // not条件查询
	//sql.DB.Not("name", []string{"机器人1号", "机器人9号"}).Find(&gormList).Count(&count)       // not in
	//sql.DB.Debug().Order("id desc").Find(&gormList).Count(&count) // order by排序
	//fmt.Println("查询的总数量:", count)
	//fmt.Println("查询的结果集:", gormList)

	// 查询指定的列(使用Select会查询两次,分别用于获取指定的字段和获取完整的模型数据。)
	//sql.DB.Select("name", "email").Find(&gormList, "name in ?", []string{"符华", "机器人10号"}).Count(&count)
	//sql.DB.Select([]string{"name", "email"}).Find(&gormList) // 一样的效果
	//fmt.Println(count, gormList)

	// 使用原生sql查询
	//sql.DB.Raw("SELECT id, name FROM sys_gorm WHERE id = ?", 1).Find(&gormList)
	//fmt.Println(gormList)

	// 去重
	//var emailList []string
	//sql.DB.Table("sys_gorm").Select("email").Distinct("email").Scan(&emailList)
	//sql.DB.Table("sys_gorm").Select("distinct email").Scan(&emailList)
	//fmt.Println(emailList)

	// 分组查询
	//var idList []string
	//sql.DB.Table("sys_gorm").Select("count(id)").Group("dept_id").Scan(&idList) // 根据部门id进行分组
	//fmt.Println(idList)
	//var groupList []Group
	//sql.DB.Table("sys_gorm").Select("count(id)", "dept_id", "group_concat(name)").Group("dept_id").Scan(&groupList) // 根据部门名称进行分组
	//fmt.Println(groupList)

	// 查询结果查询到map中
	//var res []map[string]any
	//sql.DB.Table("sys_gorm").Find(&res)
	//fmt.Println(res)

	// 分页
	//pageSize := 5                      // 每次查询条数
	//pageNum := 2                       // 当前页
	//offset := (pageNum - 1) * pageSize // 计算跳过的记录数
	//sql.DB.Offset(offset).Limit(pageSize).Find(&gormList) // 分页查询,根据offset和limit来查询
	//sql.DB.Offset(offset).Limit(pageSize).Find(&gormList).Scan(&testList) // 通过Scan方法,将查询的结果集存入另一个list
	//sql.DB.Model(&Gorm{}).Count(&count)                                   // 获取总数量(这个数量不是每一页的数量)
	//fmt.Println("查询的总数量:", count)
	//fmt.Println("查询的结果集gormList:", gormList)
	//fmt.Println("查询的结果集testList:", testList)

	// 子查询
	//var gromList []Gorm
	// SELECT * FROM `sys_gorm` WHERE id in (SELECT `id` FROM `sys_gorm` WHERE `state` = 2)
	//sql.DB.Model(Gorm{}).Where("id in (?)", sql.DB.Model(Gorm{}).Select("id").Where("state", 2)).Find(&gromList)
}

// 数据查询(多表关联)
func query2() {
	var gormList []Gorm
	// 关联查询部门
	//sql.DB.Preload("Dept").Find(&gormList)
	//for _, u := range gormList {
	//	fmt.Printf("用户 %v 所在部门是 %v\n", u.Name, u.Dept.Name)
	//}

	// 关联查询角色
	//var user Gorm
	//sql.DB.Preload("Roles").First(&user)
	//for _, role := range user.Roles {
	//	fmt.Println(user.Name, "的角色是", role.Name)
	//}

	// 多个关联
	sql.DB.Preload("Dept").Preload("Roles").Find(&gormList)
	for _, u := range gormList {
		var role string
		for i, r := range u.Roles {
			if i > 0 {
				role += ","
			}
			role += r.Name
		}
		fmt.Printf("用户 %v 所在部门是 %v,她的角色是 [%v]\n", u.Name, u.Dept.Name, role)
	}
}

ok,以上就是本篇笔记的全部内容啦~如果你觉得有用,欢迎点个大拇指😘

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

符华-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值