GORM实现简单的增删改查 | 青训营

81 阅读5分钟

认识ORM

概念

ORM,即Object-Relational Mapping(对象关系映射),是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。

ORM通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。它提供了一种实现持久化层的另一种模式,使得在任何应用的业务逻辑层和数据库层之间充当桥梁。

ORM优点

  1. 提高开发效率,降低开发成本。ORM可以自动对Entity对象与数据库中的Table进行属性与字段的映射,因此不需要一个专用的、庞大的数据访问层。
  2. 只需要面向对象编程,不需要面向数据库编写代码。对数据库的操作转化为对类的操作,不用编写各种数据库的SQL语句。
  3. 隐藏了数据访问的细节,实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异。
  4. SQL注入问题。

ORM缺点

  1. 虽然ORM提供了简化映射的解决方案,但在处理复杂查询时可能会变得困难和不太适用。
  2. ORM框架可能无法实现完全的数据库级别的事务管理,这可能导致潜在的数据不一致。
  3. 性能问题。ORM框架通常会生成额外的SQL语句,这可能会导致性能下降,尤其是在处理大量数据时。
  4. 虽然ORM可以映射类与数据库表之间的关系,但这种映射通常需要额外的配置或元数据,增加了复杂性。

GORM 官方指南

gorm.io/zh_CN/docs/…

安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite

数据库连接

var DB *gorm.DB

var mysqlLogger logger.Interface

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

	mysqlLogger = 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{  //相关配置
		//	SkipDefaultTransaction: true,
		//NamingStrategy: schema.NamingStrategy{
		//	TablePrefix:   "f_",  // 表名前缀
		//	SingularTable: false, // 单数表名
		//	NoLowerCase:   false, // 关闭小写转换
		//},
		Logger: mysqlLogger,
	})
	if err != nil {
		panic("连接数据库失败, error=" + err.Error())
	}
	// 连接成功
	DB = db

}

建表

Go语言可以通过模型的方式自动创建表结构。

模型定义

type Student struct {
  ID    uint // 默认使用ID作为主键
  Name  string
  Email *string // 使用指针是为了存空值
}

*注意:小写属性不会生成字段; *gorm采用的命名策略是,表名是蛇形复数,字段名是蛇形单数


自动生成表结构

// 可以在主函数中运行该代码自动生成表结构
DB.AutoMigrate(&Student{})

AutoMigrate的逻辑是只新增,不删除,不修改(大小会修改)

属性设置

我们可以通过gorm的标签对数据库字段进行相应的配置

例如,修改字段大小

Name  string  `gorm:"type:varchar(12)"`
Name  string  `gorm:"size:2"`

配置属性的字段标签(多个标签用 ; 连接)

字段标签

声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格

标签名说明
column指定 db 列名
type列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
serializer指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime
size定义列数据类型的大小或长度,例如 size: 256
primaryKey将列定义为主键
unique将列定义为唯一键
default定义列的默认值
precision指定列的精度
scale指定列大小
not null指定列为 NOT NULL
autoIncrement指定列为自动增长
autoIncrementIncrement自动步长,控制连续记录之间的间隔
embedded嵌套字段
embeddedPrefix嵌入字段的列名前缀
autoCreateTime创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引获取详情
uniqueIndex与 index 相同,但创建的是唯一索引
check创建检查约束,例如 check:age > 13,查看 约束获取详情
<-设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
->设置字段读的权限,->:false 无读权限
-忽略该字段,- 表示无读写,-:migration 表示无迁移权限,-:all 表示无读写迁移权限
comment迁移时为字段添加注释
type StudentInfo struct {
  Email  *string `gorm:"size:32"` // 使用指针是为了存空值
  Addr   string  `gorm:"column:y_addr;size:16"`
  Gender bool    `gorm:"default:true"`
}
type Student struct {
  Name string      `gorm:"type:varchar(12);not null;comment:用户名"`
  UUID string      `gorm:"primaryKey;unique;comment:主键"`
  Info StudentInfo `gorm:"embedded;embeddedPrefix:s_"`
}

// 建表语句
CREATE TABLE `students` (
    `name` varchar(12) NOT NULL COMMENT '用户名',
    `uuid` varchar(191) UNIQUE COMMENT '主键',
    `s_email` varchar(32),
    `s_y_addr` varchar(16),
    `s_gender` boolean DEFAULT true,
    PRIMARY KEY (`uuid`)
)

创建表结构

type Student struct {
  ID     uint   `gorm:"size:3"`
  Name   string `gorm:"size:8"`
  Age    int    `gorm:"size:3"`
  Gender bool
  Email  *string `gorm:"size:32"`
}

增加

添加数据

func insert1()  {
	email := "12345678@qq.com"
	//创建记录
	student := Student{
		Name:   "张三",
		Age:    21,
		Gender: true,
		Email:  &email,
	}
	err := DB.Create(&student).Error
	fmt.Println(err)
}

对应的SQL语句

INSERT INTO `students` (`name`,`age`,`gender`,`email`) 
	VALUES('张三',21,true,'12345678@qq.com')

注意

  1. 指针类型是为了更好的存null类型,但是传值的时候,也记得传指针
  2. Create接收的是一个指针,而不是值

批量插入

func insert2() {
	email := "12345678@qq.com"
	var studentList []Student
	for i := 0; i < 100; i++ {
		studentList = append(studentList, Student{
			Name:   fmt.Sprintf("机器人%d号", i+1),
			Age:    21,
			Gender: true,
			Email:  &email,
		})
	}
	DB.Create(&studentList)
}

查询

查询单条记录

func query1() {
	var student Student
	DB.Take(&student)
	fmt.Println(student)
}

条件查询

func query2() {
	var student Student
	DB.Take(&student, 2)
	fmt.Println(student)

	student = Student{} // 重新赋值
	DB.Take(&student, "4")
	fmt.Println(student)
}

func query3() {
	var student Student
	DB.Take(&student, "name = ?", "机器人27号")
	fmt.Println(student)
}

func query4() {
	var student Student
	// 只能有一个主要值
	student.ID = 2
	DB.Take(&student)
	data, _ := json.Marshal(student)  //由于email是指针类型,所以看不到实际的内容,需要序列化
	fmt.Println(string(data))
}

查询多条记录

func query5() {
	// 由于email是指针类型,所以看不到实际的内容
	// 但是序列化之后,会转换为我们可以看得懂的方式
	var studentList []Student
	DB.Find(&studentList)
	for _, student := range studentList {
		data, _ := json.Marshal(student)
		fmt.Println(string(data))
	}
}

更新

修改指定记录

func update1() {
	DB.Model(&Student{}).Where("id = ?", 1).Update("email", "is21@qq.com")
}

UPDATE `students` SET `email`='is22@qq.com' WHERE id = 1

func update2() {
	var studentList []Student
	DB.Find(&studentList, "age = ?", 21).Update("email", "is21@qq.com")
}

SELECT * FROM `students` WHERE age = 21  //先查询age = 21 的student id再进行更新
UPDATE `students` SET `email`='is21@qq.com' WHERE age = 21  
AND `id` IN (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,2
6,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,
53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79
,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101)

更新多列

更新数据库中年龄为21的学生的电子邮件地址,并将性别设置为 false。

然而,性别字段并没有成功更新。这是因为没有在 Updates 方法的参数中明确指定需要更新的字段。

func update3() {
	email := "123@qq.com"
	DB.Model(&Student{}).Where("age = ?", 21).Updates(Student{
		Email:  &email,
		Gender: false, // 这个不会更新
	})
}

注:用结构默认不会更新零值,不推荐


推荐使用map

func update4() {
	email := "321@qq.com"
	DB.Model(&Student{}).Where("age = ?", 21).Updates(map[string]any{
		"email":  &email,
		"gender": false,
	})
}

UPDATE `students` SET `email`='321@qq.com',`gender`=false WHERE age = 21

删除

删除指定记录

func delete1() {
	var student Student
	student.ID = 2
	DB.Delete(&student)
}

批量删除

删除多个记录

func delete2() {
	DB.Delete(&Student{}, []int{3, 4, 5})
}

总结

gorm作为一款orm库,几乎实现了CRUD的所有功能。实现灵活,简单快捷。

有了gorm,就不需要再在代码中维护sql语句了。

但也存在一定的局限性,一些比较复杂的SQL还是尽量使用SQL语句。

以上仅为gorm的入门案例,gorm还可以有更多的功能,详细用法可以去官网查询相应的API。

参考文章:

  1. 峰峰知道:docs.fengfengzhidao.com/#/
  2. 官方文档:gorm.io/zh_CN/