酷Go推荐 go-pg postgresql 库 之 模型和占位符

haolipeng12345 · 2021年08月10日 · 88 次阅读

在上一节的文章中(这里需要把上个文章的链接帖下 TODO:),我们学会了 go-pg 的基本操作,创建和删除数据库表,相关的增删改查操作等,今天我们来进一步了解下 ORM 框架基础、模型 (model)、SQL 占位符的知识。

一、ORM 框架简介

对象关系映射(Object Relational Mapping,简称 ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。

那对象和数据库是如何映射的呢?

数据库 面向对象的编程语言
表 (table) 类 (class/struct)
记录 (record,row) 对象 (object)
字段 (field,column) 对象属性 (attribute)

举一个具体的例子,来理解 ORM:

CREATE TABLE `User` (`Name` text, `Age` integer);
INSERT INTO `User` (`Name`, `Age`) VALUES ("Tom", 18);
SELECT * FROM `User`;

第一条 SQL 语句,在数据库中创建了表 User,并且定义了 2 个字段 NameAge;第二条 SQL 语句往表中添加了一条记录;最后一条语句返回表中的所有记录。

如果采用 ORM 框架来创建表 User,如下:

type User struct {
    Name string
    Age  int
}

pg.CreateTable(&User{})

ORM 框架相当于对象和数据库中间的一个桥梁,借助 ORM 可以避免写繁琐的 SQL 语言,仅仅通过操作具体的对象,就能够完成对关系型数据库的操作。

二、定义模型 Model

对于每个 PostgreSQL 表,您需要定义一个相应的 Go 结构体(模型)。 go-pg 将导出的结构体字段映射到数据库表列,并忽略未导出的字段。

2、1 结构体标签

示例 作用
tableName struct{} pg:"table_name" 覆盖默认表名
tableName struct{} pg:"alias:table_alias" 覆盖默认表别名。
tableName struct{} pg:"select:view_name" 覆盖 SELECT 查询的表名。
tableName struct{} pg:",discard_unknown_columns" 默默地丢弃未知列而不是返回错误。(很实用)
pg:"-" 忽略字段
pg:"column_name" 覆盖默认表列名。
pg:"alias:alt_name" 备用列名。在重命名列时很有用。
pg:",pk" 将列标记为主键。支持多个主键。
pg:",nopk" 非主键。对像 iduuid 这样的列很有用。
pg:"type:uuid" 覆盖默认的 SQL 类型
pg:"default:gen_random_uuid()" SQL default value for the column. go-pg uses the DEFAULT placeholder and PostgreSQL replaces it with the provided expression.
pg:",notnull" 添加 sql 非空约束
pg:",unique" 使 CreateTable 添加唯一约束。
pg:",unique:group_name" 一组列的唯一约束
pg:"on_delete:RESTRICT" 外键的 “ON DELETE” 子句。
pg:",array" 将该列视为 PostgreSQL 数组。
pg:",hstore" 将该列视为 PostgreSQL hstore。
pg:"composite:type_name" 将列视为 PostgreSQL 组合。
pg:",use_zero" 禁用将 Go 零值编组为 SQL NULL
pg:",json_use_number" 会用json.Decoder.UseNumber 解析 JSON.
pg:",msgpack" 使用 MessagePack 编码/解码数据。
pg:"partition_by:RANGE (time)" Specifies table partitioning for CreateTable.
DeletedAt time.Time pg:",soft_delete" 启用软删除

postgresql 的 array 和 hstore 概念,array 和 hstore 是个很强大的东西,我当时还躺了不少坑。

此外,以下标签可用于 ORM 关系(不是列):

Editor *Authorpg:"rel:has-one"`` Marks Editor field as a has-one relation.
User *Userpg:"fk:user_id"`` 覆盖基表的默认外键。
Genres []Genrepg:"join_fk:book_id"`` 覆盖连接表的默认外键。
Comments []Commentpg:"polymorphic,join_fk:trackable_"`` 多态 has-many 关系.
Genres []Genrepg:"many2many:book_genres"`` 多对多关系的连接表。

2、2 SQL 命名惯例

为避免错误,请使用 snake_case 名称。如果您遇到虚假的 SQL 解析器错误,请尝试用双引号引用标识符以检查问题是否消失。

警告:不要使用 sql 关键字(如 order,user)作为标识符

警告:不要使用区分大小写的名称,因为这样的名称会被折叠成小写(例如 UserOrders 变成 userorders)。

2、3 表名

表名和别名是通过下划线从结构名自动派生的。表名也是复数形式,例如 struct Genre 生到表名 genres 和别名 genre。您可以使用 tableName 字段覆盖默认表名和别名:

type Genre struct {
    tableName struct{} `pg:"genres,alias:g"` #覆盖默认表名和别名
}

pg:"select:xxx"标签可为 SELECT 查询指定不同的表名:

type Genre struct {
    tableName struct{} `pg:"select:genres_view,alias:g"`
}

上述代码为 SELECT 查询指定表明为 genres_view

引用有问题(或容易歧义)的标识符:

type User struct {
    tableName struct{} `pg:"\"user\",alias:u"`
}

“user” 部分加上了\符号。

覆盖复数化结构体名称的默认函数:

func init() {
    orm.SetTableNameInflector(func(s string) string {
        return "myprefix_" + s
    })
}

2、4 列名

列名通过下划线从结构字段名派生,例如 struct field ParentId 获取列名 parent_id。 可以使用 pg 标签覆盖默认列名:

type Genre struct {
    Id int `pg:"pk_id"`
}

go-pg 从结构字段类型派生数据库的列类型,例如 go 的 string 对应 postgresql 的 text 类型(默认情况)。可以使用 pg:"type:varchar(255)" 标签覆盖默认列类型(此处指 text 类型)。

下面是 go 语言类型和 PostgreSQL 类型的对应关系

Go type PostgreSQL type
int8, uint8, int16 smallint
uint16, int32 integer
uint32, int64, int bigint
uint, uint64 bigint
float32 real
float64 double precision
bool boolean
string text
[] byte bytea
struct, map, array jsonb
time.Time timestamptz
net.IP inet
net.IPNet cidr

2、5 丢弃未知列

要丢弃未知列,请在其前面加上下划线,例如 _ignore_me。

您还可以添加标签 tableName struct{} pg:",discard_unknown_columns" 以丢弃所有未知列。

这样能避免遇到未知列时 go-pg 会返回错误。

三、SQL 占位符

go-pg 会在查询语句中将?识别为占位符,并用参数来替换它。

在根据 PostgreSQL 规则替换 go-pg 转义参数值之前:

  • A 所有参数都正确被引用来防止 sql 注入
  • 空字节'0'被移除.
  • JSON/JSONB 将 \u0000 转义为 \\u0000.

3、1 普通占位符

使用基础的占位符

// SELECT 'foo', 'bar'
db.ColumnExpr("?, ?", 'foo', 'bar')

使用位置占位符

// SELECT 'foo', 'bar', 'foo'db.ColumnExpr("?0, ?1, ?0", 'foo', 'bar')

3、2 命名占位符

定义一个带有必填字段的结构,来命名占位符:

type Params struct {    X int    Y int}params := &Params{    X: 1,    Y: 2,}// SELECT 1 + 2db.ColumnExpr("?x + ?y", params)

你甚至可以使用返回单个值的方法作为占位符:

func (p *Params) Sum() int {    return p.X + p.Y}// SELECT 1 + 2 = 3db.ColumnExpr("?x + ?y = ?Sum", params)

3、3 PostgreSQL 标识符和禁用引号

要引用 PostgreSQL 标识符(列名或表名),请使用 pg.Ident:

// "foo" = 'bar'db.ColumnExpr("? = ?", pg.Ident("foo"), "bar")

要完全禁用引用,请使用 pg.Safe:

// FROM (generate_series(0, 10)) AS foodb.TableExpr("(?) AS foo", pg.Safe("generate_series(0, 10)"))

3、4 PostgreSQL IN 语句

要将 IN 与多个值一起使用,请使用 pg.In:

// WHERE foo IN ('hello', 'world')db.Where("foo IN (?)", pg.In([]string{"hello", "world"}))

将 IN 与复合(多个)键一起使用:

// WHERE (foo, bar) IN (('hello', 'world'), ('hell', 'yeah'))db.Where("(foo, bar) IN (?)", pg.InMulti(    []string{"hello", "world"},    []string{"hell", "yeah"},))

3、5 PostgreSQL Arrays 数组

要使用 PostgreSQL arrays 数组类型,请使用 pg.Array:

// WHERE foo @> '{"foo","bar"}'db.Where("foo @> ?", pg.Array([]string{"foo", "bar"}))

3、6 全局数据库占位符

go-pg 还支持全局数据库占位符:

// db1 and db2 share the same connection pool.//db1和db2共享同一个连接池db1 := db.WithParam("SCHEMA", "foo")db2 := db.WithParam("SCHEMA", "bar")// FROM foo.tabledb1.TableExpr("?SCHEMA.table")// FROM bar.tabledb2.TableExpr("?SCHEMA.table")

go-pg/sharding 使用此功能来实现使用 PostgreSQL 模式的分片。

参考资料

http://www.ruanyifeng.com/blog/2019/02/orm-tutorial.html

https://pg.uptrace.dev/

https://medium.com/tunaiku-tech/go-pg-golang-postgre-orm-2618b75c0430

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
keke001 GoCN 每日新闻(2021-08-11) 中提及了此贴 08月11日 01:36
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册