返回

GORM设置 Collation 为 utf8_general_ci 的最佳实践

mysql

GORM 设置字段 Collation 为 utf8_general_ci 的方案

在使用 GORM 操作 MySQL 数据库时,开发者可能需要设置特定字段的 Collation(排序规则),以便实现诸如大小写不敏感的唯一性约束等功能。 本文介绍如何通过 GORM 将字段的 Collation 设置为 utf8_general_ci

问题背景

当希望在 MySQL 数据库中创建字符串类型的字段,并确保该字段具有大小写不敏感的特性且唯一时,通常会想到设置字段的 COLLATIONutf8_general_ci。 虽然可以通过 SQL 语句直接实现,但在使用 GORM 时,可能会遇到无法直接通过 tag 修改 Collation 的情况。

原因分析

GORM 通过 tag 控制数据库字段的属性。 然而,并非所有数据库属性都可以在 GORM 的 tag 中直接设置。 COLLATION 属于较为底层的数据库属性,直接在 tag 中声明可能不生效。 GORM 需要一定的配置才能实现这种定制化。

解决方案

以下介绍几种可以实现该需求的方案。

1. 使用 GORM 的 Migrator 进行数据库迁移

这是最推荐的方式,也是相对安全、可维护性高的方式。通过 GORM 的 Migrator 创建表,可以在表定义时指定字段的 COLLATION

步骤:

  1. 定义 struct 模型。

  2. 利用 Migrator().CreateTableMigrator().AutoMigrate 创建/迁移表。在创建或更新表结构时,GORM 将执行您指定的SQL片段。

代码示例:

package main

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

type User struct {
	Id    int64  `gorm:"primaryKey;autoIncrement"`
	Email string `gorm:"type:varchar(255);uniqueIndex;not null"` // 添加了长度和 not null 约束
}

func main() {
	dsn := "user:password@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}

	// 使用 GORM Migrator 手动创建表,并指定 Collation
	err = db.Migrator().CreateTable(&User{})
	if err != nil {
		panic(err)
	}
	err = db.Exec("ALTER TABLE users MODIFY COLUMN email VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;").Error
	if err != nil {
		panic(err)
	}

	fmt.Println("Table 'users' created with utf8mb4_general_ci collation on 'email' column.")
}

解释:

首先,连接到数据库,然后使用 db.Migrator().CreateTable(&User{}) 创建 users 表。然后,使用db.Exec()执行一段SQL,使用ALTER TABLE语句更改了 email 字段的 collation 为utf8mb4_general_ci,注意如果需要使用大小写不敏感请替换utf8mb4_general_ci,为utf8mb4_general_ci替换成 utf8mb4_0900_ai_ci。如果想保持 utf8 ,只需要替换 charset即可。如 CHARACTER SET utf8 COLLATE utf8_general_ci;

这种方式将保证在数据库层面的 email 字段的 collation 符合预期。

注意:

务必对 Email 字段设置长度约束(例如:varchar(255))和 not null约束。这样做不仅能够保证数据的有效性,还可以避免一些潜在的问题。确保您的数据库连接使用 utf8mb4 字符集,这是推荐的做法,因为它支持更广泛的字符集,包含 emoji 等。

2. 使用 GORM Hook (不推荐)

GORM 提供了 Hook 函数,可以在创建、更新数据前后执行自定义操作。虽然可以利用 Hook 函数,但这种方式相对繁琐,不易维护。因此,除非无法使用 Migrator,否则不建议使用此方法。

步骤:

  1. 定义包含 COLLATION 子句的 SQL 语句。

  2. 在 Model 的 AfterCreateBeforeCreate Hook 中执行该 SQL 语句。

代码示例:

type User struct {
	Id             int64  `json:"id" gorm:"primaryKey;AUTO_INCREMENT"`
	Email          string `json:"email" gorm:"uniqueIndex"`
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
	// 执行 SQL 语句,修改 Email 字段的 Collation
	err = tx.Exec("ALTER TABLE users MODIFY COLUMN email VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci").Error
	return
}

解释:

AfterCreate Hook 中,使用 tx.Exec() 执行 ALTER TABLE 语句,将 email 字段的 Collation 修改为 utf8_general_ci。 这种方法在每次创建用户后都会执行一次 ALTER TABLE,效率较低,而且存在SQL注入的风险。请谨慎使用。

注意:

  • 请确保您的 GORM 配置正确设置了数据库连接信息。
  • ALTER TABLE 操作可能会阻塞数据库,特别是当表数据量较大时。

3. 直接操作数据库 (不推荐)

还可以绕过 GORM,直接使用 database/sql 包来操作数据库,创建表并设置 COLLATION。 但是,这将导致代码与 GORM 的其他功能脱节,增加维护难度,所以非常不建议使用。

代码示例:

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database_name")
	if err != nil {
		panic(err.Error())
	}
	defer db.Close()

	_, err = db.Exec(`
		CREATE TABLE users (
			id INT AUTO_INCREMENT PRIMARY KEY,
			email VARCHAR(255) UNIQUE,
			email VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci
		);
	`)
	if err != nil {
		panic(err.Error())
	}
}

解释:

上述代码直接使用 database/sql 包连接数据库,然后执行 CREATE TABLE 语句创建表。 在 CREATE TABLE 语句中,指定了 email 字段的 COLLATIONutf8_general_ci

注意:

  • 绕过 GORM 会增加维护难度,应尽量避免。
  • 直接操作数据库容易出现SQL注入等安全问题,务必谨慎。

安全建议

  • 对用户输入进行严格的验证和过滤,防止 SQL 注入。
  • 最小权限原则:数据库用户应只具有完成任务所需的最小权限。
  • 定期备份数据库,以防止数据丢失。

通过 GORM 的 Migrator,可以方便且安全地设置数据库字段的 Collation。 虽然可以通过 Hook 函数和直接操作数据库的方式实现,但这些方法存在一定的风险和缺点。 希望这些信息能帮助到您。