Go语言学习笔记—xorm

文章目录

  • 一 xorm简介
    • 1.1 特性
    • 1.2 驱动支持
  • 二 第一个xorm应用
    • 2.1 安装库
    • 2.2 创建数据库
    • 2.3 连接数据库
    • 2.4 创建结构体
    • 2.5 创建表
    • 2.6 添加数据
    • 2.7 完整代码
  • 三 xorm定义表结构体
    • 3.1 表名称映射规则
    • 3.2 前缀映射,后缀映射和缓存映射
    • 3.3 使用 Table 和 Tag 改变名称映射
    • 3.4 Column 属性定义
  • 四 xorm表结构操作
    • 4.1 获取数据库信息
    • 4.2 表操作
    • 4.3 创建索引和唯一索引
    • 4.4 同步数据库结构
    • 4.5 Dump 数据库结构和数据
    • 4.6 Import 执行数据库SQL脚本
  • 五 xorm插入数据
    • 5.1 插入数据
    • 5.2 创建时间Created
  • 六 xorm查询和统计数据
    • 6.1 查询条件方法
    • 6.2 Get方法
    • 6.3 Exist系列方法
      • 与Get的区别
      • 建议
    • 6.4 Find方法
    • 6.5 Iterate方法
    • 6.6 Rows方法
    • 6.7 Count方法
    • 6.8 Sum系列方法
  • 七 xorm删除数据
    • 7.1 删除数据
    • 7.2 软删除Deleted
  • 八 xorm更新数据
  • 九 xorm事务处理


一 xorm简介

xorm是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。xorm的目标并不是让你完全不去学习SQL,我们认为SQL并不会为ORM所替代,但是ORM将可以解决绝大部分的简单SQL需求。xorm支持两种风格的混用。

官方文档:https://xorm.io/zh/docs/

1.1 特性

  • 支持 Struct 和数据库表之间的灵活映射,并支持自动同步
  • 事务支持
  • 同时支持原始SQL语句和 ORM 操作的混合执行
  • 使用连写来简化调用
  • 支持使用ID, In, Where, Limit, Join, Having, Table, SQL, Cols等函数和结构体等方式作为条件
  • 支持级联加载 Struct
  • Schema支持(仅Postgres)
  • 支持缓存
  • 通过 xorm.io/reverse 支持根据数据库自动生成 xorm 结构体
  • 支持记录版本(即乐观锁)
  • 通过 xorm.io/builder 内置 SQL Builder 支持
  • 上下文缓存支持
  • 支持日志上下文

1.2 驱动支持

  • Mysql5.* / Mysql8.* / Mariadb / Tidb
    • github.com/go-sql-driver/mysql
    • github.com/ziutek/mymysql/godrv
  • Postgres / Cockroach
    • github.com/lib/pq
  • SQLite
    • github.com/mattn/go-sqlite3
  • MsSql
    • github.com/denisenkom/go-mssqldb
  • Oracle
    • github.com/mattn/go-oci8 (试验性支持)

二 第一个xorm应用

2.1 安装库

go get xorm.io/xorm
go get github.com/go-sql-driver/mysql

2.2 创建数据库

C:\Users\Mechrevo>mysql -uroot -p
Enter password: ******
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 20
Server version: 8.0.19 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> create database test_xorm;
Query OK, 1 row affected (0.48 sec)

mysql> use test_xorm;
Database changed

2.3 连接数据库

package main

import (
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"xorm.io/xorm"
)

var engine *xorm.Engine

func main() {

	var err error

	engine, err = xorm.NewEngine("mysql", "root:960690@/test_xorm?charset=utf8")
	if err != nil {
		fmt.Printf("err : %v\n", err)
	} else {
		err2 := engine.Ping()
		if err2 != nil {
			fmt.Printf("err2 : %v\n", err2)
		} else {
			print("数据库连接成功!")
		}
	}
}

运行结果:

[Running] go run "e:\golang开发学习\go_pro\test.go"
[xorm] [info]  2022/08/11 17:04:41.129767 PING DATABASE mysql
数据库连接成功!
[Done] exited with code=0 in 2.191 seconds

2.4 创建结构体

type User struct {
	Id      int64
	Name    string
	Salt    string
	Age     int
	Passwd  string    `xorm:"varchar(200)"`
	Created time.Time `xorm:"created"`
	Updated time.Time `xorm:"updated"`
}

2.5 创建表

// 创建表
err3 := engine.Sync(new(User))
if err3 != nil {
	fmt.Printf("err2 : %v\n", err3)
} else {
	fmt.Println("表创建成功!")
}

数据库查看:

Go语言学习笔记—xorm_第1张图片

2.6 添加数据

// 添加数据
user := User{
	Id:     1,
	Name:   "Psych",
	Salt:   "salt",
	Age:    18,
	Passwd: "666",
}
i, err4 := engine.Insert(&user)
if err4 != nil {
	fmt.Printf("err : %v\n", err4)
} else {
	fmt.Printf("创建记录数量: %v\n", i)
}

数据库查看:

Go语言学习笔记—xorm_第2张图片

2.7 完整代码

package main

import (
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"xorm.io/xorm"
)

var engine *xorm.Engine

type User struct {
	Id      int64
	Name    string
	Salt    string
	Age     int
	Passwd  string    `xorm:"varchar(200)"`
	Created time.Time `xorm:"created"`
	Updated time.Time `xorm:"updated"`
}

func main() {

	var err error

	engine, err = xorm.NewEngine("mysql", "root:960690@/test_xorm?charset=utf8")
	if err != nil {
		fmt.Printf("err : %v\n", err)
	} else {
		err2 := engine.Ping()
		if err2 != nil {
			fmt.Printf("err2 : %v\n", err2)
		} else {
			print("数据库连接成功!")
		}
	}

	// 创建表
	err3 := engine.Sync(new(User))
	if err3 != nil {
		fmt.Printf("err2 : %v\n", err3)
	} else {
		fmt.Println("表创建成功!")
	}

	// 添加数据
	user := User{
		Id:     1,
		Name:   "Psych",
		Salt:   "salt",
		Age:    18,
		Passwd: "666",
	}
	i, err4 := engine.Insert(&user)
	if err4 != nil {
		fmt.Printf("err : %v\n", err4)
	} else {
		fmt.Printf("创建记录数量: %v\n", i)
	}
}

三 xorm定义表结构体

3.1 表名称映射规则

跟名称相关的函数包含在 xorm.io/xorm/names 下。名称映射规则主要负责结构体名称到表名和结构体 field 到表字段的名称映射。由 names.Mapper 接口的实现者来管理,xorm 内置了三种 Mapper 实现:names.SnakeMappernames.SameMappernames.GonicMapper

  • SnakeMapper:支持struct为驼峰式命名,表结构中为下划线命名之间的转换。该种规则为xorm默认的Maper;
  • SameMapper:映射规则支持结构体名称和对应的表名称以及结构体field名称与对应的表字段名称相同的命名;
  • GonicMapper:该映射规则和驼峰式命名类似,但是对于特定词支持性更好,比如ID将会翻译成id,而不是驼峰式的i_d。

当前SnakeMapper为默认值,如果需要改变时,在engine创建完成后使用

engine.SetMapper(names.GonicMapper{})

另外,可以设置表名和表字段分别为不同的映射规则:

engine.SetTableMapper(names.SameMapper{})

engine.SetColumnMapper(names.SnakeMapper{})

测试:

package main

import (
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"xorm.io/xorm"
	"xorm.io/xorm/names"
)

var engine *xorm.Engine

type LoginUser struct {
	Id      int64
	Name    string
	Salt    string
	Age     int
	Passwd  string    `xorm:"varchar(200)"`
	Created time.Time `xorm:"created"`
	Updated time.Time `xorm:"updated"`
}

func init() {
	var err error

	engine, err = xorm.NewEngine("mysql", "root:960690@/test_xorm?charset=utf8")
	if err != nil {
		fmt.Printf("err : %v\n", err)
	} else {
		err2 := engine.Ping()
		if err2 != nil {
			fmt.Printf("err2 : %v\n", err2)
		} else {
			print("数据库连接成功!")
		}
	}
}

func main() {
	engine.ShowSQL(true)

	// engine.SetMapper(names.GonicMapper{})
	engine.SetMapper(names.SnakeMapper{})
	// engine.SetMapper(names.SameMapper{})

	// 创建表
	err := engine.Sync(new(LoginUser))
	if err != nil {
		fmt.Printf("err : %v\n", err)
	} else {
		fmt.Println("表创建成功!")
	}
}

数据库查看:

Go语言学习笔记—xorm_第3张图片

3.2 前缀映射,后缀映射和缓存映射

  • 通过 names.NewPrefixMapper(names.SnakeMapper{}, "prefix") 可以创建一个在 SnakeMapper 的基础上在命名中添加统一的前缀,当然也可以把 SnakeMapper{} 换成 SameMapper 或者你自定义的 Mapper。

例如,如果希望所有的表名都在结构体自动命名的基础上加一个前缀而字段名不加前缀,则可以在 engine 创建完成后执行以下语句:

tbMapper := names.NewPrefixMapper(names.SnakeMapper{}, "prefix_")
engine.SetTableMapper(tbMapper)

执行之后,结构体 type User struct 默认对应的表名就变成了 prefix_user 了,而之前默认的是 user

  • 通过 names.NewSuffixMapper(names.SnakeMapper{}, "suffix") 可以创建一个在 SnakeMapper 的基础上在命名中添加统一的后缀,当然也可以把SnakeMapper换成SameMapper或者你自定义的Mapper。
  • 通过 names.NewCacheMapper(names.SnakeMapper{}) 可以创建一个组合了其它的映射规则,起到在内存中缓存曾经映射过的命名映射。

测试:

package main

import (
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"xorm.io/xorm"
	"xorm.io/xorm/names"
)

var engine *xorm.Engine

type LoginUser struct {
	Id      int64
	Name    string
	Salt    string
	Age     int
	Passwd  string    `xorm:"varchar(200)"`
	Created time.Time `xorm:"created"`
	Updated time.Time `xorm:"updated"`
}

func init() {
	var err error

	engine, err = xorm.NewEngine("mysql", "root:960690@/test_xorm?charset=utf8")
	if err != nil {
		fmt.Printf("err : %v\n", err)
	} else {
		err2 := engine.Ping()
		if err2 != nil {
			fmt.Printf("err2 : %v\n", err2)
		} else {
			print("数据库连接成功!")
		}
	}
}

func main() {
	engine.ShowSQL(true)

	tbMapper := names.NewPrefixMapper(names.SnakeMapper{}, "golang_")
	engine.SetTableMapper(tbMapper)

	// 创建表
	err := engine.Sync(new(LoginUser))
	if err != nil {
		fmt.Printf("err : %v\n", err)
	} else {
		fmt.Println("表创建成功!")
	}
}

数据库查看:

Go语言学习笔记—xorm_第4张图片

3.3 使用 Table 和 Tag 改变名称映射

如果所有的命名都是按照 Mapper 的映射来操作的,那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时,我们就需要别的机制来改变。xorm 提供了如下几种方式来进行:

  • 如果结构体拥有 TableName() string 的成员方法,那么此方法的返回值即是该结构体对应的数据库表名。
  • 通过 engine.Table() 方法可以改变 struct 对应的数据库表的名称,通过 sturct 中 field 对应的 Tag 中使用 xorm:"'column_name'"可以使该 field 对应的 Column 名称为指定名称。这里使用两个单引号将 Column 名称括起来是为了防止名称冲突,因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况,单引号也可以不使用。

到此名称映射的所有方法都给出了,一共三种方式,这三种是有优先级顺序的。

  • 表名的优先级顺序如下:
    • engine.Table() 指定的临时表名优先级最高
    • TableName() string 其次
    • Mapper 自动映射的表名优先级最后
  • 字段名的优先级顺序如下:
    • 结构体tag指定的字段名优先级较高
    • Mapper 自动映射的表名优先级较低

3.4 Column 属性定义

我们在 field 对应的 Tag 中对 Column 的一些属性进行定义,定义的方法基本和我们写SQL定义表结构类似,比如:

type User struct {
    Id   int64
    Name string  `xorm:"varchar(25) notnull unique 'usr_name' comment('姓名')"`
}

对于不同的数据库系统,数据类型其实是有些差异的。因此xorm中对数据类型有自己的定义,基本的原则是尽量兼容各种数据库的字段类型,具体的字段对应关系可以查看字段类型对应表。对于使用者,一般只要使用自己熟悉的数据库字段定义即可。

具体的 Tag 规则如下,另 Tag 中的关键字均不区分大小写,但字段名根据不同的数据库是区分大小写:

name 当前field对应的字段的名称,可选,如不写,则自动根据field名字和转换规则命名,如与其它关键字冲突,请使用单引号括起来。
pk 是否是Primary Key,如果在一个struct中有多个字段都使用了此标记,则这多个字段构成了复合主键,单主键当前支持int32,int,int64,uint32,uint,uint64,string这7种Go的数据类型,复合主键支持这7种Go的数据类型的组合。
当前支持30多种字段类型,详情参见本文最后一个表格 字段类型
autoincr 是否是自增
[not ]null 或 notnull 是否可以为空
unique或unique(uniquename) 是否是唯一,如不加括号则该字段不允许重复;如加上括号,则括号中为联合唯一索引的名字,此时如果有另外一个或多个字段和本unique的uniquename相同,则这些uniquename相同的字段组成联合唯一索引
index或index(indexname) 是否是索引,如不加括号则该字段自身为索引,如加上括号,则括号中为联合索引的名字,此时如果有另外一个或多个字段和本index的indexname相同,则这些indexname相同的字段组成联合索引
extends 应用于一个匿名成员结构体或者非匿名成员结构体之上,表示此结构体的所有成员也映射到数据库中,extends可加载无限级
- 这个Field将不进行字段映射
-> 这个Field将只写入到数据库而不从数据库读取
<- 这个Field将只从数据库读取,而不写入到数据库
created 这个Field将在Insert时自动赋值为当前时间
updated 这个Field将在Insert或Update时自动赋值为当前时间
deleted 这个Field将在Delete时设置为当前时间,并且当前记录不删除
version 这个Field将会在insert时默认为1,每次更新自动加1
default 0或default(0) 设置默认值,紧跟的内容如果是Varchar等需要加上单引号
json 表示内容将先转成Json格式,然后存储到数据库中,数据库中的字段类型可以为Text或者二进制
comment 设置字段的注释(当前仅支持mysql)

另外有如下几条自动映射的规则:

  • 1.如果field名称为Id而且类型为int64并且没有定义tag,则会被xorm视为主键,并且拥有自增属性。如果想用Id以外的名字或非int64类型做为主键名,必须在对应的Tag上加上xorm:"pk"来定义主键,加上xorm:"autoincr"作为自增。这里需要注意的是,有些数据库并不允许非主键的自增属性。
  • 2.string类型默认映射为varchar(255),如果需要不同的定义,可以在tag中自定义,如:varchar(1024)
  • 3.支持type MyString string等自定义的field,支持Slice, Map等field成员,这些成员默认存储为Text类型,并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型。如果是Blob类型,则先使用Json格式序列化再转成[]byte格式。如果是[]byte或者[]uint8,则不做转换二十直接以二进制方式存储。具体参见 Go与字段类型对应表
  • 4.实现了Conversion接口的类型或者结构体,将根据接口的转换方式在类型和数据库记录之间进行相互转换,这个接口的优先级是最高的。
type Conversion interface {
    FromDB([]byte) error
    ToDB() ([]byte, error)
}
  • 5.如果一个结构体包含一个 Conversion 的接口类型,那么在获取数据时,必须要预先设置一个实现此接口的struct或者struct的指针。此时可以在此struct中实现BeforeSet(name string, cell xorm.Cell)方法来进行预先给Conversion赋值。例子参见 testConversion

下表为xorm类型和各个数据库类型的对应表:


BIT

BIT

INTEGER

BIT





TINYINT

TINYINT

INTEGER

SMALLINT



xorm mysql sqlite3 postgres remark
SMALLINT SMALLINT INTEGER SMALLINT
MEDIUMINT MEDIUMINT INTEGER INTEGER
INT INT INTEGER INTEGER
INTEGER INTEGER INTEGER INTEGER
BIGINT BIGINT INTEGER BIGINT
CHAR CHAR TEXT CHAR
VARCHAR VARCHAR TEXT VARCHAR
TINYTEXT TINYTEXT TEXT TEXT
TEXT TEXT TEXT TEXT
MEDIUMTEXT MEDIUMTEXT TEXT TEXT
LONGTEXT LONGTEXT TEXT TEXT
BINARY BINARY BLOB BYTEA
VARBINARY VARBINARY BLOB BYTEA
DATE DATE NUMERIC DATE
DATETIME DATETIME NUMERIC TIMESTAMP
TIME TIME NUMERIC TIME
TIMESTAMP TIMESTAMP NUMERIC TIMESTAMP
TIMESTAMPZ TEXT TEXT TIMESTAMP with zone timestamp with zone info
REAL REAL REAL REAL
FLOAT FLOAT REAL REAL
DOUBLE DOUBLE REAL DOUBLE PRECISION
DECIMAL DECIMAL NUMERIC DECIMAL
NUMERIC NUMERIC NUMERIC NUMERIC
TINYBLOB TINYBLOB BLOB BYTEA
BLOB BLOB BLOB BYTEA
MEDIUMBLOB MEDIUMBLOB BLOB BYTEA
LONGBLOB LONGBLOB BLOB BYTEA
BYTEA BLOB BLOB BYTEA
BOOL TINYINT INTEGER BOOLEAN
SERIAL INT INTEGER SERIAL auto increment
BIGSERIAL BIGINT INTEGER BIGSERIAL auto increment

四 xorm表结构操作

xorm 提供了一些动态获取和修改表结构的方法,通过这些方法可以动态同步数据库结构,导出数据库结构,导入数据库结构。

但是通常使用数据库相关工具,而并非使用编程思想操作表结构,该部分了解就好

4.1 获取数据库信息

  • DBMetas()

    xorm支持获取表结构信息,通过调用 engine.DBMetas() 可以获取到数据库中所有的表,字段,索引的信息。

    测试:

    package main
    
    import (
    	"fmt"
    	"time"
    
    	_ "github.com/go-sql-driver/mysql"
    	"xorm.io/xorm"
    )
    
    var engine *xorm.Engine
    
    type LoginUser struct {
    	Id      int64
    	Name    string
    	Salt    string
    	Age     int
    	Passwd  string    `xorm:"varchar(200)"`
    	Created time.Time `xorm:"created"`
    	Updated time.Time `xorm:"updated"`
    }
    
    func init() {
    	var err error
    
    	engine, err = xorm.NewEngine("mysql", "root:960690@/test_xorm?charset=utf8")
    	if err != nil {
    		fmt.Printf("err : %v\n", err)
    	} else {
    		err2 := engine.Ping()
    		if err2 != nil {
    			fmt.Printf("err2 : %v\n", err2)
    		} else {
    			print("数据库连接成功!")
    		}
    	}
    }
    
    func main() {
    	t, _ := engine.DBMetas()
    	for _, v := range t {
    		fmt.Printf("v: %v\n", v.Name)
    	}
    }
    

    运行结果:

    [xorm] [info]  2022/08/11 17:53:32.757013 PING DATABASE mysql
    数据库连接成功!v: golang_login_user
    v: login_user
    v: loginuser
    v: user
    
  • TableInfo()

    根据传入的结构体指针及其对应的Tag,提取出模型对应的表结构信息。这里不是数据库当前的表结构信息,而是我们通过struct建模时希望数据库的表的结构信息

    测试:

    package main
    
    import (
    	"fmt"
    	"time"
    
    	_ "github.com/go-sql-driver/mysql"
    	"xorm.io/xorm"
    )
    
    var engine *xorm.Engine
    
    type LoginUser struct {
    	Id      int64
    	Name    string
    	Salt    string
    	Age     int
    	Passwd  string    `xorm:"varchar(200)"`
    	Created time.Time `xorm:"created"`
    	Updated time.Time `xorm:"updated"`
    }
    
    func init() {
    	var err error
    
    	engine, err = xorm.NewEngine("mysql", "root:960690@/test_xorm?charset=utf8")
    	if err != nil {
    		fmt.Printf("err : %v\n", err)
    	} else {
    		err2 := engine.Ping()
    		if err2 != nil {
    			fmt.Printf("err2 : %v\n", err2)
    		} else {
    			print("数据库连接成功!")
    		}
    	}
    }
    
    func main() {
    	t, _ := engine.DBMetas()
    	for _, v := range t {
    		t2, _ := engine.TableInfo(v)
    		fmt.Printf("t2: %v\n", t2)
    	}
    }
    
    

    运行结果:

    [xorm] [info]  2022/08/11 17:56:11.996657 PING DATABASE mysql
    数据库连接成功!t2: &{table schemas.Table [name type indexes primary_keys auto_increment created updated deleted version store_engine charset comment] map[auto_increment:[0xc0002022d0] charset:[0xc000202870] comment:[0xc000202960] created:[0xc0002023c0] deleted:[0xc0002025a0] indexes:[0xc0002020f0] name:[0xc0000fdef0] primary_keys:[0xc0002021e0] store_engine:[0xc000202780] type:[0xc000202000] updated:[0xc0002024b0] version:[0xc000202690]] [0xc0000fdef0 0xc000202000 0xc0002020f0 0xc0002021e0 0xc0002022d0 0xc0002023c0 0xc0002024b0 0xc0002025a0 0xc000202690 0xc000202780 0xc000202870 0xc000202960] map[] []  map[]      }
    t2: &{table schemas.Table [name type indexes primary_keys auto_increment created updated deleted version store_engine charset comment] map[auto_increment:[0xc0002022d0] charset:[0xc000202870] comment:[0xc000202960] created:[0xc0002023c0] deleted:[0xc0002025a0] indexes:[0xc0002020f0] name:[0xc0000fdef0] primary_keys:[0xc0002021e0] store_engine:[0xc000202780] type:[0xc000202000] updated:[0xc0002024b0] version:[0xc000202690]] [0xc0000fdef0 0xc000202000 0xc0002020f0 0xc0002021e0 0xc0002022d0 0xc0002023c0 0xc0002024b0 0xc0002025a0 0xc000202690 0xc000202780 0xc000202870 0xc000202960] map[] []  map[]      }
    t2: &{table schemas.Table [name type indexes primary_keys auto_increment created updated deleted version store_engine charset comment] map[auto_increment:[0xc0002022d0] charset:[0xc000202870] comment:[0xc000202960] created:[0xc0002023c0] deleted:[0xc0002025a0] indexes:[0xc0002020f0] name:[0xc0000fdef0] primary_keys:[0xc0002021e0] store_engine:[0xc000202780] type:[0xc000202000] updated:[0xc0002024b0] version:[0xc000202690]] [0xc0000fdef0 0xc000202000 0xc0002020f0 0xc0002021e0 0xc0002022d0 0xc0002023c0 0xc0002024b0 0xc0002025a0 0xc000202690 0xc000202780 0xc000202870 0xc000202960] map[] []  map[]      }
    t2: &{table schemas.Table [name type indexes primary_keys auto_increment created updated deleted version store_engine charset comment] map[auto_increment:[0xc0002022d0] charset:[0xc000202870] comment:[0xc000202960] created:[0xc0002023c0] deleted:[0xc0002025a0] indexes:[0xc0002020f0] name:[0xc0000fdef0] primary_keys:[0xc0002021e0] store_engine:[0xc000202780] type:[0xc000202000] updated:[0xc0002024b0] version:[0xc000202690]] [0xc0000fdef0 0xc000202000 0xc0002020f0 0xc0002021e0 0xc0002022d0 0xc0002023c0 0xc0002024b0 0xc0002025a0 0xc000202690 0xc000202780 0xc000202870 0xc000202960] map[] []  map[]      }
    
    

4.2 表操作

  • CreateTables()

    创建表使用 engine.CreateTables(),参数为一个或多个空的对应Struct的指针。同时可用的方法有 Charset() 和 StoreEngine(),如果对应的数据库支持,这两个方法可以在创建表时指定表的字符编码和使用的引擎。Charset() 和 StoreEngine() 当前仅支持 Mysql 数据库。

  • IsTableEmpty()

    判断表是否为空,参数和 CreateTables 相同

  • IsTableExist()

    判断表是否存在

  • DropTables()

    删除表使用 engine.DropTables(),参数为一个或多个空的对应Struct的指针或者表的名字。如果为string传入,则只删除对应的表,如果传入的为Struct,则删除表的同时还会删除对应的索引。

4.3 创建索引和唯一索引

  • CreateIndexes

    根据struct中的tag来创建索引

  • CreateUniques

    根据struct中的tag来创建唯一索引

4.4 同步数据库结构

同步能够部分智能的根据结构体的变动检测表结构的变动,并自动同步。目前有两个实现:

  • Sync

Sync和Sync2为同一个函数,目前推荐使用Sync2。Sync函数将进行如下的同步操作:

err := engine.Sync2(new(User), new(Group))
  • 自动检测和创建表,这个检测是根据表的名字
  • 自动检测和新增表中的字段,这个检测是根据字段名,同时对表中多余的字段给出警告信息
  • 自动检测,创建和删除索引和唯一索引,这个检测是根据索引的一个或多个字段名,而不根据索引名称。因此这里需要注意,如果在一个有大量数据的表中引入新的索引,数据库可能需要一定的时间来建立索引。
  • 自动转换varchar字段类型到text字段类型,自动警告其它字段类型在模型和数据库之间不一致的情况。
  • 自动警告字段的默认值,是否为空信息在模型和数据库之间不匹配的情况

以上这些警告信息需要将日志的显示级别调整为Warn级别才会显示。

测试:

package main

import (
	"fmt"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"xorm.io/xorm"
)

var engine *xorm.Engine

type LoginUser1 struct {
	Id      int64
	Name    string
	Salt    string
	Age     int
	Passwd  string    `xorm:"varchar(200)"`
	Created time.Time `xorm:"created"`
	Updated time.Time `xorm:"updated"`
}

type Customer struct {
	Id      int64
	Name    string
	Salt    string
	Age     int
	Passwd  string    `xorm:"varchar(200)"`
	Created time.Time `xorm:"created"`
	Updated time.Time `xorm:"updated"`
}

func init() {
	var err error

	engine, err = xorm.NewEngine("mysql", "root:960690@/test_xorm?charset=utf8")
	if err != nil {
		fmt.Printf("err : %v\n", err)
	} else {
		err2 := engine.Ping()
		if err2 != nil {
			fmt.Printf("err2 : %v\n", err2)
		} else {
			print("数据库连接成功!")
		}
	}
}

func main() {
	engine.Sync2(new(LoginUser1), new(Customer))
}

数据库查看:

Go语言学习笔记—xorm_第5张图片

4.5 Dump 数据库结构和数据

如果需要在程序中 Dump 数据库的结构和数据可以调用

engine.DumpAll(w io.Writer)

engine.DumpAllToFile(fpath string)

DumpAll 方法接收一个io.Writer接口来保存Dump出的数据库结构和数据的SQL语句,这个方法导出的SQL语句并不能通用。只针对当前engine所对应的数据库支持的SQL。

4.6 Import 执行数据库SQL脚本

如果你需要将保存在文件或者其它存储设施中的SQL脚本执行,那么可以调用

engine.Import(r io.Reader)

engine.ImportFile(fpath string)

同样,这里需要对应的数据库的SQL语法支持。

五 xorm插入数据

5.1 插入数据

插入数据使用Insert方法,Insert方法的参数可以是一个或多个Struct的指针,一个或多个Struct的Slice的指针。

如果传入的是Slice并且当数据库支持批量插入时,Insert会使用批量插入的方式进行插入。

  • 插入一条数据,此时可以用Insert或者InsertOne
user := new(User)
user.Name = "myname"
affected, err := engine.Insert(user)
// INSERT INTO user (name) values (?)

在插入单条数据成功后,如果该结构体有自增字段(设置为autoincr),则自增字段会被自动赋值为数据库中的id。这里需要注意的是,如果插入的结构体中,自增字段已经赋值,则该字段会被作为非自增字段插入。

fmt.Println(user.Id)
  • 插入同一个表的多条数据,此时如果数据库支持批量插入,那么会进行批量插入,但是这样每条记录就无法被自动赋予id值。如果数据库不支持批量插入,那么就会一条一条插入。
users := make([]User, 1)
users[0].Name = "name0"
...
affected, err := engine.Insert(&users)
  • 使用指针Slice插入多条记录,同上
users := make([]*User, 1)
users[0] = new(User)
users[0].Name = "name0"
...
affected, err := engine.Insert(&users)
  • 插入多条记录并且不使用批量插入,此时实际生成多条插入语句,每条记录均会自动赋予Id值。
users := make([]*User, 1)
users[0] = new(User)
users[0].Name = "name0"
...
affected, err := engine.Insert(users)
  • 插入不同表的一条记录
user := new(User)
user.Name = "myname"
question := new(Question)
question.Content = "whywhywhwy?"
affected, err := engine.Insert(user, question)
  • 插入不同表的多条记录
users := make([]User, 1)
users[0].Name = "name0"
...
questions := make([]Question, 1)
questions[0].Content = "whywhywhwy?"
affected, err := engine.Insert(&users, &questions)
  • 插入不同表的一条或多条记录
user := new(User)
user.Name = "myname"
...
questions := make([]Question, 1)
questions[0].Content = "whywhywhwy?"
affected, err := engine.Insert(user, &questions)

这里需要注意以下几点:

  • 这里虽然支持同时插入,但这些插入并没有事务关系。因此有可能在中间插入出错后,后面的插入将不会继续。此时前面的插入已经成功,如果需要回滚,请开启事务。
  • 批量插入会自动生成Insert into table values (),(),()的语句,因此各个数据库对SQL语句有长度限制,因此这样的语句有一个最大的记录数,根据经验测算在150条左右。大于150条后,生成的sql语句将太长可能导致执行失败。因此在插入大量数据时,目前需要自行分割成每150条插入一次。

5.2 创建时间Created

Created可以让您在数据插入到数据库时自动将对应的字段设置为当前时间,需要在xorm标记中使用created标记,如下所示进行标记,对应的字段可以为time.Time或者自定义的time.Time或者int,int64等int类型。

type User struct {
    Id int64
    Name string
    CreatedAt time.Time `xorm:"created"`
}

type JsonTime time.Time
func (j JsonTime) MarshalJSON() ([]byte, error) {
    return []byte(`"`+time.Time(j).Format("2006-01-02 15:04:05")+`"`), nil
}

type User struct {
    Id int64
    Name string
    CreatedAt JsonTime `xorm:"created"`
}

type User struct {
    Id int64
    Name string
    CreatedAt int64 `xorm:"created"`
}

在Insert()或InsertOne()方法被调用时,created标记的字段将会被自动更新为当前时间或者当前时间的秒数(对应为time.Unix()),如下所示:

var user User
engine.Insert(&user)
// INSERT user (created...) VALUES (?...)

最后一个值得注意的是时区问题,默认xorm采用Local时区,所以默认调用的time.Now()会先被转换成对应的时区。要改变xorm的时区,可以使用:

engine.TZLocation, _ = time.LoadLocation("Asia/Shanghai")

六 xorm查询和统计数据

所有的查询条件不区分调用顺序,但必须在调用Get,Exist, Sum, Find,Count, Iterate, Rows这几个函数之前调用。同时需要注意的一点是,在调用的参数中,如果采用默认的SnakeMapper所有的字符字段名均为映射后的数据库的字段名,而不是field的名字。

6.1 查询条件方法

查询和统计主要使用Get, Find, Count, Rows, Iterate这几个方法,同时大部分函数在调用Update, Delete时也是可用的。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:

  • Alias(string)

给Table设定一个别名

engine.Alias("o").Where("o.name = ?", name).Get(&order)
  • And(string, …interface{})

和Where函数中的条件基本相同,作为条件

engine.Where(...).And(...).Get(&order)
  • Asc(…string)

指定字段名正序排序,可以组合

engine.Asc("id").Find(&orders)
  • Desc(…string)

指定字段名逆序排序,可以组合

engine.Asc("id").Desc("time").Find(&orders)
  • ID(interface{})

传入一个主键字段的值,作为查询条件,如

var user User
engine.ID(1).Get(&user)
// SELECT * FROM user Where id = 1

如果是复合主键,则可以

engine.ID(schemas.PK{1, "name"}).Get(&user)
// SELECT * FROM user Where id =1 AND name= 'name'

传入的两个参数按照struct中pk标记字段出现的顺序赋值。

  • Or(interface{}, …interface{})

和Where函数中的条件基本相同,作为条件

  • OrderBy(string)

按照指定的顺序进行排序

  • Select(string)

指定select语句的字段部分内容,例如:

engine.Select("a.*, (select name from b limit 1) as name").Find(&beans)

engine.Select("a.*, (select name from b limit 1) as name").Get(&bean)
  • SQL(string, …interface{})

执行指定的Sql语句,并把结果映射到结构体。有时,当选择内容或者条件比较复杂时,可以直接使用Sql,例如:

engine.SQL("select * from table").Find(&beans)
  • Where(string, …interface{})

和SQL中Where语句中的条件基本相同,作为条件

engine.Where("a = ? AND b = ?", 1, 2).Find(&beans)

engine.Where(builder.Eq{"a":1, "b": 2}).Find(&beans)

engine.Where(builder.Eq{"a":1}.Or(builder.Eq{"b": 2})).Find(&beans)
  • In(string, …interface{})

某字段在一些值中,这里需要注意必须是[]interface{}才可以展开,由于Go语言的限制,[]int64等不可以直接展开,而是通过传递一个slice。第二个参数也可以是一个*builder.Builder 指针。示例代码如下:

// select from table where column in (1,2,3)
engine.In("cloumn", 1, 2, 3).Find()

// select from table where column in (1,2,3)
engine.In("column", []int{1, 2, 3}).Find()

// select from table where column in (select column from table2 where a = 1)
engine.In("column", builder.Select("column").From("table2").Where(builder.Eq{"a":1})).Find()
  • Cols(…string)

只查询或更新某些指定的字段,默认是查询所有映射的字段或者根据Update的第一个参数来判断更新的字段。例如:

engine.Cols("age", "name").Get(&usr)
// SELECT age, name FROM user limit 1
engine.Cols("age", "name").Find(&users)
// SELECT age, name FROM user
engine.Cols("age", "name").Update(&user)
// UPDATE user SET age=? AND name=?
  • AllCols()

查询或更新所有字段,一般与Update配合使用,因为默认Update只更新非0,非"",非bool的字段。

engine.AllCols().ID(1).Update(&user)
// UPDATE user SET name = ?, age =?, gender =? WHERE id = 1
  • MustCols(…string)

某些字段必须更新,一般与Update配合使用。

  • Omit(…string)

和cols相反,此函数指定排除某些指定的字段。注意:此方法和Cols方法不可同时使用。

// 例1:
engine.Omit("age", "gender").Update(&user)
// UPDATE user SET name = ? AND department = ?
// 例2:
engine.Omit("age, gender").Insert(&user)
// INSERT INTO user (name) values (?) // 这样的话age和gender会给默认值
// 例3:
engine.Omit("age", "gender").Find(&users)
// SELECT name FROM user //只select除age和gender字段的其它字段
  • Distinct(…string)

按照参数中指定的字段归类结果。

engine.Distinct("age", "department").Find(&users)
// SELECT DISTINCT age, department FROM user

注意:当开启了缓存时,此方法的调用将在当前查询中禁用缓存。因为缓存系统当前依赖Id,而此时无法获得Id

  • Table(nameOrStructPtr interface{})

传入表名称或者结构体指针,如果传入的是结构体指针,则按照IMapper的规则提取出表名

  • Limit(int, …int)

限制获取的数目,第一个参数为条数,第二个参数表示开始位置,如果不传则为0

  • Top(int)

相当于Limit(int, 0)

  • Join(string,interface{},string)

第一个参数为连接类型,当前支持INNER, LEFT OUTER, CROSS中的一个值, 第二个参数为string类型的表名,表对应的结构体指针或者为两个值的[]string,表示表名和别名, 第三个参数为连接条件

详细用法参见 [5.Join的使用](5.join.md)
  • GroupBy(string)

Groupby的参数字符串

  • Having(string)

Having的参数字符串

6.2 Get方法

查询单条数据使用Get方法,在调用Get方法时需要传入一个对应结构体的指针,同时结构体中的非空field自动成为查询的条件和前面的方法条件组合在一起查询。

如:

  1. 根据Id来获得单条数据:
user := new(User)
has, err := engine.ID(id).Get(user)
// 复合主键的获取方法
// has, errr := engine.ID(xorm.PK{1,2}).Get(user)
  1. 根据Where来获得单条数据:
user := new(User)
has, err := engine.Where("name=?", "xlw").Get(user)
  1. 根据user结构体中已有的非空数据来获得单条数据:
user := &User{Id:1}
has, err := engine.Get(user)

或者其它条件

user := &User{Name:"xlw"}
has, err := engine.Get(user)

返回的结果为两个参数,一个has为该条记录是否存在,第二个参数err为是否有错误。不管err是否为nil,has都有可能为true或者false。

6.3 Exist系列方法

判断某个记录是否存在可以使用Exist, 相比GetExist性能更好。

has, err := testEngine.Exist(new(RecordExist))
// SELECT * FROM record_exist LIMIT 1

has, err = testEngine.Exist(&RecordExist{
		Name: "test1",
	})
// SELECT * FROM record_exist WHERE name = ? LIMIT 1

has, err = testEngine.Where("name = ?", "test1").Exist(&RecordExist{})
// SELECT * FROM record_exist WHERE name = ? LIMIT 1

has, err = testEngine.SQL("select * from record_exist where name = ?", "test1").Exist()
// select * from record_exist where name = ?

has, err = testEngine.Table("record_exist").Exist()
// SELECT * FROM record_exist LIMIT 1

has, err = testEngine.Table("record_exist").Where("name = ?", "test1").Exist()
// SELECT * FROM record_exist WHERE name = ? LIMIT 1

与Get的区别

Get与Exist方法返回值都为bool和error,如果查询到实体存在,则Get方法会将查到的实体赋值给参数

user := &User{Id:1}
has,err := testEngine.Get(user)	// 执行结束后,user会被赋值为数据库中Id为1的实体
has,err = testEngine.Exist(user)	// user中仍然是初始声明的user,不做改变

建议

如果你的需求是:判断某条记录是否存在,若存在,则返回这条记录。

建议直接使用Get方法。

如果仅仅判断某条记录是否存在,则使用Exist方法,Exist的执行效率要比Get更高。

6.4 Find方法

查询多条数据使用Find方法,Find方法的第一个参数为slice的指针或Map指针,即为查询后返回的结果,第二个参数可选,为查询的条件struct的指针。

  1. 传入Slice用于返回数据
everyone := make([]Userinfo, 0)
err := engine.Find(&everyone)

pEveryOne := make([]*Userinfo, 0)
err := engine.Find(&pEveryOne)
  1. 传入Map用户返回数据,map必须为map[int64]Userinfo的形式,map的key为id,因此对于复合主键无法使用这种方式。
users := make(map[int64]Userinfo)
err := engine.Find(&users)

pUsers := make(map[int64]*Userinfo)
err := engine.Find(&pUsers)
  1. 也可以加入各种条件
users := make([]Userinfo, 0)
err := engine.Where("age > ? or name = ?", 30, "xlw").Limit(20, 10).Find(&users)
  1. 如果只选择单个字段,也可使用非结构体的Slice
var ints []int64
err := engine.Table("user").Cols("id").Find(&ints)

6.5 Iterate方法

Iterate方法提供逐条执行查询到的记录的方法,他所能使用的条件和Find方法完全相同

err := engine.Where("age > ? or name=?)", 30, "xlw").Iterate(new(Userinfo), func(i int, bean interface{})error{
    user := bean.(*Userinfo)
    //do somthing use i and user
})

6.6 Rows方法

Rows方法和Iterate方法类似,提供逐条执行查询到的记录的方法,不过Rows更加灵活好用。

user := new(User)
rows, err := engine.Where("id >?", 1).Rows(user)
if err != nil {
}
defer rows.Close()
for rows.Next() {
    err = rows.Scan(user)
    //...
}

6.7 Count方法

统计数据使用Count方法,Count方法的参数为struct的指针并且成为查询条件。

user := new(User)
total, err := engine.Where("id >?", 1).Count(user)

6.8 Sum系列方法

求和数据可以使用Sum, SumInt, SumsSumsInt 四个方法,Sums系列方法的参数为struct的指针并且成为查询条件。

  • Sum 求某个字段的和,返回float64
type SumStruct struct {
    Id int64
    Money int
    Rate float32
}

ss := new(SumStruct)
total, err := engine.Where("id >?", 1).Sum(ss, "money")
fmt.Printf("money is %d", int(total))
  • SumInt 求某个字段的和,返回int64
type SumStruct struct {
    Id int64
    Money int
    Rate float32
}

ss := new(SumStruct)
total, err := engine.Where("id >?", 1).SumInt(ss, "money")
fmt.Printf("money is %d", total)
  • Sums 求某几个字段的和, 返回float64的Slice
s := new(SumStruct)
totals, err := engine.Where("id >?", 1).Sums(ss, "money", "rate")

fmt.Printf("money is %d, rate is %.2f", int(total[0]), total[1])
  • SumsInt 求某几个字段的和, 返回int64的Slice
ss := new(SumStruct)
totals, err := engine.Where("id >?", 1).SumsInt(ss, "money")

fmt.Printf("money is %d", total[0])

七 xorm删除数据

7.1 删除数据

删除数据用 Delete方法,参数为struct的指针并且成为查询条件。

user := new(User)
affected, err := engine.ID(id).Delete(user)

Delete的返回值第一个参数为删除的记录数,第二个参数为错误。

注意1:当删除时,如果user中包含有bool,float64或者float32类型,有可能会使删除失败。具体请查看 FAQ

注意2:必须至少包含一个条件才能够进行删除,这意味着直接用

engine.Delete(new(User))

将会报一个保护性的错误,如果你真的希望将整个表删除,你可以

engine.Where("1=1").Delete(new(User))

7.2 软删除Deleted

Deleted可以让您不真正的删除数据,而是标记一个删除时间。使用此特性需要在xorm标记中使用deleted标记,如下所示进行标记,对应的字段可以为time.Time, type MyTime time.Time,int 或者 int64类型。

type User struct {
    Id int64
    Name string
    DeletedAt time.Time `xorm:"deleted"`
}

在Delete()时,deleted标记的字段将会被自动更新为当前时间而不是去删除该条记录,如下所示:

var user User
engine.ID(1).Get(&user)
// SELECT * FROM user WHERE id = ?
engine.ID(1).Delete(&user)
// UPDATE user SET ..., deleted_at = ? WHERE id = ?
engine.ID(1).Get(&user)
// 再次调用Get,此时将返回false, nil,即记录不存在
engine.ID(1).Delete(&user)
// 再次调用删除会返回0, nil,即记录不存在

那么如果记录已经被标记为删除后,要真正的获得该条记录或者真正的删除该条记录,需要启用Unscoped,如下所示:

var user User
engine.ID(1).Unscoped().Get(&user)
// 此时将可以获得记录
engine.ID(1).Unscoped().Delete(&user)
// 此时将可以真正的删除记录

八 xorm更新数据

更新数据使用Update方法,Update方法的第一个参数为需要更新的内容,可以为一个结构体指针或者一个Map[string]interface{}类型。当传入的为结构体指针时,只有非空和0的field才会被作为更新的字段。当传入的为Map类型时,key为数据库Column的名字,value为要更新的内容。

Update方法将返回两个参数,第一个为 更新的记录数。

需要注意的是 SQLITE 数据库返回的是根据更新条件查询的记录数而不是真正受更新的记录数。

user := new(User)
user.Name = "myname"
affected, err := engine.ID(id).Update(user)

这里需要注意,Update会自动从user结构体中提取非0和非nil得值作为需要更新的内容,因此,如果需要更新一个值为0,则此种方法将无法实现,因此有两种选择:

  • 1.通过添加Cols函数指定需要更新结构体中的哪些值,未指定的将不更新,指定了的即使为0也会更新。
affected, err := engine.ID(id).Cols("age").Update(&user)
  • 2.通过传入map[string]interface{}来进行更新,但这时需要额外指定更新到哪个表,因为通过map是无法自动检测更新哪个表的。
affected, err := engine.Table(new(User)).ID(id).Update(map[string]interface{}{"age":0})

有时候希望能够指定必须更新某些字段,而其它字段根据值的情况自动判断,可以使用 MustCols 来组合 Update 使用。

affected, err := engine.ID(id).MustCols("age").Update(&user)

另外,如果需要更新所有的字段,可以使用 AllCols()

affected, err := engine.ID(id).AllCols().Update(&user)

九 xorm事务处理

当使用事务处理时,需要创建 Session 对象。在进行事务处理时,可以混用 ORM 方法和 RAW 方法,如下代码所示:

func MyTransactionOps() error {
    session := engine.NewSession()
    defer session.Close()

    // add Begin() before any action
    if err := session.Begin(); err != nil {
        return err
    }

    user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: time.Now()}
    if _, err := session.Insert(&user1); err != nil {
        return err
    }
    user2 := Userinfo{Username: "yyy"}
    if _, err = session.Where("id = ?", 2).Update(&user2); err != nil {
        return err
    }

    if _, err = session.Exec("delete from userinfo where username = ?", user2.Username); err != nil {
        return err
    }

    // add Commit() after all actions
    return session.Commit()
}

注意如果您使用的是 mysql,数据库引擎为 innodb 事务才有效,myisam 引擎是不支持事务的。

你可能感兴趣的:(Go语言进阶学习笔记,学习,数据库,mysql,golang)