1 GORM 入门
当我们学习一个新东西的时候如何快熟入门有以下几个步骤
1.1 什么是 ORM
orm 是一种理念而不是软件
- orm英文全称object relational mapping ,就是
对象映射关系
程序 - 简单来说类似python这种面向对象的程序来说一切皆对象,但是我们使用的数据库却都是关系型的
- 为了保证一致的使用习惯,通过
orm将编程语言的对象模型和数据库的关系模型建立映射关系
- 这样我们直接
使用编程语言的对象模型进行操作数据库
就可以了,而不用直接使用 sql 语言
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和付费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。
对数据表的操作和咱们的面向对象的概念对应起来
-
数据表的操作 <=> 面向对象
- 也就是说对数据表的操作其实就是一个面向对象的过程
- 结构体、结构体的实例、结构体的方法
-
表的定义(通过列定义) <=> 结构体(通过属性定义)
- 数据表中有对表的定义,通过列来定义也就是对应的结构体中通过属性定义。
-
表中的数据 <=> 结构体的属性实例
- 表中的每一个数据就对应到结构体中每个属性的实例
-
对数据的操作 <=> 结构体中的方法
- 在数据表中对数据的操作其实就是对应到结构体中的方法
ORM 其实指的就是上面这几方面的映射:
- 表和结构体的映射
- 数据与结构体实例的映射
- 数据操作与方法的映射
ORM 映射的好处:
-
通过定义结构体,自动生成(工具)表
-
通过对结构体实例的方法调用,对表进行操作
在自动生成表的这个过程中会涉及到一个工具,这个工具的功能就是结构体如何与数据表进行对应,对于结构体方法的调用如何转换成数据库中的 SQL 语句。这中间的转换通过 ORM 框架来实现的。
-
对使用的数据库没有关系,也就是说 ORM 支持多种数据库操作,比如我现在使用的是 MySQL 后期想换成 mariadb、PGSQL 等数据库,ORM 框架层面的代码不用替换,因为对数据库的操作的话都是 ORM 框架里面来做的,只要 ORM 支持我们只需要修改一下配置,比如数据库链接和驱动的配置就 OK 了,但是 ORM 并不是万能的,比如当我们想实现复杂的 sql,或者对性能要求比较高的话还需要通过 sql 来实现
1.2 什么是 GORM
GORM 实现了遵循 ORM 这种理念并通过 Go 语言开发实现的这么一个库
官方地址:https://gorm.io/zh_CN/docs/index.html
GORM是一个神奇的,对开发人员友好的 Golang ORM 库
- 全特性 ORM (几乎包含所有特性)
- 模型关联 (一对一,
一对多,一对多
(反向), 多对多, 多态关联) - 钩子 (Before/After Create/Save/Update/Delete/Find),钩子函数在创建增删改查前或者后需要做的事情
- 预加载
- 事务,用于保证数据安全
- 复合主键
- SQL 构造器
- 自动迁移
- 日志
- 基于GORM回调编写可扩展插件
- 全特性测试覆盖
- 开发者友好
gorm 说明:
在 gorm 中只会添加表的信息,不会删除表中的信息
在使用 orm 必须有以下几步操作:
- 导入驱动(初始化)
- 导入 gorm 包
- 在 gorm 包中注册驱动
- 注册数据库(填写数据库的配置信息)
- 注册模型
- 注册完成之后就可以进行操作,操作分为 DDL DML DQL
- 如果表不存在就会创建对应的表(他会把结构体的名称对应到表名,把结构体的属性名称对应到表里面的列名,结构体属性的类型对应到表的类型上)
- 如果表存在会检查结构体对应的属性在数据表列中是否在存在,如果属性不存在就会自动在该表中添加列
- 还会检查索引是否存在,索引如果不存在就会自动添加索引
1.3 GORMv3 基本使用
1.3.1 环境初始化
1 初始化项目文件夹
$ mkdir gorm
$ cd gorm/
$ go mod init gorm
# 安装 gorm
$ go get -u gorm.io/gorm
2 安装数据库
$ apt install mysql-server
$ mysql
3 创建 test_db
数据库用于练习
mysql> create database test_db charset utf8;
# 配置 root 用户远程访问
mysql> grant all privileges on *.* to 'root'@'%' identified by '123456' with grant option;
mysql> flush privileges;
1.3.2 连接数据库
package main
import (
"fmt"
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
func main() {
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
// 通过 dsn 链接数据
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
fmt.Println(db, err)
}
可以看到 err 为 nil 表示远程链接数据库成功
root@consul-3:~/go/src/DEVOPS/gorm# go run main.go
&{0xc0001e4510 <nil> 0 0xc0000aa000 1} <nil>
1.3.3 自动创建表结构
声明模型:https://gorm.io/zh_CN/docs/models.html
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成
例如:
type User struct {
ID uint
Name string
Email *string
Age uint8
Birthday *time.Time
MemberNumber sql.NullString
ActivatedAt sql.NullTime
CreatedAt time.Time
UpdatedAt time.Time
}
1.3.3.1 约定
GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID
作为主键,使用结构体名的 蛇形复数
作为表名,字段名的 蛇形
作为列名,并使用 CreatedAt
、UpdatedAt
字段追踪创建、更新时间
遵循 GORM 已有的约定,可以减少您的配置和代码量。如果约定不符合您的需求,GORM 允许您自定义配置它们
1.3.3.2 gorm.Model
GORM 定义一个 gorm.Model
结构体,该结构体为 GORM 内置的结构体,其包括字段 ID
、CreatedAt
、UpdatedAt
、DeletedAt
源码如下:
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
1.3.3.3 自定义表结构演示
当我们链接上了数据库之后就需要自定义表结构并实现模型定义,在 go 中就是通过结构体定义
package main
import (
"fmt"
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
// 2.定义表结构(模型定义)
type User struct {
// 我们都知道用户 ID 都是自增
// gorm:"primary_key" 用来标记当前这个 id 是自增的,就是 MYSQL 表中的主键索引
Id int64 `json:"id" gorm:"primary_key"`
UserName string
Password string
}
func main() {
// 1.连接数据库
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
fmt.Println(db, err)
// 3.通过 AutoMigrate 自动创建表结构
if err := db.AutoMigrate(User{}); err != nil {
log.Panic(err)
}
fmt.Println("ok!!")
}
执行之后在数据库中看到已经创建 users 这张表
# 已经创建
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| users |
+-------------------+
1 row in set (0.00 sec)
mysql> select * from users ;
Empty set (0.00 sec)
# 当前表结构
mysql> desc users;
+-----------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| user_name | longtext | YES | | NULL | |
| password | longtext | YES | | NULL | |
+-----------+------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
现在当我们创建了表之后就需要对该表进行 CURD 操作
总结:
-
gorm 创建的数据表名和程序中的结构体、属性名称对应:
- 驼峰式转化为下划线式,非首字母的大写字母前加_并将所有字符转为小写
- 如在程序中的结构体名称
UserAAA
,对应到数据库表中就是user_a_a_a
- 如在结构体中
ID
属性,对应到数据表中就是i_d
- 如在程序中的结构体名称
- 驼峰式转化为下划线式,非首字母的大写字母前加_并将所有字符转为小写
-
程序中结构体的属性类型与数据表中的类型对应:
- int 类型对应数据库中的 INT (INTEGHR)
- int64 类型对应数据库中的 bigint
- string 类型对应数据库中默认是 longtext
- *time.Time 类型对应数据库中的 datetime
- bool 类型对应数据库中的 tinyint(1)
1.3.4 基本 CURD 操作
1.3.4.1 添加数据操作
当我们想向 users 这张表添加数据那我们又该如何操作呢?
package main
import (
"fmt"
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
// 2.定义表结构(模型定义)
type User struct {
// 我们都知道用户 ID 都是自增
// gorm:"primary_key" 用来标记当前这个 id 是自增的,就是 MYSQL 表中的主键索引
Id int64 `json:"id" gorm:"primary_key"`
UserName string
Password string
}
func main() {
// 1.连接数据库
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
fmt.Println(db, err)
// 3.通过 AutoMigrate 自动创建表结构
// if err := db.AutoMigrate(User{}); err != nil {
// log.Panic(err)
// }
// 4. CURD 操作之增加操作
db.Create(&User{
// 由于 id 已经通过 gorm:"primary_key" 定义为自增,所以这里不用传递
UserName: "z",
Password: "12345",
})
}
可以看到 users 表中已经有了刚才在程序中写的信息
mysql> select * from users ;
+----+-----------+----------+
| id | user_name | password |
+----+-----------+----------+
| 1 | z | 12345 |
+----+-----------+----------+
1 row in set (0.00 sec)
1.3.4.2 修改操作
我们如果想对一个表中的数据进行修改的话,就需要先通过表中主键先查询到想要修改的,然后在获取到对应的表内容,最后在 update 进行修改
package main
import (
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
// 2.定义表结构(模型定义)
type User struct {
// 我们都知道用户 ID 都是自增
// gorm:"primary_key" 用来标记当前这个 id 是自增的,就是 MYSQL 表中的主键索引
Id int64 `json:"id" gorm:"primary_key"`
UserName string
Password string
}
func main() {
// 1.连接数据库
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
// 3.通过 AutoMigrate 自动创建表结构
// if err := db.AutoMigrate(User{}); err != nil {
// log.Panic(err)
// }
// 4. CURD 操作
// 4.1 增加操作
// db.Create(&User{
// 由于 id 已经通过 gorm:"primary_key" 定义为自增,所以这里不用传递
// UserName: "z",
// Password: "12345",
// })
// 4.2 修改表操作,如某一个字段
// 查询 ID 为 1 的数据,并通过 update 进行修改
db.Model(User{
Id: 1,
}).Update("password", "7777")
}
可以看到当前 password 已经被修改为了 7777
mysql> select * from users ;
+----+-----------+----------+
| id | user_name | password |
+----+-----------+----------+
| 1 | z | 7777 |
+----+-----------+----------+
1 row in set (0.00 sec)
1.3.4.3 查询操作
1.3.4.3.1 查询单条数据
package main
import (
"fmt"
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
// 2.定义表结构(模型定义)
type User struct {
// 我们都知道用户 ID 都是自增
// gorm:"primary_key" 用来标记当前这个 id 是自增的,就是 MYSQL 表中的主键索引
Id int64 `json:"id" gorm:"primary_key"`
UserName string
Password string
}
func main() {
// 1.连接数据库
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
// 3.通过 AutoMigrate 自动创建表结构
// if err := db.AutoMigrate(User{}); err != nil {
// log.Panic(err)
// }
// 4. CURD 操作
// 4.1 增加操作
// db.Create(&User{
// // 由于 id 已经通过 gorm:"primary_key" 定义为自增,所以这里不用传递
// UserName: "zgy",
// Password: "12345",
// })
// 4.2 修改表操作,如某一个字段
// 查询 ID 为 1 的数据,并通过 update 进行修改
// db.Model(User{
// Id: 1,
// }).Update("password", "7777")
// 4.3 查询操作
// 4.3.1 查询单条数据 First 方法
u := User{Id: 1}
db.First(&u)
fmt.Printf("%#v\n", u)
}
查询到 ID 为 1 的数据
root@consul-3:~/go/src/DEVOPS/gorm# go run main.go
main.User{Id:1, UserName:"z", Password:"7777"}
1.3.4.3.2 查询所有数据操作
package main
import (
"fmt"
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
// 2.定义表结构(模型定义)
type User struct {
// 我们都知道用户 ID 都是自增
// gorm:"primary_key" 用来标记当前这个 id 是自增的,就是 MYSQL 表中的主键索引
Id int64 `json:"id" gorm:"primary_key"`
UserName string
Password string
}
func main() {
// 1.连接数据库
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
// 3.通过 AutoMigrate 自动创建表结构
// if err := db.AutoMigrate(User{}); err != nil {
// log.Panic(err)
// }
// 4. CURD 操作
// 4.1 增加操作
// db.Create(&User{
// // 由于 id 已经通过 gorm:"primary_key" 定义为自增,所以这里不用传递
// UserName: "zgy",
// Password: "12345",
// })
// 4.2 修改表操作,如某一个字段
// 查询 ID 为 1 的数据,并通过 update 进行修改
// db.Model(User{
// Id: 1,
// }).Update("password", "7777")
// 4.3 查询操作
// 4.3.1 查询单条数据 First 方法
// u := User{Id: 1}
// db.First(&u)
// fmt.Printf("%#v\n", u)
// 4.3.2 查询所有数据,这时候就需要先定义一个 user 结构体的切片,来接收从数据所有的数据
users := []User{}
// find 就是一次查询所有数据
db.Find(&users)
fmt.Printf("%#v\n", users)
}
查询到所有数据
root@consul-3:~/go/src/DEVOPS/gorm# go run main.go
[]main.User{main.User{Id:1, UserName:"z", Password:"7777"}, main.User{Id:2, UserName:"zgy", Password:"12345"}}
1.3.4.4 删除数据
1.3.4.4.1 根据主键删除
package main
import (
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
// 2.定义表结构(模型定义)
type User struct {
// 我们都知道用户 ID 都是自增
// gorm:"primary_key" 用来标记当前这个 id 是自增的,就是 MYSQL 表中的主键索引
Id int64 `json:"id" gorm:"primary_key"`
UserName string
Password string
}
func main() {
// 1.连接数据库
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
// 3.通过 AutoMigrate 自动创建表结构
// if err := db.AutoMigrate(User{}); err != nil {
// log.Panic(err)
// }
// 4. CURD 操作
// 4.1 增加操作
// db.Create(&User{
// // 由于 id 已经通过 gorm:"primary_key" 定义为自增,所以这里不用传递
// UserName: "zgy",
// Password: "12345",
// })
// 4.2 修改表操作,如某一个字段
// 查询 ID 为 1 的数据,并通过 update 进行修改
// db.Model(User{
// Id: 1,
// }).Update("password", "7777")
// 4.3 查询操作
// 4.3.1 查询单条数据 First 方法
// u := User{Id: 1}
// db.First(&u)
// fmt.Printf("%#v\n", u)
// 4.3.2 查询所有数据,这时候就需要先定义一个 user 结构体的切片,来接收从数据所有的数据
// users := []User{}
// // find 就是一次查询所有数据
// db.Find(&users)
// fmt.Printf("%#v\n", users)
// 4.4 删除数据
// delete 方法需要根据主键的删除,这里我删除的是 id=1 的数据
db.Delete(User{
Id: 1,
})
}
再次查看该数据库会发现当前的 Id=1 的数据已经删除
mysql> select * from users;
+----+-----------+----------+
| id | user_name | password |
+----+-----------+----------+
| 2 | zgy | 12345 |
+----+-----------+----------+
1 row in set (0.01 sec)
1.3.4.4.2 根据条件删除
比如当我们不知道某条数据的主键是啥,但是我知道他的 name 或者其他数据字段信息,那么就能够更具条件过滤删除
package main
import (
"log"
"gorm.io/gorm"
// 注册 mysql 链接驱动
"gorm.io/driver/mysql"
)
// 2.定义表结构(模型定义)
type User struct {
// 我们都知道用户 ID 都是自增
// gorm:"primary_key" 用来标记当前这个 id 是自增的,就是 MYSQL 表中的主键索引
Id int64 `json:"id" gorm:"primary_key"`
UserName string
Password string
}
func main() {
// 1.连接数据库
/*
创建 GORM 链接数据配置
root:123456 链接数据库用户名和密码
tcp(10.0.0.134:3306) tcp 协议链接远端数据库和端口
test_db?charset=utf8mb4 数据库名称和数据库采用的字符集
*/
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
// 3.通过 AutoMigrate 自动创建表结构
// if err := db.AutoMigrate(User{}); err != nil {
// log.Panic(err)
// }
// 4. CURD 操作
// 4.1 增加操作
// db.Create(&User{
// // 由于 id 已经通过 gorm:"primary_key" 定义为自增,所以这里不用传递
// UserName: "zgy",
// Password: "12345",
// })
// 4.2 修改表操作,如某一个字段
// 查询 ID 为 1 的数据,并通过 update 进行修改
// db.Model(User{
// Id: 1,
// }).Update("password", "7777")
// 4.3 查询操作
// 4.3.1 查询单条数据 First 方法
// u := User{Id: 1}
// db.First(&u)
// fmt.Printf("%#v\n", u)
// 4.3.2 查询所有数据,这时候就需要先定义一个 user 结构体的切片,来接收从数据所有的数据
// users := []User{}
// // find 就是一次查询所有数据
// db.Find(&users)
// fmt.Printf("%#v\n", users)
// 4.4 删除数据
// 4.4.1 根据主键删除
// delete 方法需要根据主键的删除,这里我删除的是 id=1 的数据
// db.Delete(User{
// Id: 1,
// })
// 4.4.2 根据条件删除
// 这里过滤 user_name = zgy 的数据并且是来自于 user 表中,将其删除
db.Where("user_name = ? ", "zgy").Delete(&User{})
}
1.4 模型定义
https://gorm.io/zh_CN/docs/models.html
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成
例如:
type User struct {
Id int64 `gorm:"primary_key" json:"id"`
Name string
CreatedAt *time.Time `json:"createdAt" gorm:"column:create_at"`
Email string `gorm:"type:varchar(100);unique_index"` // 唯一索引
Role string `gorm:"size:255"` //设置字段的大小为255个字 节
MemberNumber *string `gorm:"unique;not null"` // 设置 memberNumber 字段唯一且不为空
Num int `gorm:"AUTO_INCREMENT"` // 设置 Num字段自增
Address string `gorm:"index:addr"` // 给Address 创建一个 名字是 `addr`的索引
IgnoreMe int `gorm:"-"` //忽略这个字段
}
在下面的例子中可以看到如果我想对一些复杂数据类型的操作
package main
import (
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 定义常见的复杂数据类型
type User struct {
Id int64 `gorm:"primary_key" json:"id"`
Name string // 默认 string 类型对应数据库中就是 text 文本类型
CreatedAt *time.Time `json:"createdAt" gorm:"column:create_at"`
Email string `gorm:"type:varchar(100);unique_index"` // 唯一索引,默认为 191 这里我将其设置为 100
Role string `gorm:"size:255"` // 设置字段的大小为255个字节,这里设置一个 size 就不是 text 类型
MemberNumber *string `gorm:"unique;not null"` // 设置 memberNumber 字段唯一且不为空
Num int `gorm:"AUTO_INCREMENT"` // 设置 Num字段自增
Address string `gorm:"index:addr"` // 给Address 创建一个 名字是 `addr`的索引
IgnoreMe int `gorm:"-"` // 忽略这个字段
}
func main() {
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Panic(err)
}
// 创建 user 表
if err := db.AutoMigrate(&User{}); err != nil {
log.Panic(err)
}
}
查看当前表结构
mysql> desc users;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| name | longtext | YES | | NULL | |
| create_at | datetime(3) | YES | | NULL | |
| email | varchar(100) | YES | | NULL | |
| role | varchar(255) | YES | | NULL | |
| member_number | varchar(191) | NO | UNI | NULL | |
| num | bigint(20) | YES | | NULL | |
| address | varchar(191) | YES | MUL | NULL | |
+---------------+--------------+------+-----+---------+----------------+