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

news2024/12/25 9:03:45

目录

  • go get命令
    • 1、go get命令无响应问题
    • 2、Unresolved dependency错误
  • 连接数据库
    • 连接.go
    • main.go
  • 操作数据库
    • 创建表
    • 新增数据
    • 更新数据
    • 删除数据
    • 查询数据
      • 单表查询
      • 多表查询
    • 用到的数据库表
    • 原生SQL
  • 完整代码

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,以上就是本篇笔记的全部内容啦~如果你觉得有用,欢迎点个大拇指😘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/910778.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python-docx把dataframe表格添加到word文件中

python-docx把dataframe表格添加到word文件中思路较为简单&#xff1a; 先把dataframe格式转变为table新建一个段落&#xff1a;document.add_paragraph()把table添加到这个段落下方 效果图 示例代码 from docx import Document, oxml import pandas as pd import numpy as …

什么是安全测试报告,怎么获得软件安全检测报告?

安全测试报告 软件安全测试报告&#xff1a;是指测试人员对软件产品的安全缺陷和非法入侵防范能力进行检查和验证的过程&#xff0c;并对软件安全质量进行整体评估&#xff0c;发现软件的缺陷与 bug&#xff0c;为开发人员修复漏洞、提高软件质量奠定坚实的基础。 怎么获得靠谱…

单链表制作通讯录

1 问题 通讯录具有输入信息&#xff0c;显示信息&#xff0c;查找以姓名作为关键字&#xff0c;删除信息。 2 方法 实现一个基于面向对象思想的&#xff0c;具有添加、查看、删除、修改、查找、保存数据功能的通讯录程序。 class Node: def __init__(self, name, phone_numb…

HCIP的交换机(STP,VRRP)实验

题目 拓扑图 链路聚合 LSW1 [lsw3]interface Eth-Trunk 1 [lsw3-Eth-Trunk1]trunkport GigabitEthernet 0/0/3 0/0/4 [lsw3-Eth-Trunk1]q [lsw3]vlan batch 1 2 [lsw3]interface Eth-Trunk 1 [lsw3-Eth-Trunk1]port link-type trunk [lsw3-Eth-Trunk1]port trunk allow-pass…

多线程与并行计算(python与c++)基础入门篇

目录 1.简介 1.1什么事多线程 1.2概念 2.python多线程基础知识讲解 创建线程&#xff1a; 启动线程&#xff1a; 线程函数/方法&#xff1a; 线程同步&#xff1a; 线程间通信&#xff1a; 线程结束与等待&#xff1a; 线程常用方法&#xff1a; 3.c多线程基础知识…

无涯教程-PHP - ereg()函数

ereg() - 语法 int ereg(string pattern, string originalstring, [array regs]); ereg()函数在string指定的字符串中搜索pattern指定的字符串&#xff0c;如果找到pattern&#xff0c;则返回true&#xff0c;否则返回false。搜索对于字母字符区分大小写。 可选的输入参数re…

拒绝摆烂!C语言练习打卡第五天

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;每日一练 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、选择题 &#x1f4dd;1.第一题 &#x1f4dd;2.第二题 &#x1f4d…

论文复现--关于yolov8在姿态估计方面的研究

分类&#xff1a;动作捕捉 github地址&#xff1a;https://github.com/ultralytics/ultralytics 所需环境&#xff1a; Windows10&#xff0c;CUDA11.6&#xff0c;conda 4.13.0&#xff0c;Visual Studio 2017&#xff1b; 目录 环境搭建Conda list使用测试 环境搭建 # 创建环…

企业出海营销:如何借助HubSpot将潜在用户引导为忠实客户

在全球化的商业环境中&#xff0c;企业出海营销已成为实现可持续增长的重要策略之一。然而&#xff0c;成功吸引潜在客户并将其转化为忠实客户并非易事。 这正是HubSpot这款强大的综合性平台发挥作用的时候&#xff0c;它不仅帮助企业引导潜在客户进入销售漏斗&#xff0c;还在…

PyQty5—第一课:安装及简单的界面设计(附代码)

在日常办公中我们将经常使用的代码与PyQty5进行配合&#xff0c;从而设计出GUI的小程序&#xff0c;这样不经界面好看而且可以进行打包&#xff0c;发给自己的亲朋好友们进行使用&#xff0c;那么今天我们就来进行PyQty5的第一节课 1、首先我们需要安装PyQty5的库 库名安装si…

Jmeter压测 —— 动态数据关联

在进行压测时&#xff0c;经常需要对一些动态的数据进行数据关联。要不然容易造成脚本回放失败&#xff0c;或者多个虚拟用户执行时&#xff0c;只有一个用户运行成功。如下图&#xff1a;未关联数据时&#xff0c;脚本参数及回放结果 因此&#xff0c;在进行压测时&#xff0c…

opencv 进阶15-检测DoG特征并提取SIFT描述符cv2.SIFT_create()

前面我们已经了解了Harris函数来进行角点检测&#xff0c;因为角点的特性&#xff0c;这些角点在图像旋转的时候也可以被检测到。但是&#xff0c;如果我们放大或缩小图像时&#xff0c;就可能会丢失图像的某些部分&#xff0c;甚至有可能增加角点的质量。这种损失的现象需要一…

纯js封装一个弹出窗口

先上效果图&#xff1a; 左图是默认的样式(默认标题是黑色的。不是橙色的。截图时我改了点东西所以变了色。。。)。右图是通过传递参数自定义了外观的样式。 封装实现&#xff1a; function showWindow() {this.rnd Math.random();this.obj null;this.title ;this.content …

NER(命名实体识别)的介绍与使用--附源码

概述 本文将向您简要介绍命名实体识别,这是一种用于识别文本文档中存在的实体的流行方法。本文针对 NLP 领域的初学者。在本文末尾,已经实现了预训练的 NER 模型来展示实际用例。 为什么是NER? 图1 通过观察上

排序算法之详解选择排序

引入 选择排序顾名思义是需要进行选择的&#xff0c;那么就要问题了&#xff0c;选择到底是选择什么呢?选择排序的选择是选择数组中未排序的数组中最小的值&#xff0c;将被选择的元素放在未排序数组的首位 如果你对 ‘未排序数组’ &#xff0c; ‘选择’ 的概念不理解&#…

C++中机器人应用程序的行为树(ROS2)

马库斯布赫霍尔茨 一、说明 以下文章为您提供了对机器人应用程序或框架中经常使用的行为树的一般直觉&#xff1a;ROS&#xff0c;Moveit和NAV2。了解行为 Tress &#xff08;BT&#xff09; 框架的原理为您提供了在游戏领域应用知识的绝佳机会。BT可以与Unity或Unreal集成。 由…

淘宝有哪些值得抓取的数据?通过API可以获取到哪些数据?

淘宝是中国最大的在线购物平台&#xff0c;有丰富的数据可供抓取。以下是一些值得抓取的数据以及通过API可以获取到的数据&#xff1a; 1. 商品信息&#xff1a;包括商品标题、价格、销量、评价等。 2. 店铺信息&#xff1a;包括店铺名称、店铺信用、开店时间等。 3. 物流信…

java测试抽红包接口概率《搬代码》

1.首先我们进行抽红包概率测试的时候&#xff0c;为了测试抽取红包的概率完全正确。个人建议不使用线程池多线程执行&#xff0c;建议直接使用for循环执行 2.我们调取抽红包接口返回的信息值进行判断 3.下面就是判断代码&#xff0c;然后是抽红包接口代码 Test public static …

探索智能文字识别:技术、应用与发展前景

探索智能文字识别&#xff1a;技术、应用与发展前景 前言一张图全览大赛作品解读随心记你不对我对小结 智能文字识别体系化解读图像预处理文字定位和分割文字区域识别图像校正字体识别和匹配结果后处理小结 如何应对复杂场景下挑战复杂场景应对方法小结 人才时代对人才要求合合…

源代码审计对企业有哪些好处?

源代码扫描 源代码扫描&#xff0c;对应用程序进行静态漏洞扫描&#xff0c;分析源代码中存在的安全风险&#xff0c;运行应用于模拟器中对应用进行实时漏洞攻击检测。 你是否了解源代码扫描对企业的好处&#xff1f; 一、源代码扫描&#xff0c;通常能够帮助企业解决这些问题…