书接上文,本文主要对使用 GORM 操作数据库时如何同时兼容多种数据库进行说明。
在定义 GORM 数据模型时,需要确保定义的数据模型能够在不同数据库系统之间正确地映射和转换,包括数据类型、表名和字段名等方面。
package model
// ExampleStandardModel GORM 标准模型示例
//
// 定义字段类型兼容多种数据库参考规则
type ExampleStandardModel struct {
// ID 字段在 GORM 中默认为主键,两个字母全部大写,建议显式指定 primaryKey 标签
//
// - Go 整数类型字段 GORM 的 autoIncrement:false 标签,表示非自增字段,建议显式指定,否则在 PostgreSQL 中会默认自增
// - Go 整数类型字段 GORM 的 autoIncrement:true 标签,表示自增字段
// - GORM 的 not null 标签,表示数据库字段的非空约束,按需指定
// - GORM 的 comment:xxx 标签,表示字段注释,建议显式指定
ID int64 `gorm:"column:id;size:64;not null;primaryKey;autoIncrement:true;comment:主键,自增 ID" json:"id"`
IDNoIncrement int64 `gorm:"column:id_no_increment;size:64;not null;primaryKey;autoIncrement:false;comment:主键,非自增整数 ID" json:"idNoIncrement"`
IDString string `gorm:"column:id_string;type:varchar(64);not null;primaryKey;comment:主键,字符串 ID" json:"idString"`
// Go 整数类型字段 GORM 标签建议指定 size:64(字段大小),GORM 会根据 Go 类型和 size 自动转换为对应的数据库类型
//
// - PostgreSQL: smallint、integer、bigint、smallserial、serial、bigserial
// - SQL Server: tinyint、smallint、int、bigint
// - MySQL: tinyint、smallint、mediumint、int、bigint
// - Oracle: SMALLINT、INTEGER
// - 达梦: TINYINT、SMALLINT、INT、BIGINT
// - SQLite: integer
TinyintField int `gorm:"column:tinyint_size_field;size:8;comment:微小整数字段;" json:"tinyintField"`
SmallintField int `gorm:"column:smallint_field;size:16;comment:小型整数字段;" json:"smallintField"`
IntegerField int `gorm:"column:integer_field;size:32;comment:普通整数字段;" json:"integerField"`
BigintField int64 `gorm:"column:bigint_field;size:64;comment:大型整数字段;" json:"bigintField"`
// Go 浮点类型字段 GORM 标签建议指定 precision:18(精度)和 scale:4(小数位数),GORM 会根据 precision 和 scale 自动转换为对应的数据库类型
//
// - PostgreSQL: decimal、numeric(precision)、numeric(precision, scale)
// - SQL Server: float、decimal(precision)、decimal(precision, scale)
// - MySQL: double、float、decimal(precision, scale)
// - Oracle: FLOAT
// - 达梦: DOUBLE、DECIMAL(precision, scale)
// - SQLite: real
FloatField float64 `gorm:"column:float_field;precision:18;scale:4;comment:浮点型小数字段,可通过 precision 和 scale 指定小数精度;" json:"floatField"`
// Go 字符串类型字段 GORM 标签建议指定 size:1000(字段大小),GORM 会根据 Go 类型和 size 自动转换为对应的数据库类型
//
// - PostgreSQL: text、varchar(size)
// - SQL Server: nvarchar(MAX)、nvarchar(size)
// - MySQL: longtext、mediumtext、varchar(size)
// - Oracle: CLOB、VARCHAR2(size)、VARCHAR2(size CHAR)
// - 达梦: CLOB、VARCHAR、VARCHAR(8188 CHAR)、VARCHAR(size)、VARCHAR(size CHAR)
// - SQLite: text
NvarcharField string `gorm:"column:nvarchar_field;size:1000;comment:(SQL Server)双字节可变长度字符串类型;" json:"nvarcharField"`
VarcharField string `gorm:"column:varchar_field;type:varchar(1000);comment:(SQL Server)单字节可变长度字符串类型;" json:"varcharField"`
// Go 布尔类型字段 GORM 标签不需要指定 type:bit 或 type:boolean,GORM 会根据 Go 类型自动转换为对应的数据库类型
//
// - PostgreSQL: boolean
// - SQL Server: bit
// - MySQL: boolean
// - Oracle: NUMBER(1)
// - 达梦: BIT
// - SQLite: numeric
BoolField bool `gorm:"column:bool_field;comment:布尔类型字段" json:"boolField"`
// Go 字节切片类型字段 GORM 标签建议指定 size:-1(字段大小),GORM 会根据 Go 类型和 size 自动转换为对应的数据库类型
//
// - PostgreSQL: bytea
// - SQL Server: varbinary(MAX)
// - MySQL: longblob、mediumblob、varbinary(size)
// - Oracle: BLOB
// - 达梦: BLOB、VARBINARY(size)
// - SQLite: blob
BytesField []byte `gorm:"column:bytes_field;size:-1;comment:二进制数据类型字段,可用于存储文件内容" json:"bytesField"`
}
// TableName 显式指定数据库对应的表名
func (ExampleStandardModel) TableName() string {
return "example_standard"
}
通常有以下三种类型的主键:
如下三个字段所示:
type ExampleStandardModel struct {
ID int64 `gorm:"column:id;size:64;not null;primaryKey;autoIncrement:true;comment:主键,自增 ID" json:"id"`
IDNoIncrement int64 `gorm:"column:id_no_increment;size:64;not null;primaryKey;autoIncrement:false;comment:主键,非自增整数 ID" json:"idNoIncrement"`
IDString string `gorm:"column:id_string;type:varchar(64);not null;primaryKey;comment:主键,字符串 ID" json:"idString"`
}
注意事项:
size:n
标签映射;type:varchar(n)
标签映射;ID
字段在 GORM 中默认为主键,两个字母全部大写,建议显式指定 primaryKey
标签;autoIncrement:false
标签,表示非自增字段,建议显式指定,否则在 PostgreSQL
中会默认自增;autoIncrement:true
标签,表示自增字段;not null
标签,表示数据库字段的非空约束,按需指定;comment:xxx
标签,表示字段注释,建议显式指定。[!Tip]
Go 整数类型字段 GORM 数据类型使用 size:n
(n
为字段大小,如 size:64
)标签映射,
GORM 会根据 Go 类型和 size
自动转换为对应的数据库类型,通过 size
标签映射数据库中的整数类型对照关系如下所示:
序号 | size 标签 |
SQL Server | PostgreSQL | MySQL | Oracle | 达梦 DM8 | SQLite |
---|---|---|---|---|---|---|---|
1. | size:8 |
tinyint |
smallint /smallserial |
tinyint |
SMALLINT |
TINYINT |
integer |
2. | size:16 |
smallint |
smallint /smallserial |
smallint |
INTEGER |
SMALLINT |
integer |
3. | size:24 |
int |
integer /serial |
mediumint |
INTEGER |
INT |
integer |
4. | size:32 |
int |
integer /serial |
int |
INTEGER |
INT |
integer |
5. | size:64 |
bigint |
bigint /bigserial |
bigint |
INTEGER |
BIGINT |
integer |
[!Tip]
- https://github.com/go-gorm/sqlserver/blob/v1.5.2/sqlserver.go#L188
- https://github.com/go-gorm/postgres/blob/v1.5.4/postgres.go#L165
- https://github.com/go-gorm/mysql/blob/v1.5.2/mysql.go#L475
- https://github.com/godoes/gorm-oracle/blob/v1.6.1/oracle.go#L443
- https://github.com/godoes/gorm-dameng/blob/v0.0.8/dm.go#L198
- https://github.com/glebarez/sqlite/blob/v1.10.0/sqlite.go#L202
type ExampleStandardModel struct {
TinyintField int `gorm:"column:tinyint_size_field;size:8;comment:微小整数字段;" json:"tinyintField"`
SmallintField int `gorm:"column:smallint_field;size:16;comment:小型整数字段;" json:"smallintField"`
IntegerField int `gorm:"column:integer_field;size:32;comment:普通整数字段;" json:"integerField"`
BigintField int64 `gorm:"column:bigint_field;size:64;comment:大型整数字段;" json:"bigintField"`
}
Go 浮点类型字段 GORM 数据类型使用 precision:n
(n
为精度,如 precision:18
)和 scale:n
(n
为小数位数,如 scale:4
)标签映射,
GORM 会根据 Go 类型以及 precision
和 scale
自动转换为对应的数据库类型。通过 precision
和 scale
标签映射数据库中的小数类型对照关系如下所示:
序号 | precision 和 scale 标签 |
SQL Server | PostgreSQL | MySQL | Oracle | 达梦 DM8 | SQLite |
---|---|---|---|---|---|---|---|
1. | precision:0;scale:0 |
float |
decimal |
float |
FLOAT |
DOUBLE |
real |
2. | precision:18;scale:0 |
decimal(18) |
numeric(18) |
decimal(18, 0) |
FLOAT |
DECIMAL(18, 0) |
real |
3. | precision:18;scale:4 |
decimal(18, 4) |
numeric(18, 4) |
decimal(18, 4) |
FLOAT |
DECIMAL(18, 4) |
real |
[!Tip]
- https://github.com/go-gorm/sqlserver/blob/v1.5.2/sqlserver.go#L203
- https://github.com/go-gorm/postgres/blob/v1.5.4/postgres.go#L188
- https://github.com/go-gorm/mysql/blob/v1.5.2/mysql.go#L370
- https://github.com/godoes/gorm-oracle/blob/v1.6.1/oracle.go#L452
- https://github.com/godoes/gorm-dameng/blob/v0.0.8/dm.go#L210
- https://github.com/glebarez/sqlite/blob/v1.10.0/sqlite.go#L210
type ExampleStandardModel struct {
FloatField float64 `gorm:"column:float_field;precision:18;scale:4;comment:浮点型小数字段,可通过 precision 和 scale 指定小数精度;" json:"floatField"`
}
Go 字符串类型字段 GORM 数据类型建议使用 size:n
(n
为字段大小,如 size:1000
)标签映射,
GORM 会根据 Go 类型和 size
自动转换为对应的数据库类型。通过 size
标签映射数据库中的字符串类型对照关系如下所示:
序号 | size 标签 |
SQL Server | PostgreSQL | MySQL | Oracle | 达梦 DM8 | SQLite |
---|---|---|---|---|---|---|---|
1. | size:4000 |
nvarchar(4000) |
varchar(4000) |
varchar(4000) |
VARCHAR2(4000) |
VARCHAR(4000) |
text |
2. | size:10485760 |
nvarchar(MAX) |
varchar(10485760) |
mediumtext |
CLOB |
CLOB |
text |
3. | size:-1 |
nvarchar(MAX) |
text |
longtext |
CLOB |
CLOB |
text |
[!Tip]
- https://github.com/go-gorm/sqlserver/blob/v1.5.2/sqlserver.go#L211
- https://github.com/go-gorm/postgres/blob/v1.5.4/postgres.go#L196
- https://github.com/go-gorm/mysql/blob/v1.5.2/mysql.go#L382
- https://github.com/godoes/gorm-oracle/blob/v1.6.1/oracle.go#L454
- https://github.com/godoes/gorm-dameng/blob/v0.0.8/dm.go#L218
- https://github.com/glebarez/sqlite/blob/v1.10.0/sqlite.go#L212
type ExampleStandardModel struct {
NvarcharField string `gorm:"column:nvarchar_field;size:1000;comment:(SQL Server)双字节可变长度字符串类型;" json:"nvarcharField"`
VarcharField string `gorm:"column:varchar_field;type:varchar(1000);comment:(SQL Server)单字节可变长度字符串类型;" json:"varcharField"`
}
由于 nvarchar
类型仅在 SQL Server
数据库中支持,所以 nvarchar
类型的字段不能使用 GORM 的 type:nvarchar(n)
标签映射,
需要使用 size:n
标签。
而 varchar
类型在所有数据库中都支持,所以 varchar
类型的字段可以使用 GORM 的 type:varchar(n)
标签映射。
但是不同数据库对 varchar
类型字段的长度支持有所不同,所以即使是 varchar
类型,也建议使用 size
标签进行映射。
另外,text
/clob
类型的字段请使用 size:-1
标签进行映射。
Go 布尔类型字段 GORM 数据类型不要使用 type:bit
或 type:boolean
标签进行映射,
GORM 会直接根据 Go 类型 bool
自动转换为对应的数据库类型,指定 type
标签后会降低数据库兼容性。
Go 布尔类型映射数据库中的布尔类型对照关系如下所示:
序号 | 数据库类型 | 对应布尔数据类型 | 存储的值 |
---|---|---|---|
1. | SQL Server | bit |
0 / 1 |
2. | PostgreSQL | boolean |
false / true |
3. | MySQL | tinyint(1) |
0 / 1 |
4. | Oracle | NUMBER(1) |
0 / 1 |
5. | 达梦 DM8 | BIT |
0 / 1 |
6. | SQLite | numeric |
0 / 1 |
[!Tip]
- https://github.com/go-gorm/sqlserver/blob/v1.5.2/sqlserver.go#L186
- https://github.com/go-gorm/postgres/blob/v1.5.4/postgres.go#L162
- https://github.com/go-gorm/mysql/blob/v1.5.2/mysql.go#L353
- https://github.com/godoes/gorm-oracle/blob/v1.6.1/oracle.go#L441
- https://github.com/godoes/gorm-dameng/blob/v0.0.8/dm.go#L157
- https://github.com/glebarez/sqlite/blob/v1.10.0/sqlite.go#L200
type ExampleStandardModel struct {
BoolField bool `gorm:"column:bool_field;comment:布尔类型字段" json:"boolField"`
}
Go 字节切片类型字段 GORM 数据类型建议使用 size:-1
标签映射,GORM 会根据 Go 类型和 size
自动转换为对应的数据库类型。
Go 字节切片类型映射数据库中的二进制数据类型对照关系如下所示:
序号 | 数据库类型 | 对应二进制数据类型 |
---|---|---|
1. | SQL Server | varbinary(MAX) |
2. | PostgreSQL | bytea |
3. | MySQL | longblob 、mediumblob 、varbinary(size) |
4. | Oracle | BLOB |
5. | 达梦 DM8 | BLOB 、VARBINARY(size) |
6. | SQLite | blob |
[!Tip]
- https://github.com/go-gorm/sqlserver/blob/v1.5.2/sqlserver.go#L230
- https://github.com/go-gorm/postgres/blob/v1.5.4/postgres.go#L206
- https://github.com/go-gorm/mysql/blob/v1.5.2/mysql.go#L423
- https://github.com/godoes/gorm-oracle/blob/v1.6.1/oracle.go#L486
- https://github.com/godoes/gorm-dameng/blob/v0.0.8/dm.go#L257
- https://github.com/glebarez/sqlite/blob/v1.10.0/sqlite.go#L221
type ExampleStandardModel struct {
BytesField []byte `gorm:"column:bytes_field;size:-1;comment:二进制数据类型字段,可用于存储文件内容" json:"bytesField"`
}
GORM 自动迁移表结构时默认会通过模型结构体的名称自动转换为数据库中的表名,为了在不同的数据库中具有一样的表名,
模型结构体应该实现 GORM 中 Tabler 接口的 TableName
方法,
来指定固定表名:
// TableName 显式指定数据库对应的表名
func (ExampleStandardModel) TableName() string {
return "example_standard"
}
另外,为了防止没有实现 TableName()
方法的模型在不同的数据库中标识符出现命名截断,在初始化 GORM 时应该配置
命名策略 中的 IdentifierMaxLength
标识符最大长度选项为 30
,以保证在不同数据库中保持一致的标识符长度。
推荐配置:
gorm.Config{
// 自定义命名策略
NamingStrategy: schema.NamingStrategy{
IdentifierMaxLength: 30, // Oracle: 30, PostgreSQL:63, MySQL: 64, SQL Server、DM: 128
},
// ...
}
对于实现了 TableName
方法的模型返回的表名和 column
标签指定的字段名尽量不要超过 30
!
GORM 提供了一致的 API 接口,用于在不同数据库系统之间执行 CRUD 操作。
但在实际应用中,仍然需要针对不同数据库系统的特性和要求进行适配和调整。
在多数据库环境下使用 GORM 操作数据库时,需要特别注意以下几点:
使用 GORM 自动迁移表结构时,GORM 会自动使用双引号 ""
或反引号 ``
包裹标识符用于明确指定标识符的大小写,
所以为了提高多数据库的兼容性,在显式指定表名、字段名时需要注意标识符的大小写!
本节内容将通过以下 联合查询语句 进行举例说明:
SELECT A.column_1 AS C1, B.column_1 AS C2
FROM table_a A
LEFT JOIN table_b B ON A.column_2=B.column_2
WHERE A.column_3 = 'true'
ORDER BY A.column_1 DESC
对于以上 SQL 语句,使用 GORM 最基本的实现方式可能是这样的:
db.Select("A.column_1 AS C1, B.column_1 AS C2").
Table("table_a A").
Joins("table_b B ON A.column_2=B.column_2").
Where("A.column_3 = ?", true).
Order("A.column_1 DESC")
Find(...)
但这样生成的 SQL 跟原语句一样并没有明确指定标识符的大小写,不能兼容某些标识符区分大小写数据库。
为了明确指定标识符的大小写以兼容多种数据库,我们最终需要的 SQL 应该是下面这样用双引号 ""
或反引号 ``
(MySQL
和 SQLite
)包裹了标识符的:
SELECT A."column_1" AS C1, B."column_1" AS C2 -- 列名和别名之间可以使用 AS 关键字
FROM "table_a" A -- 表名和别名之间不要使用 AS 关键字
LEFT JOIN "table_b" B ON A."column_2"=B."column_2" -- 表名和字段名要包裹起来明确指定大小写
WHERE A."column_3" = 'true' -- 别名不要包裹起来,且尽量全部大写
ORDER BY A."column_1" DESC
下面将对如何通过 GORM 得到类似上述明确指定标识符的大小写的 SQL 语句进行详细说明。
GORM 框架内部提供了 Table
和
Column
子句表达式,用于生成带引号的表名和字段名。
可以在 GORM 的方法中通过问号 ?
占位符来使用 GORM 子句表达式。
column1 := clause.Column{Name: "column_1"}
column2 := clause.Column{Name: "column_2"}
column3 := clause.Column{Name: "column_3"}
tableA := clause.Table{Name: "table_a"}
tableB := clause.Table{Name: "table_b"}
db.Select("A.? AS C1, B.? AS C2", column1, column1).
Table("? A", tableA).
Joins("? B ON A.?=B.?", tableB, column2, column2).
Where("A.? = ?", column3, true).
Order("A.? DESC", column1)
Find(...)
在使用 GORM 操作数据库时通常都会定义对应表结构的模型结构体,在 GORM 的方法中应该尽可能的去使用模型结构体和结构体中的字段。
上述示例中的 SQL 应该对应以下两个模型结构体:
type TableA struct {
Column1 string `gorm:"column:column_1"`
Column2 string `gorm:"column:column_2"`
Column3 bool `gorm:"column:column_3"`
}
func (*TableA) TableName() string {
return "table_a"
}
type TableB struct {
Column1 string `gorm:"column:column_1"`
Column2 string `gorm:"column:column_2"`
}
func (*TableB) TableName() string {
return "table_b"
}
然后我们可以在 GORM 的方法中使用模型结构体及其字段:
column1 := clause.Column{Name: "column_1"}
column2 := clause.Column{Name: "column_2"}
db.Select("A.? AS C1, B.? AS C2", column1, column1).
Table("(?) A", db.Model(&TableA{}).Where(&TableA{Column3: true})).
Joins("(?) B ON A.?=B.?", db.Model(&TableB{}), column2, column2).
Order("A.? DESC", column1)
Find(...)
在 GORM 中指定表名时,可以通过 db.Table("table_name")
使用字符串指定表名,也可以通过 db.Model(&TableModel{})
使用模型结构体指定表名。
通过合理使用模型结构体,减少了 GORM 子句表达式的使用,使代码更加符合 GORM 的规范,还增加了代码的可读性。
另外可以看到,上述代码中的 Where
子句被放到了 Table
方法中,将 table_a
过滤查询后的整理作为 A
表,减少了别名的使用。
map[string]interface{}
指定查询条件在上一步我们将 Where
查询条件改为了使用模型结构体及其字段作为参数值进行过滤查询,
GORM 的条件方法还支持传入 map[string]interface{}
类型的参数,GORM 会自动使用引号对 map
的 key
进行包裹后作为字段名,
将 map
的 value
作为字段值。
column1 := clause.Column{Name: "column_1"}
column2 := clause.Column{Name: "column_2"}
db.Select("A.? AS C1, B.? AS C2", column1, column1).
Table("(?) A", db.Model(&TableA{}).Where(map[string]interface{}{"column_3": true})).
Joins("(?) B ON A.?=B.?", db.Model(&TableB{}), column2, column2).
Order("A.? DESC", column1)
Find(...)
通过以上操作,可以确保在使用 GORM 操作数据库时能够兼容多种数据库,提高应用程序的灵活性和可移植性。
在处理更复杂 SQL 的多数据库兼容性时,以上 3 种方式可以相互结合使用。
未完待续...
内容声明 | |
---|---|
标题: Go 语言使用 GORM 对象关系映射框架兼容多种数据库 | |
链接: https://zixizixi.cn/go-gorm-for-compatibility-with-multiple-databases | 来源: iTanken |
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可,转载请保留此声明。
|