dedecms 5.7 通用企业网站模板,一个域名可以做两个网站吗,我的网站怎么转网页呢,intellij idea做网站连接
需要下载mysql的驱动
go get gorm.io/driver/mysql
go get gorm.io/gorm
约定
主键#xff1a;GORM 使用一个名为ID 的字段作为每个模型的默认主键。表名#xff1a;默认情况下#xff0c;GORM 将结构体名称转换为 snake_case 并为表名加上复数形式。 例如#xf…连接
需要下载mysql的驱动
go get gorm.io/driver/mysql
go get gorm.io/gorm
约定
主键GORM 使用一个名为ID 的字段作为每个模型的默认主键。表名默认情况下GORM 将结构体名称转换为 snake_case 并为表名加上复数形式。 例如一个 User 结构体在数据库中的表名变为 users 。列名GORM 自动将结构体字段名称转换为 snake_case 作为数据库中的列名。时间戳字段GORM使用字段 CreatedAt 和 UpdatedAt 来自动跟踪记录的创建和更新时间。
简单连接
username : root //账号
password : 325523 //密码
host : 127.0.0.1 //数据库地址可以是Ip或者域名
port : 3306 //数据库端口
Dbname : gorm //数据库名
timeout : 10s //连接超时10秒// root:325523tcp(127.0.0.1:3306)/gorm?
dsn : fmt.Sprintf(%s:%stcp(%s:%d)/%s?charsetutf8mb4parseTimeTruelocLocaltimeout%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: true, //是否单数表名NoLowerCase: true, //是否不要小写转换},
})if err ! nil {panic(连接数据库失败, error err.Error())
}
//连接成功
//gorm.DB是GORM库中的一个结构体用于表示数据库连接可以在整个程序中访问和操作数据库
fmt.Println(db)
高级配置
跳过默认事务
为了确保数据一致性GORM 会在事务里执行写入操作创建、更新、删除。如果没有这方面的要求您可以在初始化时禁用它这样可以获得60%的性能提升
db, err : gorm.Open(mysql.Open(gorm.db), gorm.Config{SkipDefaultTransaction: true,
})
命名策略
gorm采用的命名策略是表名是蛇形复数字段名是蛇形单数蛇形命名单词之间采用下划线分割单词全小写
type Student struct {Name stringAge intMyStudent string
}
gorm会为我们这样生成表结构
CREATE TABLE students (name longtext,age bigint,my_student longtext)
我们也可以修改这些策略
db, err : gorm.Open(mysql.Open(dsn), gorm.Config{NamingStrategy: schema.NamingStrategy{TablePrefix: f_, // 表名前缀SingularTable: false, // 关闭单数表名NoLowerCase: false, // 是否不要小写转换false代表否表示要小写转换将大写转换为小写},
})//生成的表
CREATE TABLE f_students (Name longtext,Age bigint,MyStudent longtext)
显示日志
gorm的默认日志是只打印错误和慢SQL
可以自己设置
var mysqlLogger logger.Interface
// 要显示的日志等级
mysqlLogger logger.Default.LogMode(logger.Info)
db, err : gorm.Open(mysql.Open(dsn), gorm.Config{Logger: mysqlLogger,
})
如果你想自定义日志的显示
那么可以使用如下代码
newLogger : logger.New(log.New(os.Stdout, \r\n, log.LstdFlags), // 日志输出的目标前缀和日志包含的内容logger.Config{SlowThreshold: time.Second, // 慢 SQL 阈值LogLevel: logger.Info, // 日志级别IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound记录未找到错误Colorful: true, // 使用彩色打印},
)db, err : gorm.Open(mysql.Open(dsn), gorm.Config{Logger: newLogger,
})
部分展示日志
var student Student
session : DB.Session(gorm.Session{Logger: newLogger,
})
session.First(student)
// SELECT * FROM students ORDER BY students.name LIMIT 1
如果只想某些语句显示日志
DB.Debug().First(model)
模型定义
在GORM中模型Models通常是正常定义的结构体、基本的go类型或它们的指针。 同时也支持sql.Scanner及driver.Valuer接口interfaces。
gorm.Model
为了方便模型定义GORM内置了一个gorm.Model结构体。gorm.Model是一个包含了ID, CreatedAt, UpdatedAt, DeletedAt四个字段的Golang结构体。
// gorm.Model 定义go语言内置
type Model struct {ID uint gorm:primary_keyCreatedAt time.Time // 初次创建记录的时间UpdatedAt time.Time // 每次更新记录的时间DeletedAt *time.Time // 删除该记录的时间
}
ID 每个记录的唯一标识符主键。CreatedAt 在创建记录时自动设置为当前时间。UpdatedAt每当记录更新时自动更新为当前时间。DeletedAt用于软删除将记录标记为已删除而实际上并未从数据库中删除。
你可以将它嵌入到你自己的结构体模型中
// 将 ID, CreatedAt, UpdatedAt, DeletedAt字段注入到User模型中
type User struct {gorm.ModelName string
}
模型定义示例
type User struct {gorm.Model Name stringAge sql.NullInt64Birthday *time.TimeEmail string gorm:type:varchar(100);unique_indexRole string gorm:size:255 // 设置字段大小为255MemberNumber *string gorm:unique;not null // 设置会员号member number唯一并且不为空Num int gorm:AUTO_INCREMENT // 设置 num 为自增类型Address string gorm:index:addr // 给address字段创建名为addr的索引IgnoreMe int gorm:- // 忽略本字段
}
结构体标记tags
使用结构体声明模型时标记tags是可选项。gorm支持以下标记:
支持的结构体标记Struct tags Column 指定列名 Type 指定列数据类型 Size 指定列大小, 默认值255 PRIMARY_KEY 将列指定为主键 UNIQUE 将列指定为唯一 DEFAULT 指定列默认值 PRECISION 指定列精度 NOT NULL 将列指定为非 NULL AUTO_INCREMENT 指定列是否为自增类型 INDEX 创建具有或不带名称的索引, 如果多个索引同名则创建复合索引 UNIQUE_INDEX 和INDEX 类似只不过创建的是唯一索引 EMBEDDED 将结构设置为嵌入 EMBEDDED_PREFIX 设置嵌入结构的前缀 - 忽略此字段
关联相关标记tags MANY2MANY 指定连接表 FOREIGNKEY 设置外键 ASSOCIATION_FOREIGNKEY 设置关联外键 POLYMORPHIC 指定多态类型 POLYMORPHIC_VALUE 指定多态值 JOINTABLE_FOREIGNKEY 指定连接表的外键 ASSOCIATION_JOINTABLE_FOREIGNKEY 指定连接表的关联外键 SAVE_ASSOCIATIONS 是否自动完成 save 的相关操作 ASSOCIATION_AUTOUPDATE 是否自动完成 update 的相关操作 ASSOCIATION_AUTOCREATE 是否自动完成 create 的相关操作 ASSOCIATION_SAVE_REFERENCE 是否自动完成引用的 save 的相关操作 PRELOAD 是否自动完成预加载的相关操作
模型是标准的 struct由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成
定义一张表
type Student struct {ID uint // gorm会默认使用ID字段作为主键Name stringEmail *string // 使用指针是为了存空值
}
小写属性是不会生成字段的
自动生成表结构
// 可以放多个
DB.AutoMigrate(Student{})
AutoMigrate的逻辑是只新增不删除不修改类型不修改大小会修改
例如将字段名Name修改为Name1进行迁移会多出一个name1的字段原本name字段不会被删除 AutoMigrate 方法的核心逻辑如下 新增字段如果模型中添加了新的字段AutoMigrate 会在数据库表中相应地新增这些字段。不删除字段如果模型中删除了某个字段AutoMigrate 不会删除数据库中的这个字段。这样可以避免数据丢失。不修改字段类型虽然可以通过 AutoMigrate 来调整字段的大小或其他属性但一般来说字段的类型是不被修改的。如果需要修改字段类型通常需要手动执行相应的 SQL 语句 修改大小
我们可以使用gorm的标签进行修改
有两种方式
Name string gorm:type:varchar(12)
Name string gorm:size:2
字段标签
type 定义字段类型
size 定义字段大小
column 自定义列名
primaryKey 将列定义为主键
unique 将列定义为唯一键
default 定义列的默认值
not null 不可为空
embedded 嵌套字段
embeddedPrefix 嵌套字段前缀
comment 注释
多个标签之前用 ; 连接
type StudentInfo struct {Email *string gorm:size:32 // 使用指针是为了存空值Addr string gorm:column:y_addr;size:16Gender 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:3Name string gorm:size:8Age int gorm:size:3Gender boolEmail *string gorm:size:32 //指针类型可以传nil值
}
选定/忽略字段
Select选定字段
Omit忽略字段
增加(Create)
忽略零值
单条插入
email : xxxqq.com
// 创建记录
student : Student{Name: 枫枫,Age: 21,Gender: true,Email: email,
}
DB.Create(student)
fmt.Printf(%#v\n, student)
// main.Student{ID:0x2, Name:zhangsan, Age:23, Gender:false, Email:(*string)(0x11d40980)}
// 由于我们传递的是一个指针调用完Create之后student这个对象上面就有该记录的信息了如创建的id
有两个地方需要注意
指针类型是为了更好的存nil类型但是传值的时候也记得传指针Create接收的是一个指针而不是值
批量插入
Create方法还可以用于插入多条记录
var studentList []Student
for i : 0; i 100; i {studentList append(studentList, Student{Name: fmt.Sprintf(机器人%d号, i1),Age: 21,Gender: true,Email: email,})
}
DB.Create(studentList)
既可以使用结构体或map来创建当使用map来创建时钩子方法不会执行关联不会被保存且不会回写主键
注意
可以通过 tag 定义字段的默认值比如
type User struct {ID int64Name string gorm:default:小王子Age int64
}
通过tag定义字段的默认值在创建记录时候生成的 SQL 语句会排除没有值或值为零值的字段。 在将记录插入到数据库后Gorm会从数据库加载那些字段的默认值。
举个例子
var user User{Name: , Age: 99}
db.Create(user)
上面代码实际执行的SQL语句是INSERT INTO users(age) values(99);排除了零值字段Name而在数据库中这一条数据会使用设置的默认值小王子作为Name字段的值。
所有字段的零值都不会保存到数据库内但会使用他们的默认值。 如果想避免这种情况可以考虑使用指针或实现 Scanner/Valuer接口比如
使用指针方式实现零值存入数据库
// 使用指针
type User struct {ID int64Name *string gorm:default:小王子Age int64
}
user : User{Name: new(string), Age: 18))}
db.Create(user) // 此时数据库中该条记录name字段的值就是
使用Scanner/Valuer接口方式实现零值存入数据库
// 使用 Scanner/Valuer
type User struct {ID int64Name sql.NullString gorm:default:小王子 // sql.NullString 实现了Scanner/Valuer接口Age int64
}
user : User{Name: sql.NullString{, true}, Age:18}
db.Create(user) // 此时数据库中该条记录name字段的值就是
查询
单条查询Take
根据顺序查询
var student Student
DB.Take(student) //查找表中一条匹配的记录并将其封装到student中方法返回一个错误
// SELECT * FROM students LIMIT 1DB.First(student) //查询第一条匹配的数据(主键升序)并将其封装到student中没有找到时不会返回错误而是将结果设置为该结构体类型的零值
// SELECT * FROM students ORDER BY students.id LIMIT 1DB.Last(student) //查询表中最后一条匹配的数据(主键降序)并将其封装到student中没有找到时不会返回错误而是将结果设置为该结构体类型的零值
// SELECT * FROM students ORDER BY students.id DESC LIMIT 1
First 和 Last 方法会按主键排序找到第一条记录和最后一条记录 (分别)。 只有在目标 struct 是指针或者通过 db.Model() 指定 model 时该方法才有效。如果相关 model 没有定义主键那么将按 model 的第一个字段进行排序 Model()方法是一个非常重要的函数它用于指定后续数据库操作所针对的模型Model。这个方法通常用于告诉GORM你想要对哪个模型进行操作特别是在执行更新Update、删除Delete等操作时你需要明确指出操作的目标模型。 不影响原始记录使用Model()方法时你不需要传入具体的实例而只需要传入模型的类型。这意味着GORM不会自动加载任何记录而是根据你提供的模型类型来确定操作的目标。链式操作Model()方法返回的是一个*gorm.DB对象这意味着你可以将它与其他GORM方法链式调用如Where()、Updates()、Update()、Delete()等。 根据主键查询
var student Student
DB.Take(student, 2)//查询主键id是2的
fmt.Println(student)student Student{} // 重新赋值
DB.Take(student, 4)//查询主键id是4(字符串)的
fmt.Println(student)
Take的第二个参数默认会根据主键查询可以是字符串可以是数字
根据其他条件查询
使用?作为占位符将查询的内容放入?
var student Student
DB.Take(student, name ?, 机器人27号)
fmt.Println(student)SELECT * FROM students WHERE name 机器人27号 LIMIT 1
使用?可以有效的防止sql注入它的原理就是将参数全部转义将机器人27号识别为字符串去掉双引号用单引号包围替换掉?占位符
var student []Student
//不会出现sql注入
DB.Take(student, name ?, 机器人27号 or 11;#)
//SELECT * FROM students WHERE name 机器人27号 or 11;# LIMIT 1 //会出现sql注入
DB.Take(student,fmt.sprintf(name %s,机器人27号 or 11;#))
//SELECT * FROM students WHERE name 机器人27号 or 11;# LIMIT 1
根据struct查询
会忽略零值
var student Student // 只能有一个主键值 student.ID 2 //student.Name 枫枫 DB.Take(student) fmt.Println(student)
多条查询Find
1.使用Find()方法
先将所有符合条件的记录都加载到studentList切片中再进行更新操作先查询再更新。
对单个对象使用Find而不带limitdb.Find(user)将会查询整个表并且只返回第一个对象。
2.使用Model()方法
直接在数据库层面进行条件筛选和更新不需要将所有记录加载到内存中。这在处理大量数据时更为高效因为它只涉及一次数据库操作。
根据顺序查询
var studentList []Student
DB.Find(studentList)//查询所有数据并将其封装到studentList
for _, student : range studentList {fmt.Println(student)
}// 由于email是指针类型所以看不到实际的内容
// 但是序列化之后会转换为我们可以看得懂的方式
var studentList []Student
DB.Find(studentList)
for _, student : range studentList {data, _ : json.Marshal(student)fmt.Println(string(data))
}
根据主键列表查询
var studentList []Student
DB.Find(studentList, []int{1, 3, 5, 7})
DB.Find(studentList, 1, 3, 5, 7) // 二者效果相同
fmt.Println(studentList)
根据其他条件查询
DB.Find(studentList, name in ?, []string{枫枫, zhangsan})
获取结果
1.获取查询的记录数量受影响的行数
count : DB.Find(studentList).RowsAffected
2.是否查询失败返回错误信息
err : DB.Find(studentList).Error
查询失败有查询为空查询条件错误sql语法错误
可以使用判断
var student Student
err : DB.Take(student, xx).Error
switch err {
case gorm.ErrRecordNotFound:fmt.Println(没有找到)
default:fmt.Println(sql错误)
}
更新
更新的前提的先查询到记录
保存所有字段更新单条记录Save
不要将 Save 和 Model一同使用, 这是未定义的行为保存是一个组合函数。 如果保存值不包含主键它将执行 Create否则它将执行 Update (包含所有字段)。
它会保存所有字段即使零值也会更新不会忽略零值
var student Student
DB.Take(student)//先查询到记录才能更新
student.Age 23
// 全字段更新
DB.Save(student)
// UPDATE students SET name枫枫,age23,gendertrue,emailxxxqq.com WHERE id 1//零值也会更新
DB.Take(student)//先查询到记录才能更新
student.Age 0
// 全字段更新
DB.Save(student)
// UPDATE students SET name枫枫,age0,gendertrue,emailxxxqq.com WHERE id 1
可以使用select选择要更新的字段
var student Student
DB.Take(student)//先查询到记录
student.Name xiaoming
student.Age 21
// 更新指定字段(只更新age不更新name),Select中传入表中字段名
DB.Select(age).Save(student)
// UPDATE students SET age21 WHERE id 1
更新多条记录
更新单个字段Update Update()方法 更新单个字段只接受两个参数字段名和一个值忽略零值字段只更新非零值字段如果传入的是该字段类型的零值该字段不会被更新 给所有年龄21的学生都更新一下邮箱
var studentList []Student
//以下两种效果一样效率不同
DB.Find(studentList, age ?, 21).Update(email, is21qq.com)
DB.Model(Student{}).Where(age ?, 21).Update(email, is21qq.com)
// UPDATE students SET emailis22qq.com WHERE age 21
更新多个字段Updates Updates方法 一次性更新多个字段接受一个结构体作为参数包含要更新的字段和它们的新值忽略零值字段只更新非零值字段如果传入的是该字段类型的零值该字段不会被更新 email : xxxqq.com
DB.Model(Student{}).Where(age ?, 21).Updates(Student{Email: email,Gender: false, // Gender属性设为零值bool类型这个不会更新
})
// 只有email属性会被更新
// UPDATE students SET emailxxxqq.com WHERE age 21
用select选中特定字段可以更新零值
email : xxx1qq.com
//使用Select方法指定要更新的字段即使是零值也会被更新
DB.Model(Student{}).Where(age ?, 21).Select(gender, email).Updates(Student{Email: email,Gender: false, //设置为零值也会被更新
})
// UPDATE students SET genderfalse,emailxxx1qq.com WHERE age 21
用map也可以更新零值
DB.Model(Student{}).Where(age ?, 21).Updates(map[string]any{email: email,gender: false, //使用map即使是零值也会被更新
})
删除Delete
删除一条记录
删除一条记录时删除对象需要指定主键否则会触发 批量删除例如
// email 的 ID 是 10
db.Delete(email)
// DELETE from emails where id 10;// 带额外条件的删除
db.Where(name ?, jinzhu).Delete(email)
// DELETE from emails where id 10 AND name jinzhu;
根据主键删除
GORM 允许通过主键(可以是复合主键)和内联条件来删除对象
db.Delete(User{}, 10)
// DELETE FROM users WHERE id 10;db.Delete(User{}, 10)
// DELETE FROM users WHERE id 10;db.Delete(users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3)
批量删除
如果指定的值不包括主属性那么 GORM 会执行批量删除它将删除所有匹配的记录
db.Where(email LIKE ?, %jinzhu%).Delete(Email{})
// DELETE from emails where email LIKE %jinzhu%;db.Delete(Email{}, email LIKE ?, %jinzhu%)
// DELETE from emails where email LIKE %jinzhu%; Hook钩子函数
对象生命周期
Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。
如果您已经为模型定义了指定的方法它会在创建、更新、查询、删除时自动被调用。如果任何回调返回错误GORM 将停止后续的操作并回滚事务。
钩子方法的函数签名应该是 func(*gorm.DB) error
创建对象
创建时可用的 hook
// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务
代码示例
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {u.UUID uuid.New()if !u.IsValid() {err errors.New(cant save invalid data)}return
}func (u *User) AfterCreate(tx *gorm.DB) (err error) {if u.ID 1 {tx.Model(u).Update(role, admin)}return
}
注意 在 GORM 中保存、删除操作会默认运行在事务上 因此在事务完成之前该事务中所作的更改是不可见的如果您的钩子返回了任何错误则修改将被回滚。
func (u *User) AfterCreate(tx *gorm.DB) (err error) {if !u.IsValid() {return errors.New(rollback invalid user)}return nil
}
更新对象
// 开始事务
BeforeSave
BeforeUpdate
// 关联前的 save
// 更新 db
// 关联后的 save
AfterUpdate
AfterSave
// 提交或回滚事务
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {if u.readonly() {err errors.New(read only user)}return
}// 在同一个事务中更新数据
func (u *User) AfterUpdate(tx *gorm.DB) (err error) {if u.Confirmed {tx.Model(Address{}).Where(user_id ?, u.ID).Update(verfied, true)}return
}
删除对象
删除时可用的 hook
// 开始事务
BeforeDelete
// 删除 db 中的数据
AfterDelete
// 提交或回滚事务
代码示例
// 在同一个事务中更新数据
func (u *User) AfterDelete(tx *gorm.DB) (err error) {if u.Confirmed {tx.Model(Address{}).Where(user_id ?, u.ID).Update(invalid, false)}return
}
查询对象
查询时可用的 hook
// 从 db 中加载数据
// Preloading (eager loading)
AfterFind
代码示例
func (u *User) AfterFind(tx *gorm.DB) (err error) {if u.MemberShip {u.MemberShip user}return
}
修改当前操作
func (u *User) BeforeCreate(tx *gorm.DB) error {// 通过 tx.Statement 修改当前操作例如tx.Statement.Select(Name, Age)tx.Statement.AddClause(clause.OnConflict{DoNothing: true})
//向当前的数据库操作添加了一个冲突处理条款指定如果尝试插入的记录与现有记录发生冲突例如违反了唯一性约束则不执行任何操作
//这个设置通常用于INSERT 操作以处理可能的重复插入。// tx 是带有 NewDB 选项的新会话模式 // 基于 tx 的操作会在同一个事务中但不会带上任何当前的条件err : tx.First(role, name ?, user.Role).Error// SELECT * FROM roles WHERE name admin// ...return err
}
这段代码的主要作用是在创建 User 记录之前先查询一个与 User 相关的角色信息并处理可能的数据库冲突。这个查询操作的结果是否成功将决定 BeforeCreate 钩子函数的返回值从而可能影响 User 记录的创建过程。