目 录 xorm 创建 Orm 引擎定义表结构体名称映射规则前缀映射, 后缀映射和缓存映射使用 Table 和 Tag 改变名称映射 Column 属性定义表结构操作获取数据库信息表操作创建索引和唯一索引同步数据库结构导入导出 SQL 脚本 SqlMap 及 SqlTemplate 模板初始化 Sq

Save this PDF as:
 WORD  PNG  TXT  JPG

Size: px
Start display at page:

Download "目 录 xorm 创建 Orm 引擎定义表结构体名称映射规则前缀映射, 后缀映射和缓存映射使用 Table 和 Tag 改变名称映射 Column 属性定义表结构操作获取数据库信息表操作创建索引和唯一索引同步数据库结构导入导出 SQL 脚本 SqlMap 及 SqlTemplate 模板初始化 Sq"

Transcription

1

2 目 录 xorm 创建 Orm 引擎定义表结构体名称映射规则前缀映射, 后缀映射和缓存映射使用 Table 和 Tag 改变名称映射 Column 属性定义表结构操作获取数据库信息表操作创建索引和唯一索引同步数据库结构导入导出 SQL 脚本 SqlMap 及 SqlTemplate 模板初始化 SqlMap 配置文件及 SqlTemplate 模板 SqlMap 及 SqlTemplate 相关功能 API SqlMap 配置文件及 SqlTemplate 模板加密存储及解析手动管理 SqlMap 配置及 SqlTemplate 模板插入数据 ORM 方式插入数据执行 SQL 命令插入数据创建时间 Created 查询和统计数据 ORM 方式查询和统计数据查询条件方法临时开关方法 Get 方法 Find 方法 Join 的使用 Iterate 方法 Count 方法 Rows 方法 Sum 系列方法 Exist 方法子查询执行 SQL 查询执行 SQL 查询的 11 种常用方式查询返回 json 或 xml 字符串链式查询据操返回某条记录的某个字段的值 - 2 -

3 SqlTemplateClient 执行过程关于数据库分页查询更新数据 ORM 方式更新数据 Update 方法乐观锁 Version 更新时间 Updated 执行 SQL 命令更新数据删除数据 ORM 方式删除数据 Delete 方法软删除 Deleted 执行 SQL 命令删除数据事务处理简单事务模型嵌套事务模型八种事务类型及事务传播机制简单事务相关 API 嵌套事务相关 API 嵌套事务示例代码主从数据库 (Master/Slave) 读写分离创建引擎组负载策略引擎组其他配置方法数据库读写分离批量混合 SQL 操作 SQL Builder 缓存事件数据导出 Dump 数据库结构和数据查询结果集导出 csv tsv xml json xlsx yaml html 多 Sheet 页数据导出日志连接池 xorm 工具常见问题感谢支持 - 3 -

4 xorm xorm xorm xorm 是一个简单而强大的 Go 语言 ORM 库. 通过它可以使数据库操作非常简便 说明 特性 本库是基于原版 xorm: 的定制增强版本, 由于本定制版有第三方库依赖 ( 原版 xorm 无任何第三方库依赖 ), 原版 xorm 要保持对第三方库零依赖特性, 所以只好单独开了本 Github 库 本库的相关定制功能是为了解决更简单的进行复杂 SQL 调用和一些特殊业务需求场景而开发的 本定制版 ORM 相关核心功能和原版保持一致, 会跟随原版 xorm 更新 定制功能采用针对原版弱侵入性代码实现 支持 Struct 和数据库表之间的灵活映射, 并支持自动同步事务支持, 支持嵌套事务 ( 支持类 JAVA Spring 的事务传播机制 ) 同时支持原始 SQL 语句和 ORM 操作的混合执行使用连写来简化调用支持使用 Id, In, Where, Limit, Join, Having, Table, Sql, Cols 等函数和结构体等方式作为条件支持级联加载 Struct 支持类 ibatis 方式配置 SQL 语句 ( 支持 xml 配置文件 json 配置文件 xsql 配置文件, 支持 pongo2 jet html/template 模板和自定义实现配置多种方式 ) 支持动态 SQL 功能支持一次批量混合执行多个 CRUD 操作, 并返回多个结果集支持数据库查询结果直接返回 Json 字符串和 xml 字符串支持 SqlMap 配置文件和 SqlTemplate 模板密文存储和解析支持缓存支持主从数据库 (Master/Slave) 数据库读写分离支持根据数据库自动生成 xorm 的结构体支持记录版本 ( 即乐观锁 ) 支持查询结果集导出 csv tsv xml json xlsx yaml html 功能支持 SQL Builder github.com/go-xorm/builder 驱动支持 - 4 -

5 xorm 目前支持的 Go 数据库驱动和对应的数据库如下 : 安装 Mysql: github.com/go-sql-driver/mysql MyMysql: github.com/ziutek/mymysql Postgres: github.com/lib/pq Tidb: github.com/pingcap/tidb SQLite: github.com/mattn/go-sqlite3 MsSql: github.com/denisenkom/go-mssqldb MsSql: github.com/lunny/godbc Oracle: github.com/mattn/go-oci8 ( 试验性支持 ) go get -u github.com/xormplus/xorm 如果这项目对您有很大帮助, 您愿意支持这个项目的进一步开发和这个项目的持续维护 你可以扫描下面的二维 码, 让我喝一杯咖啡或豆奶 非常感谢您的捐赠, 感谢您对开源项目的支持, 谢谢 ( 支付宝 ) 讨论 请加入 QQ 群 : 进行讨论 API 设计相关建议可联系本人 QQ:

6 创建 Orm 引擎 创建 Orm 引擎创建 Orm 引擎 在 xorm 里面, 可以同时存在多个 Orm 引擎, 一个 Orm 引擎称为 Engine, 一个 Engine 一般只对应一个数据库 Engine 通过调用 xorm.newengine 生成, 如 : import ( _ "github.com/go-sql-driver/mysql" "github.com/xormplus/xorm" ) var engine *xorm.engine func main() { var err error engine, err = xorm.newengine("mysql", or import ( _ "github.com/mattn/go-sqlite3" "github.com/xormplus/xorm" ) var engine *xorm.engine func main() { var err error engine, err = xorm.newengine("sqlite3", "./test.db") 您也可以针对特定数据库及数据库驱动使用类似下面的快捷方式创建引擎 engine, err = xorm.newpostgresql(datasourcename) engine, err = xorm.newsqlite3(datasourcename) 一般情况下如果只操作一个数据库, 只需要创建一个 engine 即可 engine 是 GoRutine 安全的 创建完成 engine 之后, 并没有立即连接数据库, 此时可以通过 engine.ping() 来进行数据库的连接测试是否可以 - 6 -

7 创建 Orm 引擎 连接到数据库 另外对于某些数据库有连接超时设置的, 可以通过起一个定期 Ping 的 Go 程来保持连接鲜活 对于有大量数据并且需要分区的应用, 也可以根据规则来创建多个 Engine, 比如 : var err error for i:=0;i<5;i++ { engines[i], err = xorm.newengine("sqlite3", fmt.sprintf("./test%d.db", i)) engine 可以通过 engine.close 来手动关闭, 但是一般情况下可以不用关闭, 在程序退出时会自动关闭 NewEngine 传入的参数和 sql.open 传入的参数完全相同, 因此, 在使用某个驱动前, 请查看此驱动中关于传入 参数的说明文档 以下为各个驱动的连接符对应的文档链接 : sqlite3 mysql dsn mymysql postgres 在 engine 创建完成后可以进行一些设置, 如 : 日志 日志是一个接口, 通过设置日志, 可以显示 SQL, 警告以及错误等, 默认的显示级别为 INFO engine.showsql(true), 则会在控制台打印出生成的 SQL 语句 ; engine.logger().setlevel(core.log_debug), 则会在控制台打印调试及以上的信息 ; 如果希望将信息不仅打印到控制台, 而是保存为文件, 那么可以通过类似如下的代码实现, NewSimpleLogger(w io.writer) 接收一个 io.writer 接口来将数据写入到对应的设施中 f, err := os.create("sql.log") if err!= nil { println(err.error()) return engine.setlogger(xorm.newsimplelogger(f)) 当然, 如果希望将日志记录到 syslog 中, 也可以如下 : logwriter, err := syslog.new(syslog.log_debug, "rest-xorm-example") if err!= nil { log.fatalf("fail to create xorm system logger: %v\n", err) - 7 -

8 创建 Orm 引擎 logger := xorm.newsimplelogger(logwriter) logger.showsql(true) engine.setlogger(logger) 连接池 engine 内部支持连接池接口和对应的函数 如果需要设置连接池的空闲数大小, 可以使用 engine.setmaxidleconns() 来实现 如果需要设置最大打开连接数, 则可以使用 engine.setmaxopenconns() 来实现 - 8 -

9 定义表结构体 定义表结构体 定义表结构体 xorm 支持将一个 struct 映射为数据库中对应的一张表 - 9 -

10 名称映射规则 名称映射规则名称映射规则 名称映射规则主要负责结构体名称到表名和结构体 field 到表字段的名称映射 由 core.imapper 接口的实现者来管理,xorm 内置了三种 IMapper 实现 :core.snakemapper, core.samemapper 和 core.gonicmapper * SnakeMapper 支持 struct 为驼峰式命名, 表结构为下划线命名之间的转换, 这个是默认的 Maper; * SameMapper 支持结构体名称和对应的表名称以及结构体 field 名称与对应的表字段名称相同的命名 ; * GonicMapper 和 SnakeMapper 很类似, 但是对于特定词支持更好, 比如 ID 会翻译成 id 而不是 i_d 当前 SnakeMapper 为默认值, 如果需要改变时, 在 engine 创建完成后使用 engine.setmapper(core.samemapper{) 同时需要注意的是 : 如果你使用了别的命名规则映射方案, 也可以自己实现一个 IMapper 表名称和字段名称的映射规则默认是相同的, 当然也可以设置为不同, 如 : engine.settablemapper(core.samemapper{) engine.setcolumnmapper(core.snakemapper{) When a struct auto mapping to a database s table, the below table describes how they change to each other: go type's kind value method xorm type implemented Conversion int, int8, int16, int32, uint, uint8, uint16, uint32 int64, uint64 float32 float64 complex64, complex128 []uint8 Conversion.ToDB / Conversion.FromDB json.marshal / json.unmarshal array, slice, map except json.marshal / Text Int BigInt Float Double Varchar(64) Blob Text

11 名称映射规则 []uint8 json.unmarshal Text string time.time Varchar(255) DateTime cascade struct primary key field value BigInt struct Others json.marshal / json.unmarshal Text Text bool 1 or 0 Bool

12 前缀映射, 后缀映射和缓存映射 前缀映射, 后缀映射和缓存映射前缀映射, 后缀映射和缓存映射 通过 core.newprefixmapper(core.snakemapper{, "prefix") 可以创建一个在 SnakeMapper 的基础上在命名中添加统一的前缀, 当然也可以把 SnakeMapper{ 换成 SameMapper 或者你自定义的 Mapper 例如, 如果希望所有的表名都在结构体自动命名的基础上加一个前缀而字段名不加前缀, 则可以在 engine 创建完 成后执行以下语句 : tbmapper := core.newprefixmapper(core.snakemapper{, "prefix_") engine.settablemapper(tbmapper) 执行之后, 结构体 type User struct 默认对应的表名就变成了 prefix_user 了, 而之前默认的是 user 通过 core.newsufffixmapper(core.snakemapper{, "suffix") 可以创建一个在 SnakeMapper 的基础上在命名中添加统一的后缀, 当然也可以把 SnakeMapper 换成 SameMapper 或者你自定义的 Mapper 通过 core.newcachemapper(core.snakemapper{) 可以创建一个组合了其它的映射规则, 起到在内存中缓存曾经映射过的命名映射

13 使用 Table 和 Tag 改变名称映射 使用 Table 和 Tag 改变名称映射使用 Table 和 Tag 改变名称映射 如果所有的命名都是按照 IMapper 的映射来操作的, 那当然是最理想的 但是如果碰到某个表名或者某个字段名跟映射规则不匹配时, 我们就需要别的机制来改变 xorm 提供了如下几种方式来进行 : 如果结构体拥有 TableName() string 的成员方法, 那么此方法的返回值即是该结构体对应的数据库表名 通过 engine.table() 方法可以改变 struct 对应的数据库表的名称, 通过 sturct 中 field 对应的 Tag 中使用 xorm:"'column_name'" 可以使该 field 对应的 Column 名称为指定名称 这里使用两个单引号将 Column 名称括起来是为了防止名称冲突, 因为我们在 Tag 中还可以对这个 Column 进行更多的定义 如果名称不冲突的情况, 单引号也可以不使用 到此名称映射的所有方法都给出了, 一共三种方式, 这三种是有优先级顺序的 表名的优先级顺序如下 : engine.table() 指定的临时表名优先级最高 TableName() string 其次 Mapper 自动映射的表名优先级最后字段名的优先级顺序如下 : 结构体 tag 指定的字段名优先级较高 Mapper 自动映射的表名优先级较低

14 Column 属性定义 Column 属性定义 Column 属性定义 我们在 field 对应的 Tag 中对 Column 的一些属性进行定义, 定义的方法基本和我们写 SQL 定义表结构类似, 比如 : type User struct { Id int64 Name string `xorm:"varchar(25) notnull unique 'usr_name'"` 对于不同的数据库系统, 数据类型其实是有些差异的 因此 xorm 中对数据类型有自己的定义, 基本的原则是尽量 兼容各种数据库的字段类型 对于使用者, 一般只要使用自己熟悉的数据库字段定义即可 具体的 Tag 规则如下, 另 Tag 中的关键字均不区分大小写, 但字段名根据不同的数据库是区分大小写 : Tag name pk 当前支持 30 多种字段类型, 详情参见本文最后一个表格 autoincr [not ]null 或 notnull unique 或 unique(uniquename) 说明 当前 field 对应的字段的名称, 可选, 如不写, 则自动根据 field 名字和转换规则命名, 如与其它关键字冲突, 请使用单引号括起来 是否是 Primary Key, 如果在一个 struct 中有多个字段都使用了此标记, 则这多个字段构成了复合主键, 单主键当前支持 int32,int,int64,uint32,uint,uint64,stri ng 这 7 种 Go 的数据类型, 复合主键支持这 7 种 Go 的数据类型的组合 字段类型 是否是自增 是否可以为空 是否是唯一, 如不加括号则该字段不允许重复 ; 如加上括号, 则括号中为联合唯一索引的名字, 此时如果有另外一个或多个字段和本 unique 的 uniquename 相同, 则这些 uniquename 相同的字段组成联合唯一索引 是否是索引, 如不加括号则该字段自身为索引, 如加上括号, 则括号中为联合

15 Column 属性定义 index 或 index(indexname) extends 索引的名字, 此时如果有另外一个或多个字段和本 index 的 indexname 相同, 则这些 indexname 相同的字段组成联合索引 应用于一个匿名成员结构体或者非匿名成员结构体之上, 表示此结构体的所有成员也映射到数据库中,extends 可加载无限级 - 这个 Field 将不进行字段映射 -> <- created updated deleted version default 0 或 default(0) json 这个 Field 将只写入到数据库而不从数据库读取 这个 Field 将只从数据库读取, 而不写入到数据库 这个 Field 将在 Insert 时自动赋值为当前时间 这个 Field 将在 Insert 或 Update 时自动赋值为当前时间 这个 Field 将在 Delete 时设置为当前时间, 并且当前记录不删除 这个 Field 将会在 insert 时默认为 1, 每次更新自动加 1 设置默认值, 紧跟的内容如果是 Varchar 等需要加上单引号 表示内容将先转成 Json 格式, 然后存储到数据库中, 数据库中的字段类型可以为 Text 或者二进制 另外有如下几条自动映射的规则 : 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 接口的类型或者结构体, 将根据接口的转换方式在类型和数据库记录之间进行相互转

16 Column 属性定义 换, 这个接口的优先级是最高的 type Conversion interface { FromDB([]byte) error ToDB() ([]byte, error) 5. 如果一个结构体包含一个 Conversion 的接口类型, 那么在获取数据时, 必须要预先设置一个实现此接口的 struct 或者 struct 的指针 此时可以在此 struct 中实现 BeforeSet(name string, cell xorm.cell) 方法来进行预先给 Conversion 赋值 下表为 xorm 类型和各个数据库类型的对应表 : xorm mysql sqlite3 postgres remark BIT BIT INTEGER BIT TINYINT TINYINT INTEGER SMALLINT 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 MEDIUMTEX T MEDIUMTEX T 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 TIMESTAMP Z TEXT TEXT TIMESTAMP with zone timestamp with zone info

17 Column 属性定义 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 MEDIUMBL OB MEDIUMBL OB BLOB BYTEA LONGBLOB LONGBLOB BLOB BYTEA BYTEA BLOB BLOB BYTEA BOOL TINYINT INTEGER BOOLEAN SERIAL INT INTEGER SERIAL BIGSERIAL BIGINT INTEGER BIGSERIAL auto increment auto increment

18 表结构操作 表结构操作表结构操作 xorm 提供了一些动态获取和修改表结构的方法, 通过这些方法可以动态同步数据库结构, 导出数据库结构, 导入数据库结构 如果您只是需要一个工具, 可以直接使用 go get github.com/go-xorm/cmd 来安装 xorm 命令行工具

19 获取数据库信息 获取数据库信息获取数据库信息 DBMetas() xorm 支持获取表结构信息, 通过调用 engine.dbmetas() 可以获取到数据库中所有的表, 字段, 索引的信息 TableInfo() 根据传入的结构体指针及其对应的 Tag, 提取出模型对应的表结构信息 这里不是数据库当前的表结构信息, 而是我们通过 struct 建模时希望数据库的表的结构信息

20 表操作 表操作 表操作 CreateTables() 创建表使用 engine.createtables(), 参数为一个或多个空的对应 Struct 的指针 同时可用的方法有 Charset() 和 StoreEngine(), 如果对应的数据库支持, 这两个方法可以在创建表时指定表的字符编码和使用的引擎 Charset() 和 StoreEngine() 当前仅支持 Mysql 数据库 IsTableEmpty() 判断表是否为空, 参数和 CreateTables 相同 IsTableExist() 判断表是否存在 DropTables() 删除表使用 engine.droptables(), 参数为一个或多个空的对应 Struct 的指针或者表的名字 如果为 string 传入, 则只删除对应的表, 如果传入的为 Struct, 则删除表的同时还会删除对应的索引

21 创建索引和唯一索引 创建索引和唯一索引 创建索引和唯一索引 CreateIndexes 根据 struct 中的 tag 来创建索引 CreateUniques 根据 struct 中的 tag 来创建唯一索引

22 同步数据库结构 同步数据库结构同步数据库结构 同步能够部分智能的根据结构体的变动检测表结构的变动, 并自动同步 目前有两个实现 : Sync Sync 将进行如下的同步操作 : 自动检测和创建表, 这个检测是根据表的名字 自动检测和新增表中的字段, 这个检测是根据字段名 自动检测和创建索引和唯一索引, 这个检测是根据索引的一个或多个字段名, 而不根据索引名称 调用方法如下 : err := engine.sync(new(user), new(group)) Sync2 Sync2 对 Sync 进行了改进, 目前推荐使用 Sync2 Sync2 函数将进行如下的同步操作 : 自动检测和创建表, 这个检测是根据表的名字自动检测和新增表中的字段, 这个检测是根据字段名, 同时对表中多余的字段给出警告信息自动检测, 创建和删除索引和唯一索引, 这个检测是根据索引的一个或多个字段名, 而不根据索引名称 因此这里需要注意, 如果在一个有大量数据的表中引入新的索引, 数据库可能需要一定的时间来建立索引 自动转换 varchar 字段类型到 text 字段类型, 自动警告其它字段类型在模型和数据库之间不一致的情况 自动警告字段的默认值, 是否为空信息在模型和数据库之间不匹配的情况 以上这些警告信息需要将 engine.showwarn 设置为 true 才会显示 调用方法和 Sync 一样 : err := engine.sync2(new(user), new(group))

23 导入导出 SQL 脚本 导入导出 SQL 脚本 导入导出 SQL 脚本 Dump 数据库结构和数据 如果需要在程序中 Dump 数据库的结构和数据可以调用 engine.dumpall(w io.writer) 和 engine.dumpallfile(fpath string) DumpAll 方法接收一个 io.writer 接口来保存 Dump 出的数据库结构和数据的 SQL 语句, 这个方法导出的 SQL 语句 并不能通用 只针对当前 engine 所对应的数据库支持的 SQL Import 执行数据库 SQL 脚本 如果你需要将保存在文件或者其它存储设施中的 SQL 脚本执行, 那么可以调用 engine.import(r io.reader) 和 engine.importfile(fpath string) 同样, 这里需要对应的数据库的 SQL 语法支持

24 SqlMap 及 SqlTemplate 模板 SqlMap 及 SqlTemplate 模板 SqlMap 及 SqlTemplate 模板 xorm 提供了 SqlMap 配置和 SqlTemplate 功能支持类 ibatis 方式的 SQL 操作

25 初始化 SqlMap 配置文件及 SqlTemplate 模板 初始化 SqlMap 配置文件及 SqlTemplate 模板初始化 SqlMap 配置文件及 SqlTemplate 模板 xorm 提供了 SqlMap 配置和 SqlTemplate 功能, 如创建引擎, 且需要使用 SqlMap 配置和 SqlTemplate 功能, 参考如下方式创建引擎并初始化 SqlMap 配置文件及 SqlTemplate 模板 var err error engine, err = b?sslmode=disable") if err!= nil { t.fatal(err) /* 使用 RegisterSqlMap() 注册 SqlMap 配置 2 RegisterSqlTemplate() 方法注册 SSqlTemplate 模板配置 3 SqlMap 配置文件总根目录和 SqlTemplate 模板配置文件总根目录可为同一目录 */ // 注册 SqlMap 配置, 可选功能, 如应用中无需使用 SqlMap, 可无需初始化 // 此处使用 xml 格式的配置, 配置文件根目录为 "./sql/oracle", 配置文件后缀为 ".xml" err = x.registersqlmap(xorm.xml("./sql/oracle", ".xml")) if err!= nil { t.fatal(err) // 注册动态 SQL 模板配置, 可选功能, 如应用中无需使用 SqlTemplate, 可无需初始化 // 此处注册动态 SQL 模板配置, 使用 Pongo2 模板引擎, 配置文件根目录为 "./sql/oracle", 配置文件后缀为 ".stpl" err = x.registersqltemplate(xorm.pongo2("./sql/oracle", ".stpl")) if err!= nil { t.fatal(err) // 开启 SqlMap 配置文件和 SqlTemplate 配置文件更新监控功能, 将配置文件更新内容实时更新到内存, 如无需要可以不调用该方法 err = engine.startfswatcher() if err!= nil { t.fatal(err) db.registersqlmap() 过程

26 初始化 SqlMap 配置文件及 SqlTemplate 模板 使用 RegisterSqlMap() 方法指定 SqlMap 配置文件文件格式, 配置文件总根目录, 配置文件后缀名, RegisterSqlMap() 方法按指定目录遍历所配置的目录及其子目录下的所有 xml 配置文件 ( 配置文件样例 ) 或 json 配置文件 ( 配置文件样例 ) 解析所有配置 SqlMap 的 xml 配置文件或 json 配置文件 xml 配置文件中 sql 标签的 id 属性值作为 SqlMap 的 key, 如有重名 id, 则后加载的覆盖之前加载的配置 sql 条目 json 配置文件中 key 值作为 SqlMap 的 key, 如有重名 key, 则后加载的覆盖之前加载的配置 sql 条目 json 配置文件中 key 和 xml 配置文件中 sql 标签的 id 属性值有相互重名的, 则后加载的覆盖之前加载的配置 sql 条目配置文件中 sql 配置会读入内存并缓存由于 SqlTemplate 模板能完成更多复杂组装和特殊场景需求等强大功能, 故 SqlMap 的 xml 或 json 只提供这种极简配置方式, 非 ibatis 的 OGNL 的表达式实现方式 db.registersqltemplate() 过程使用 RegisterSqlTemplate() 方法指定 SqlTemplate 模板的模板引擎 ( 支持 pongo2 jet html/template3 种模板引擎, 但只能选用一种, 不能同时混用 ), 配置文件总根目录, 配置文件后缀名,RegisterSqlTemplate() 方法按指定目录遍历所配置的目录及其子目录及其子目录下的所有 stpl 模板文件 ( 模板文件样例 ) 使用指定模板引擎解析 stpl 模板文件 stpl 模板文件名作为 SqlTemplate 存储的 key( 不包含目录路径 ), 如有不同路径下出现同名文件, 则后加载的覆盖之前加载的配置模板内容 stpl 模板内容会读入内存并缓存

27 SqlMap 及 SqlTemplate 相关功能 API SqlMap 及 SqlTemplate 相关功能 API SqlMap 及 SqlTemplate 相关功能 API RegisterSqlMap(sqlm SqlM, Cipher...Cipher) error 注册 SqlMap 配置 // 注册 SqlMap 配置,xml 格式 err := engine.registersqlmap(xorm.xml("./sql/oracle", ".xml")) // 注册 SqlMap 配置,json 格式 err := engine.registersqlmap(xorm.json("./sql/oracle", ".json")) // 注册 SqlMap 配置,xsql 格式 err := engine.registersqlmap(xorm.xsql("./sql/oracle", ".sql")) xsql 格式配置文件样例 ( 其中 -- id: 之后的字符串为 sqlmap 的 key, 该行下面的 sql 内容为该 key 的配置内容 ) -- id: create-users-table CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255), VARCHAR(255) ); -- id: create-user INSERT INTO users (name, ) VALUES(?,?) -- id: find-users-by- SELECT id,name, FROM users WHERE =? -- id: find-one-user-by- SELECT id,name, FROM users WHERE =? LIMIT 1 -- id: drop-users-table DROP TABLE users RegisterSqlTemplate(sqlt SqlTemplate, Cipher...Cipher) 注册 SqlTemplate 配置 // 注册 SqlTemplate 配置, 使用 Pongo2 模板引擎 err := engine.registersqltemplate(xorm.pongo2("./sql/oracle", ".stpl")) // 注册 SqlTemplate 配置, 使用 Jet 模板引擎 err := engine.registersqltemplate(xorm.jet("./sql/oracle", ".jet"))

28 SqlMap 及 SqlTemplate 相关功能 API // 注册 SqlTemplate 配置, 使用 html/template 模板引擎 err := engine.registersqltemplate(xorm.default("./sql/oracle", ".tpl")) StartFSWatcher() 开启 SqlMap 配置文件和 SqlTemplate 配置文件更新监控功能, 将配置文件更新内容实时更新到内存, 如无需要可 以不调用该方法 // 该监控模式下, 如删除配置文件, 内存中不会删除相关配置 engine.startfswatcher() StopFSWatcher 停止 SqlMap 配置文件和 SqlTemplate 配置文件更新监控功能 engine.stopfswatcher()

29 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析 出于系统信息安全的原因, 一些大公司有自己的信息安全规范 其中就有类似这样的配置文件不允许明文存储的 需求, 本 orm 定制版本也内置了一些 API 对 SqlMap 配置文件及 SqlTemplate 模板密文存储解析的需求进行支持 本库内置提供了 AES,DES,3DES,RSA 四种加密算法支持 SqlMap 配置文件及 SqlTemplate 模板加解密存储 解析功能 其中,AES,DES,3DES 和标准实现略有不同, 如不提供 key, 本库会提供了一个内置 key, 当然 您也可以设置自己的 key RSA 支持公钥加密私钥解密和私钥加密公钥解密两种模式 // 内置 4 种加密算法如下 type AesEncrypt struct { PubKey string type DesEncrypt struct { PubKey string type TripleDesEncrypt struct { PubKey string //RSA 加密解密算法支持公钥加密私钥解密和私钥加密公钥解密两种模式, 请合理设置 DecryptMode type RsaEncrypt struct { PubKey string PriKey string pubkey *rsa.publickey prikey *rsa.privatekey EncryptMode int DecryptMode int const ( RSA_PUBKEY_ENCRYPT_MODE = iota //RSA 公钥加密 RSA_PUBKEY_DECRYPT_MODE //RSA 公钥解密 RSA_PRIKEY_ENCRYPT_MODE //RSA 私钥加密 RSA_PRIKEY_DECRYPT_MODE //RSA 私钥解密 )

30 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析 除以上 4 种内置加解密算法外, 本库也支持自定义加解密算法功能, 您也可以使用自己实现的加密解密算法, 只需要实现 Cipher 接口即可 // 如需使用自定义的加密解密算法, 只需实现 Cipher 接口即可 type Cipher interface { Encrypt(strMsg string) ([]byte, error) Decrypt(src []byte) (decrypted []byte, err error) //SqlMapOptions 中的 Cipher 实现了加密和解密方法 type SqlMapOptions struct { Capacity uint Extension string Cipher Cipher //SqlTemplateOptions 中的 Cipher 实现了加密和解密方法 type SqlTemplateOptions struct { Capacity uint Extension string Cipher Cipher 本库还提供一个批量配置文件加密工具, 采用 sciter 的 Golang 绑定库实现 工具传送门 :xorm tools 内存中缓存的是经指定解密算法解密之后的配置文件内容或模板内容 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析具体示例如下 : // 如需使用自定义的加密解密算法, 只需实现 Cipher 接口即可 type Cipher interface { Encrypt(strMsg string) ([]byte, error) Decrypt(src []byte) (decrypted []byte, err error) //SqlMapOptions 中的 Cipher 实现了加密和解密方法 type SqlMapOptions struct { Capacity uint Extension string Cipher Cipher //SqlTemplateOptions 中的 Cipher 实现了加密和解密方法 type SqlTemplateOptions struct { Capacity uint Extension string Cipher Cipher

31 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析 // 如现在我们已经使用 3DES 加密了 SqlMap 配置文件和 SqlTemplate 模板, 则 xorm 初始化方式如下 var err error engine, err = if err!= nil { t.fatal(err) enc := &xorm.aesencrypt{ PubKey: "122334", // 如自定义加解密算法, 则此处传入的 enc 为自己实现的加解密算法, 后续代码与本示例一致 err = x.registersqlmap(xorm.xml("./sql/aes", ".xml"), enc) if err!= nil { t.fatal(err) // 这里也可以 new 其他加密解密算法,SqlMap 配置文件和 SqlTemplate 模板的加密结算算法可不相同 err = x.registersqltemplate(xorm.pongo2("./sql/aes", ".stpl"), enc) if err!= nil { t.fatal(err) // 内置 4 种加密算法如下 type AesEncrypt struct { PubKey string type DesEncrypt struct { PubKey string type TripleDesEncrypt struct { PubKey string //RSA 加密解密算法支持公钥加密私钥解密和私钥加密公钥解密两种模式, 请合理设置 DecryptMode type RsaEncrypt struct { PubKey string PriKey string pubkey *rsa.publickey prikey *rsa.privatekey EncryptMode int DecryptMode int const ( RSA_PUBKEY_ENCRYPT_MODE = iota //RSA 公钥加密

32 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析 ) RSA_PUBKEY_DECRYPT_MODE //RSA 公钥解密 RSA_PRIKEY_ENCRYPT_MODE //RSA 私钥加密 RSA_PRIKEY_DECRYPT_MODE //RSA 私钥解密 //RSA 使用示例 pukeystr := `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyqyVCWQBeIgY4FyLnrA1 vioq9m++oyuwxivpeh7zn7mjejp7nsk5pbkvv81zibqrmqxqztue52qjyfmfhvoq FyK+Qxw+B/1qY3TACj8b4TlDS0IrII9u1QBRhGHmtmqJ5c6As/rIqYLQCdbmycC0 3iKBM8990Pff8uq+jqzsoQCFDexZClprR6Vbz3S1ejoFLuDUAXUfNrsudQ/7it3s Vn540kh4a9MKeSOg68TSmKKQe1huTF03uDAdPuDveFpVU/l7nETH8mFoW06QvvJR 6Dh6FC9LzJA6EOK4fNGeDzJg9e2jByng/ubJM6WeU29uri2zwnMGQ3qsCuGMXBS/ yqidaqab -----END PUBLIC KEY-----` var err error engine, err = if err!= nil { t.fatal(err) // 公钥解密 enc := new(xorm.rsaencrypt) enc.pubkey = pukeystr enc.decryptmode = xorm.rsa_pubkey_decrypt_mode err = engine.registersqlmap(xorm.xml("./sql/rsa", ".xml"), enc) if err!= nil { t.fatal(err) err = engine.registersqltemplate(xorm.pongo2("./sql/rsa", ".stpl"), enc) if err!= nil { t.fatal(err) 其他相关 API /* ClearSqlMapCipher() 可清除 SqlMap 中设置的 Cipher, 这样可以使用 engine.loadsqlmap(file path) 手工加载一些没有加密的配置文件 2. 如果您之后又想加载一些其他加密算法存储的配置文件, 也可以先清除, 再重新 SetSqlMapCipher() 之后加载 3. 当然, 配置文件也可以不加密存储, 遇到需要部分加密存储的配置文件可以手工调用 SetSqlMapCipher () 之后加载 4. 注意 InitSqlMap() 是对整个 SqlMapRootDir 做统一操作, 如您有区别对待的配置文件, 请自行设置隔离目录, 使用 ClearSqlMapCipher() 或 SetSqlMapCipher() 后调用 LoadSqlMap() 方法进行指定处理

33 SqlMap 配置文件及 SqlTemplate 模板加密存储及解析 */ engine.clearsqlmapcipher() engine.setsqlmapcipher(cipher) /* ClearSqlTemplateCipher() 可清除 SqlTemplate 中设置的 Cipher, 这样可以使用 engine.loads qltemplate(filepath) 手工加载一些没有加密的模板文件 2. 如果您之后又想加载一些其他加密算法存储的模板文件, 也可以先清除, 再重新 SetSqlTemplateCiph er() 之后加载 3. 当然, 配置文件也可以不加密存储, 遇到需要部分加密存储的配置文件可以手工调用 SetSqlTemplateC ipher() 之后加载 4. 注意 InitSqlMap() 是对整个 SqlTemplateRootDir 做统一操作, 如您有区别对待的配置文件, 请自行设置隔离目录, 使用 ClearSqlTemplateCipher() 或 SetSqlTemplateCipher() 后调用 LoadSqlTemp late() 方法进行指定处理 */ engine.clearsqltemplatecipher() engine.setsqltemplatecipher(cipher)

34 手动管理 SqlMap 配置及 SqlTemplate 模板 手动管理 SqlMap 配置及 SqlTemplate 模板手动管理 SqlMap 配置及 SqlTemplate 模板 以下方法是在没有 engine.initsqlmap() 和 engine.initsqltemplate() 初始化相关配置文件的情况下让您在代码中可以轻松的手动管理 SqlMap 配置及 SqlTemplate 模板 engine.initsqlmap() 和 engine.initsqltemplate() 初始化相关配置文件之后也可以使用以下方法灵活的对 SqlMap 配置及 SqlTemplate 模板进行管理方便支持您系统中其他初始化配置源, 可不依赖于本库的初始化配置方式可在代码中依据业务场景, 动态的添加 更新 删除 SqlMap 配置及 SqlTemplate 模板手工管理的 SqlMap 配置及 SqlTemplate 模板, 与 xorm 初始化方法一样会将相关配置缓存, 但不会生成相关配置文件 LoadSqlMap(string) 加载指定文件的 SqlMap 配置 engine.loadsqlmap(filepath) ReloadSqlMap(string) 重新加载指定文件的 SqlMap 配置 engine.reloadsqlmap(filepath) BatchLoadSqlMap([]string) 批量加载指定文件的 SqlMap 配置 engine.batchloadsqlmap([]filepath) BatchReloadSqlMap([]string) 批量重新加载指定文件的 SqlMap 配置 engine.batchreloadsqlmap([]filepath) GetSql(string)

35 手动管理 SqlMap 配置及 SqlTemplate 模板 获取一条 SqlMap 配置 engine.getsql(key) AddSql(string, string) 新增一条 SqlMap 配置 engine.addsql(key, sql) UpdateSql(string, string) 更新一条 SqlMap 配置 engine.updatesql(key, sql) RemoveSql(string) 删除一条 SqlMap 配置 engine.removesql(key) BatchAddSql(map[string]string) 批量新增 SqlMap 配置 engine.batchaddsql(map[key]sql) BatchUpdateSql(map[string]string) 批量更新 SqlMap 配置 engine.batchupdatesql(map[key]sql) BatchRemoveSql([]string) 批量删除 SqlMap 配置 engine.batchremovesql([]key)

36 手动管理 SqlMap 配置及 SqlTemplate 模板 LoadSqlTemplate(string) 加载指定文件的 SqlTemplate 模板 engine.sqltemplate.loadsqltemplate(filepath) ReloadSqlTemplate(string) 重新加载指定文件的 SqlTemplate 模板 engine.sqltemplate.reloadsqltemplate(filepath) ReloadSqlTemplate([]string) 批量加载指定文件的 SqlTemplate 模板 engine.sqltemplate.batchloadsqltemplate([]filepath) BatchReloadSqlTemplate([]string) 批量重新加载指定文件的 SqlTemplate 模板 engine.sqltemplate.batchreloadsqltemplate([]filepath) AddSqlTemplate(string, string) 新增一条 SqlTemplate 模板,sql 为 SqlTemplate 模板内容字符串 engine.sqltemplate.addsqltemplate(key, sql) UpdateSqlTemplate(string, string) 更新一条 SqlTemplate 模板,sql 为 SqlTemplate 模板内容字符串 engine.sqltemplate.updatesqltemplate(key, sql) RemoveSqlTemplate(string) 删除一条 SqlTemplate 模板 engine.sqltemplate.removesqltemplate(key)

37 手动管理 SqlMap 配置及 SqlTemplate 模板 BatchAddSqlTemplate(map[string]string) 批量新增 SqlTemplate 配置,sql 为 SqlTemplate 模板内容字符串 engine.sqltemplate.batchaddsqltemplate(map[key]sql) BatchUpdateSqlTemplate(map[string]string) 批量更新 SqlTemplate 配置,sql 为 SqlTemplate 模板内容字符串 engine.sqltemplate.batchupdatesqltemplate(map[key]sql) BatchRemoveSqlTemplate([]string) 批量删除 SqlTemplate 配置 engine.sqltemplate.batchremovesqltemplate([]key) GetSqlMap(string...) 指定多个 key, 批量查询 SqlMap 配置,...key 的数据类型为...interface{, 返回类型为 map[string]string 如不传任何参数, 调用 engine.getsqlmap(), 则返回整个内存中当前缓存的所有 SqlMap 配置 /* 支持如下多种调用方式 a)engine.getsqlmap("test_getsqlmap_1"), 返回 key 为 Test_GetSqlMap_1 的 SqlMap 配置 b)engine.getsqlmap("test_getsqlmap_1", "Test_GetSqlMap_3"), 返回 key 为 Test_GetSqlM ap_1,test_getsqlmap_3 的 SqlMap 配置 c)engine.getsqlmap("test_getsqlmap_1", "Test_GetSqlMap_3","Test_GetSqlMap_null" ), 返回 key 为 Test_GetSqlMap_1,Test_GetSqlMap_3 的 SqlMap,Test_GetSqlMap_null 配置, 其中 Test_GetSqlMap_null 在内存中缓存的的 key 不存在, 则在返回的 map[string]string 中,key Te st_getsqlmap_null 配置返回的值为空字符串 d)engine.getsqlmap([]string{"test_getsqlmap_1", "Test_GetSqlMap_3") 支持字符串数组形式参数 e)engine.getsqlmap([]string{"test_getsqlmap_1", "Test_GetSqlMap_3","Test_GetSq lmap_2") 支持字符串数组形式和字符串参数混用 f)engine.getsqlmap([]string{"test_getsqlmap_1", "Test_GetSqlMap_3","Test_GetSq lmap_2",3) 支持字符串数组形式, 字符串参数和其他类型参数混用, 但查询时只会处理字符串类型参数和字符转数组类型参数 ( 因为 SqlMap 的 key 是字符串类型 ), 返回的 map[string]string 也无其他类型的 k ey */ engine.getsqlmap(key...)

38 手动管理 SqlMap 配置及 SqlTemplate 模板 GetSqlTemplates(string...) 指定多个 key, 批量查询 SqlTemplate 配置,...key 的数据类型为...interface{, 返回类型为 map[string]*pongo2.template 如不传任何参数, 调用 engine.getsqltemplates(), 则返回整个内存中当前缓存的所有 SqlTemplate 配置 engine.getsqltemplates() 返回类型为 map[string]*pongo2.template, 可以方便的实现链式调用 pongo2 的 Execute(),ExecuteBytes(),ExecuteWriter() 方法 /* 支持如下多种调用方式 a)engine.getsqltemplates("test_getsqltemplates_1"), 返回 key 为 Test_GetSqlTemplates _1 的 SSqlTemplate 配置 b)engine.getsqltemplates("test_getsqltemplates_1", "Test_GetSqlTemplates_3"), 返回 key 为 Test_GetSqlTemplates_1,Test_GetSqlTemplates_3 的 SqlTemplate 配置 c)engine.getsqltemplates("test_getsqltemplates_1", "Test_GetSqlTemplates_3","Te st_getsqltemplates_null"), 返回 key 为 Test_GetSqlTemplates_1,Test_GetSqlTemplates_3 的 SqlMap,Test_GetSqlMap_null 配置, 其中 Test_GetSqlTemplates_null 在内存中缓存的的 key 不存在, 则在返回的 map[string]*pongo2.template 中,key Test_GetSqlTemplates_null 配置返回的值为 nil d)engine.getsqltemplates([]string{"test_getsqltemplates_1", "Test_GetSqlTemplat es_3") 支持字符串数组形式参数 e)engine.getsqltemplates([]string{"test_getsqltemplates_1", "Test_GetSqlTemplat es_3","test_getsqltemplates_2") 支持字符串数组形式和字符串参数混用 f)engine.getsqltemplates([]string{"test_getsqltemplates_1", "Test_GetSqlTemplat es_3","test_getsqltemplates_2",3) 支持字符串数组形式, 字符串参数和其他类型参数混用, 但查询时只会处理字符串类型参数和字符转数组类型参数 ( 因为 SqlTemplate 的 key 是字符串类型 ), 返回的 map[string]*pongo2.template 也无其他类型的 key */ engine.sqltemplate.getsqltemplates(...key)

39 插入数据 插入数据 插入数据 支持执行 SQL 与 ORM 两种方式插入数据

40 ORM 方式插入数据 ORM 方式插入数据 ORM 方式插入数据 插入数据使用 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"

41 ORM 方式插入数据... 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 条插入一次

42 执行 SQL 命令插入数据 执行 SQL 命令插入数据执行 SQL 命令插入数据 也可以直接执行一个 SQL 命令, 即执行 Insert 操作 第 1 种方式 sql ="insert into config(key,value) values (?,?)" res, err := engine.exec(sql, "OSCHINA", "OSCHINA") 第 2 种方式 sql_2 := "insert into config(key,value) values (?,?)" affected, err := engine.sql(sql_4, "OSCHINA", "OSCHINA").Execute() 第 3 种方式 //SqlMap 中 key 为 "sql_i_1" 配置的 Sql 语句为 :insert into config(key,value) values (?,?) sql_i_1 := "sql_i_1" affected, err := engine.sqlmapclient(sql_i_1, "config_1", "1").Execute() //SqlMap 中 key 为 "sql_i_2" 配置的 Sql 语句为 :insert into config(key,value) values (? key,?value) sql_i_2 := "sql_i_2" parammap_i := map[string]interface{{"key": "config_2", "value": "2" affected, err := engine.sqlmapclient(sql_i_2, &parammap_i).execute() 第 4 种方式 sql_i_3 := "insert.example.stpl" parammap_i_t := map[string]interface{{"key": "config_3", "value": "3" affected, err := engine.sqltemplateclient(sql_i_3, &parammap_i_t).execute() 注 : 除以上 4 种方式外, 本库还支持另外 3 种方式, 由于这 3 种方式支持一次性批量混合 CRUD 操作, 返回多个结果集, 且支持多种参数组合形式, 内容较多, 场景比较复杂, 因此不在此处赘述 欲了解另外 3 种方式相关内容您可移步批量 SQL 操作章节, 此 4 种方式将在此章节单独说明

43 创建时间 Created 创建时间 Created 创建时间 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(" :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 (?...)

44 创建时间 Created 最后一个值得注意的是时区问题, 默认 xorm 采用 Local 时区, 所以默认调用的 time.now() 会先被转换成对应的时 区 要改变 xorm 的时区, 可以使用 : engine.tzlocation, _ = time.loadlocation("asia/shanghai")

45 查询和统计数据 查询和统计数据 查询和统计数据 支持执行 SQL 与 ORM 两种方式查询数据

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

47 查询条件方法 查询条件方法查询条件方法 查询和统计主要使用 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 如果是复合主键, 则可以

48 查询条件方法 engine.id(core.pk{1, "name").get(&user) // SELECT * FROM user Where id =1 AND name= 'name' 传入的两个参数按照 struct 中 pk 标记字段出现的顺序赋值 Or(string, 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) Where(string, interface{) 和 SQL 中 Where 语句中的条件基本相同, 作为条件 In(string, interface{) 某字段在一些值中, 这里需要注意必须是 []interface{ 才可以展开, 由于 Go 语言的限制,[]int64 等不可以直接展 开, 而是通过传递一个 slice 示例代码如下 : engine.in("cloumn", 1, 2, 3).Find() engine.in("column", []int{1, 2, 3).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=?

49 查询条件方法 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)

50 查询条件方法 相当于 Limit(int, 0) Join(string,interface{,string) 第一个参数为连接类型, 当前支持 INNER, LEFT OUTER, CROSS 中的一个值, 第二个参数为 string 类型的表名, 表对应的结构体指针或者为两个值的 []string, 表示表名和别名, 第三个参数为连接条件 GroupBy(string) Groupby 的参数字符串 Having(string) Having 的参数字符串

51 临时开关方法 临时开关方法 临时开关方法 NoAutoTime() 如果此方法执行, 则此次生成的语句中 Created 和 Updated 字段将不自动赋值为当前时间 NoCache() 如果此方法执行, 则此次生成的语句则在非缓存模式下执行 NoAutoCondition() 禁用自动根据结构体中的值来生成条件 engine.where("name =?", "lunny").get(&user{id:1) // SELECT * FROM user where name='lunny' AND id = 1 LIMIT 1 engine.where("name =?", "lunny").noautocondition().get(&user{id:1) // SELECT * FROM user where name='lunny' LIMIT 1 UseBool( string) 当从一个 struct 来生成查询条件或更新字段时,xorm 会判断 struct 的 field 是否为 0,,nil, 如果为以上则不当做查询条件或者更新内容 因为 bool 类型只有 true 和 false 两种值, 因此默认所有 bool 类型不会作为查询条件或者更新字段 如果可以使用此方法, 如果默认不传参数, 则所有的 bool 字段都将会被使用, 如果参数不为空, 则参数中指定的为字段名, 则这些字段对应的 bool 值将被使用 NoCascade() 是否自动关联查询 field 中的数据, 如果 struct 的 field 也是一个 struct 并且映射为某个 Id, 则可以在查询时自动调用 Get 方法查询出对应的数据

52 Get 方法 Get 方法 Get 方法 查询单条数据使用 Get 方法, 在调用 Get 方法时需要传入一个对应结构体的指针, 同时结构体中的非空 field 自动成为查询的条件和前面的方法条件组合在一起查询 如 : 1. 根据 Id 或者 name 来获得单条数据 : has, err := engine.get(&user) // SELECT * FROM user LIMIT 1 has, err := engine.where("name =?", name).desc("id").get(&user) // SELECT * FROM user WHERE name =? ORDER BY id DESC LIMIT 1 var name string has, err := engine.where("id =?", id).cols("name").get(&name) // SELECT name FROM user WHERE id =? var id int64 has, err := engine.where("name =?", name).cols("id").get(&id) // SELECT id FROM user WHERE name =? var valuesmap = make(map[string]string) has, err := engine.where("id =?", id).get(&valuesmap) // SELECT * FROM user WHERE id =? var valuesslice = make([]interface{, len(cols)) has, err := engine.where("id =?", id).cols(cols...).get(&valuesslice) // SELECT col1, col2, col3 FROM user WHERE id =? 2. 根据 Where 来获得单条数据 : user := new(user) has, err := engine.where("name=?", "xlw").get(user) 3. 根据 user 结构体中已有的非空数据来获得单条数据 : user := &User{Id:1 has, err := engine.get(user) 或者其它条件 user := &User{Name:"xlw" has, err := engine.get(user)

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

54 Find 方法 Find 方法 Find 方法 查询多条数据使用 Find 方法,Find 方法的第一个参数为 slice 的指针或 Map 指针, 即为查询后返回的结果, 第二个参数可选, 为查询的条件 struct 的指针 1. 传入 Slice 用于返回数据 everyone := make([]userinfo, 0) err := engine.find(&everyone) peveryone := make([]*userinfo, 0) err := engine.find(&peveryone) 2. 传入 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) 3. 也可以加入各种条件 users := make([]userinfo, 0) err := engine.where("age >? or name =?", 30, "xlw").limit(20, 10).Find(&users) 4. 如果只选择单个字段, 也可使用非结构体的 Slice var ints []int64 err := engine.table("user").cols("id").find(&ints)

55 Join 的使用 Join 的使用 Join 的使用 Join(string,interface{,string) 第一个参数为连接类型, 当前支持 INNER, LEFT OUTER, CROSS 中的一个值, 第二个参数为 string 类型的表名, 表对应的结构体指针或者为两个值的 []string, 表示表名和别名, 第三个参数为连接条件 以下将通过示例来讲解具体的用法 : 假如我们拥有两个表 user 和 group, 每个 User 只在一个 Group 中, 那么我们可以定义对应的 struct type Group struct { Id int64 Name string type User struct { Id int64 Name string GroupId int64 `xorm:"index"` OK 问题来了, 我们现在需要列出所有的 User, 并且列出对应的 GroupName 利用 extends 和 Join 我们可以比 较优雅的解决这个问题 代码如下 : type UserGroup struct { User `xorm:"extends"` Name string func (UserGroup) TableName() string { return "user" users := make([]usergroup, 0) engine.join("inner", "group", "group.id = user.group_id").find(&users) 这里我们将 User 这个匿名结构体加了 xorm 的 extends 标记 ( 实际上也可以是非匿名的结构体, 只要有 extends 标 记即可 ), 这样就减少了重复代码的书写 实际上这里我们直接用 Sql 函数也是可以的, 并不一定非要用 Join users := make([]usergroup, 0)

56 Join 的使用 engine.sql("select user.*, group.name from user, group where user.group_id = gr oup.id").find(&users) 然后, 我们忽然发现, 我们还需要显示 Group 的 Id, 因为我们需要链接到 Group 页面 这样又要加一个字段, 算 了, 不如我们把 Group 也加个 extends 标记吧, 代码如下 : type UserGroup struct { User `xorm:"extends"` Group `xorm:"extends"` func (UserGroup) TableName() string { return "user" users := make([]usergroup, 0) engine.join("inner", "group", "group.id = user.group_id").find(&users) 这次, 我们把两个表的所有字段都查询出来了, 并且赋值到对应的结构体上了 这里要注意,User 和 Group 分别有 Id 和 Name, 这个是重名的, 但是 xorm 是可以区分开来的, 不过需要特别注意 UserGroup 中 User 和 Group 的顺序, 如果顺序反了, 则有可能会赋值错误, 但是程序不会报错 这里的顺序应遵循如下原则 : 结构体中 extends 标记对应的结构顺序应和最终生成 SQL 中对应的表出现的顺序相同 还有一点需要注意的, 如果在模板中使用这个 UserGroup 结构体, 对于字段名重复的必须加匿名引用, 如 : 对于不重复字段, 可以 {{.GroupId, 对于重复字段 {{.User.Id 和 {{.Group.Id 这是 2 个表的用法,3 个或更多表用法类似, 如 : type Type struct { Id int64 Name string type UserGroupType struct { User `xorm:"extends"` Group `xorm:"extends"` Type `xorm:"extends"` users := make([]usergrouptype, 0) engine.table("user").join("inner", "group", "group.id = user.group_id"). Join("INNER", "type", "type.id = user.type_id"). Find(&users)

57 Join 的使用 同时, 在使用 Join 时, 也可同时使用 Where 和 Find 的第二个参数作为条件,Find 的第二个参数同时也允许为各种 bean 来作为条件 Where 里可以是各个表的条件,Find 的第二个参数只是被关联表的条件 engine.table("user").join("inner", "group", "group.id = user.group_id"). Join("INNER", "type", "type.id = user.type_id"). Where("user.name like?", "%"+name+"%").find(&users, &User{Name:name) 当然, 如果表名字太长, 我们可以使用别名 : engine.table("user").alias("u"). Join("INNER", []string{"group", "g", "g.id = u.group_id"). Join("INNER", "type", "type.id = u.type_id"). Where("u.name like?", "%"+name+"%").find(&users, &User{Name:name)

58 Iterate 方法 Iterate 方法 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 )

59 Count 方法 Count 方法 Count 方法 统计数据使用 Count 方法,Count 方法的参数为 struct 的指针并且成为查询条件 user := new(user) total, err := engine.where("id >?", 1).Count(user)

60 Rows 方法 Rows 方法 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) //

61 Sum 系列方法 Sum 系列方法 Sum 系列方法 求和数据可以使用 Sum, SumInt, Sums 和 SumsInt 四个方法,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 ss := 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")

62 Sum 系列方法 fmt.printf("money is %d", total[0])

63 Exist 方法 Exist 方法 Exist 方法 检测记录是否存在 // SELECT * FROM record_exist LIMIT 1 has, err := engine.exist(new(recordexist)) // SELECT * FROM record_exist WHERE name =? LIMIT 1 has, err = engine.exist(&recordexist{ Name: "test1", ) // SELECT * FROM record_exist WHERE name =? LIMIT 1 has, err = engine.where("name =?", "test1").exist(&recordexist{) // select * from record_exist where name =? has, err = engine.sql("select * from record_exist where name =?", "test1").exi st() // SELECT * FROM record_exist LIMIT 1 has, err = engine.table("record_exist").exist() // SELECT * FROM record_exist WHERE name =? LIMIT 1 has, err = engine.table("record_exist").where("name =?", "test1").exist() 与 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 更高

64 子查询 子查询子查询 QueryExpr() 在 Where() 条件方法中可以使用 QueryExpr() 来构建子查询 参考用法如下 : var student []Student err = db.table("student").select("id,name").where("id in (?)", db.table("stude ntinfo").select("id").where("status =?", 2).QueryExpr()).Find(&student) //SELECT id,name FROM `student` WHERE (id in (SELECT id FROM `studentinfo` WHE RE (status = 2))) 注意 : 使用 QueryExpr() 后,Where() 中构建的 SQL 语句中占位符 '?' 全部替换为实参, 请注意合理构建 SQL, 防

65 执行 SQL 查询 执行 SQL 查询执行 SQL 查询 执行指定的 Sql 语句, 并把结果映射到结构体 有时, 当选择内容或者条件比较复杂时, 可以直接使用 Sql 执行一个 SQL 查询, 即 Select 命令 在 Postgres 中支持原始 SQL 语句中使用 ` 和? 符号

66 执行 SQL 查询的 11 种常用方式 执行 SQL 查询的 11 种常用方式 执行 SQL 查询的 11 种常用方式 第 1 种方式 sql_1_1 := "select * from user" results, err := engine.querybytes(sql_1_1) //SqlMapClient 和 SqlTemplateClient 传参方式类同如下 2 种, 具体参见第 6 种方式和第 7 种方式 sql_1_2 := "select id,userid,title,createdatetime,content from Article where id =?" results, err := db.sql(sql_1_2, 2).QueryBytes() sql_1_3 := "select id,userid,title,createdatetime,content from Article where id =?id" parammap_1_3 := map[string]interface{{"id": 2 results, err := db.sql(sql_1_3, &parammap_1_3).querybytes() 当调用 QueryBytes 时, 第一个返回值 results 为 []map[string][]byte 的形式 第 2 种方式 sql_2_1 := "select * from user" results, err := engine.querystring(sql_2_1) //SqlMapClient 和 SqlTemplateClient 传参方式类同如下 2 种, 具体参见第 6 种方式和第 7 种方式 sql_2_2 := "select id,userid,title,createdatetime,content from Article where id =?" results, err := db.sql(sql_2_2, 2).QueryString() sql_2_3 := "select id,userid,title,createdatetime,content from Article where id =?id" parammap_2_3 := map[string]interface{{"id": 2 results, err := db.sql(sql_2_3, &parammap_2_3).querystring() 当调用 QueryString 时, 第一个返回值 results 为 []map[string]string 的形式 第 3 种方式 //Value 类型本质是 []byte, 具有一系列类型转换函数 sql_2_1 := "select * from user" results, err := engine.queryvalue(sql_2_1) //SqlMapClient 和 SqlTemplateClient 传参方式类同如下 2 种, 具体参见第 6 种方式和第 7 种方式

67 执行 SQL 查询的 11 种常用方式 sql_2_2 := "select id,userid,title,createdatetime,content from Article where id =?" results, err := db.sql(sql_2_2, 2).QueryValue() title := results[0]["title"].string() sql_2_3 := "select id,userid,title,createdatetime,content from Article where id =?id" parammap_2_3 := map[string]interface{{"id": 2 results, err := db.sql(sql_2_3, &parammap_2_3).queryvalue() 当调用 QueryValue 时, 第一个返回值 results 为 []map[string]xorm.value 的形式 xorm.value 类型本质是 []byte, 具有一系列类型转换函数 第 4 种方式 : 返回的结果类型为 xorm.result //xorm.value 类型本质是 []byte, 具有一系列类型转换函数 sql_2_1 := "select * from user" results, err := engine.queryresult(sql_2_1).list() //SqlMapClient 和 SqlTemplateClient 传参方式类同如下 2 种, 具体参见第 6 种方式和第 7 种方式 sql_2_2 := "select id,userid,title,createdatetime,content from Article where id =?" result, err := db.sql(sql_2_2, 2).QueryResult().List() title := result[0]["createdatetime"].time(" t15:04:05.999z") content := result[0]["content"].nullstring() sql_2_3 := "select id,userid,title,createdatetime,content from Article where id =?id" parammap_2_3 := map[string]interface{{"id": 2 results, err := db.sql(sql_2_3, &parammap_2_3).queryresult().list() 当调用 QueryResult 时, 第一个返回值 results 为 xorm.result 的形式 第 5 种方式 sql_3_1 := "select * from user" results, err := engine.queryinterface(sql_3_1) //SqlMapClient 和 SqlTemplateClient 传参方式类同如下 2 种, 具体参见第 6 种方式和第 7 种方式 sql_3_2 := "select id,userid,title,createdatetime,content from Article where id =?" results, err := db.sql(sql_3_2, 2).QueryInterface() sql_3_3 := "select id,userid,title,createdatetime,content from Article where id =?id" parammap_3_3 := map[string]interface{{"id": 2 results, err := db.sql(sql_3_3, &parammap_3_3).queryinterface()

68 执行 SQL 查询的 11 种常用方式 //Query 方法返回的是一个 ResultMap 对象, 它有 List(),Count(),ListPage(),Json(),Xml() //Xml(),XmlIndent(),SaveAsCSV(),SaveAsTSV(),SaveAsHTML(),SaveAsXML(),SaveAs XMLWithTagNamePrefixIndent(), //SaveAsYAML(),SaveAsJSON(),SaveAsXLSX() 系列实用函数 sql_3_4 := "select * from user" //List() 方法返回查询的全部结果集, 类型为 []map[string]interface{ results, err := engine.sql(sql_3_4).query().list() // 当然也支持这种方法, 将数据库中的时间字段格式化, 时间字段对应的 golang 数据类型为 time.time // 当然你也可以在数据库中先使用函数将时间类型的字段格式化成字符串, 这里只是提供另外一种方式 // 该方式会将所有时间类型的字段都格式化, 所以请依据您的实际需求按需使用 results, err := engine.sql(sql_3_4).querywithdateformat(" ").list() sql_3_5 := "select * from user where id =? and age =?" results, err := engine.sql(sql_3_5, 7, 17).Query().List() sql_3_6 := "select * from user where id =?id and age =?age" parammap_3_6 := map[string]interface{{"id": 7, "age": 17 results, err := engine.sql(sql_3_6, &parammap_3_6).query().list() // 此 Query() 方法返回对象还支持 ListPage() 方法和 Count() 方法, 这两个方法都是针对数据库查询出来后的结果集进行操作 // 此 Query() 方法返回对象还支持 Xml() 方法 XmlIndent() 方法和 Json() 方法, 相关内容请阅读之后的章节 //ListPage() 方法并非数据库分页方法, 只是针对数据库查询出来后的结果集 []map[string]interfa ce{ 对象取部分切片 // 例如以下例子, 是取结果集的第 1 条到第 50 条记录 results, err := engine.sql(sql_3_5, 7, 17).Query().ListPage(1,50) // 例如以下例子, 是取结果集的第 13 条到第 28 条记录 results, err := engine.sql(sql_3_5, 7, 17).Query().ListPage(13,28) // 此 Count() 方法也并非使用数据库 count 函数查询数据库某条件下的记录数, 只是针对 Sql 语句对数据库查询出来后的结果集 []map[string]interface{ 对象的数量 // 此 Count() 方法也并非 Engine 对象和 Session 对象下的 Count() 方法, 使用时请区分场景 count, err := engine.sql(sql_3_5, 7, 17).Query().Count() 当调用 QueryInterface,List 或 ListPage 时, 第一个返回值 results 为 []map[string]interface{ 的形式 第 6 种方式 : 使用 SqlMapClient 调用 SqlMap 配置执行 Sql 语句 sql_id_4_1 := "sql_4_1" // 配置文件中 sql 标签的 id 属性,SqlMap 的 key results, err := engine.sqlmapclient(sql_id_4_1).query().list() sql_id_4_2 := "sql_4_2" results, err := engine.sqlmapclient(sql_id_4_2, 7, 17).Query().List() sql_id_4_3 := "sql_4_3" parammap_4_3 := map[string]interface{{"id": 7, "name": "xormplus" results1, err := engine.sqlmapclient(sql_id_4_3, &parammap_4_3).query().list()

69 执行 SQL 查询的 11 种常用方式 第 7 种方式 : 使用 SqlTemplateClient 调用 SqlTemplate 模板执行 Sql 语句 sql_key_5_1 := "select.example.stpl" // 配置文件名,SqlTemplate 的 key // 执行的 sql:select * from user where id=7 // 如部分参数未使用, 请记得使用对应类型 0 值, 如此处 name 参数值为空字符串, 模板使用指南请详见 pon go2 parammap_5_1 := map[string]interface{{"count": 2, "id": 7, "name": "" results, err := engine.sqltemplateclient(sql_key_5_1, &parammap_5_1).query().li st() // 执行的 sql:select * from user where name='xormplus' // 如部分参数未使用, 请记得使用对应类型 0 值, 如此处 id 参数值为 0, 模板使用指南请详见 pongo2 parammap_5_2 := map[string]interface{{"id": 0, "count": 2, "name": "xormplus" results, err := engine.sqltemplateclient(sql_key_5_1, &parammap_5_2).query().li st() 当调用 List 或 ListPage 时, 第一个返回值 results 为 []map[string]interface{ 的形式 第 8 种方式 : 返回的结果类型为对应的 []interface{ var categories []Category err := engine.sql("select * from category where id =?", 16).Find(&categories) parammap_6 := map[string]interface{{"id": 2 err := engine.sql("select * from category where id =?id", &parammap_6).find(&ca tegories) 第 9 种方式 : 返回的结果类型为对应的 []interface{ sql_id_7_1 := "sql_7_1" var categories []Category err := engine.sqlmapclient(sql_id_7_1, 16).Find(&categories) sql_id_7_2 := "sql_7_2" var categories []Category parammap_7_2 := map[string]interface{{"id": 25 err := engine.sqlmapclient(sql_id_7_2, &parammap_7_2).find(&categories) 第 10 种方式 : 返回的结果类型为对应的 []interface{ // 执行的 sql:select * from user where name='xormplus' sql_key_8_1 := "select.example.stpl" // 配置文件名,SqlTemplate 的 key var users []User parammap_8_1 := map[string]interface{{"id": 0, "count": 2, "name": "xormplus" err := engine.sqltemplateclient(sql_key_8_1, &parammap_8_1).find(&users)

70 执行 SQL 查询的 11 种常用方式 第 11 种方式 : 查询单条数据 使用 Sql,SqlMapClient,SqlTemplateClient 函数与 Get 函数组合可以查询单条数据 以 Sql 与 Get 函数组合为例 : // 获得单条数据的值, 并存为结构体 var article Article has, err := db.sql("select * from article where id=?", 2).Get(&article) // 获得单条数据的值并存为 map var valuesmap1 = make(map[string]string) has, err := db.sql("select * from article where id=?", 2).Get(&valuesMap1) var valuesmap2 = make(map[string]interface{) has, err := db.sql("select * from article where id=?", 2).Get(&valuesMap2) var valuesmap3 = make(map[string]xorm.value) has, err := db.sql("select * from article where id=?", 2).Get(&valuesMap3) // 获得单条数据的值并存为 xorm.record record := make(xorm.record) has, err = session.sql("select * from article where id=?", 2).Get(&record) id := record["id"].int64() content := record["content"].nullstring() // 获得单条数据某个字段的值 var title string has, err := db.sql("select title from article where id=?", 2).Get(&title) var id int has, err := db.sql("select id from article where id=?", 2).Get(&id) 注 : 第 6 种和第 9 种方式所使用的 SqlMap 配置文件内容如下 <sqlmap> <sql id="sql_4_1"> select * from user </sql> <sql id="sql_4_2"> select * from user where id=? and age=? </sql> <sql id="sql_4_3"> select * from user where id=?id and name=?name </sql> <sql id="sql_id_7_1">

71 执行 SQL 查询的 11 种常用方式 select * from category where id =? </sql> <sql id="sql_id_7_2"> select * from category where id =?id </sql> </sqlmap> 第 7 种和第 10 种方式所使用的 SqlTemplate 配置文件内容如下, 文件名 :select.example.stpl, 路径为 engine.sqlmap.sqlmaprootdir 配置目录下的任意子目录中 使用模板方式配置 Sql 较为灵活, 可以使用 pongo2 引擎的相关功能灵活组织 Sql 语句以及动态 SQL 拼装 select * from user where {% if count>1% id=?id {% else% name=?name {% endif % 除以上 1 种方式外, 本库还支持另外 3 种方式, 由于这 3 种方式支持一次性批量混合 CRUD 操作, 返回多个结果集, 且支持多种参数组合形式, 内容较多, 场景比较复杂, 因此不在此处赘述 欲了解另外 3 种方式相关内容您可移步批量 SQL 操作章节, 此 3 种方式将在此章节单独说明采用 Sql(),SqlMapClient(),SqlTemplateClient() 方法执行 sql 调用 Find() 方法, 与 ORM 方式调用 Find() 方法不同, 此时 Find() 方法中的参数, 即结构体的名字不需要与数据库表的名字映射 ( 因为前面的 Sql() 方法已经确定了 SQL 语句 ), 但字段名需要和数据库查询结果集中的字段名字做映射 使用 Find() 方法需要自己定义查询返回结果集的结构体, 如不想自己定义结构体可以使用 Query() 方法, 返回 []map[string]interface{, 两种方式请依据实际需要选用 举例 : 多表联合查询例子如下 // 执行的 SQL 如下, 查询的是 article 表与 category 表, 查询的表字段是这两个表的部分字段 sql := `SELECT article.id, article.title, article.isdraft, article.lastupdatetime, category.name as categoryname FROM article, category WHERE article.categorysubid = category. ID AND category. ID = 4`

72 执行 SQL 查询的 11 种常用方式 // 我们可以定义一个结构体, 注意 : 结构体中的字段名和上面执行的 SQL 语句字段名映射, 字段数据类型正确 // 当然你还可以给这个结构体加更多其他字段, 但是如果执行上面的 SQL 语句时, 这些其他字段只会被赋值对应数据类型的零值 type CategoryInfo struct { Id int Title string Categoryname string Isdraft int Lastupdatetime time.time var categoryinfo []CategoryInfo // 执行 sql, 返回值为 error 对象, 同时查询的结果集会被赋值给 []CategoryInfo // 同理, 此处也可以使用 SqlMapClient(),SqlTemplateClient() 函数来获取 sql err = db.sql(sql).find(&categoryinfo) if err!= nil { t.fatal(err) t.log(categoryinfo) t.log(categoryinfo[0].categoryname) t.log(categoryinfo[0].id) t.log(categoryinfo[0].title) t.log(categoryinfo[0].isdraft) t.log(categoryinfo[0].lastupdatetime)

73 查询返回 json 或 xml 字符串 查询返回 json 或 xml 字符串查询返回 json 或 xml 字符串 Search() 方法和 Query() 方法支持链式读取数据操作查询返回 json 或 xml 字符串 第 1 种方式 var users []User results,err := engine.where("id=?", 6).Search(&users).Xml() // 返回查询结果的 xml 字符串 results,err := engine.where("id=?", 6).Search(&users).Json() // 返回查询结果的 json 字符串 第 2 种方式 sql := "select * from user where id =?" // 返回查询结果的 json 字符串 results, err := engine.sql(sql, 2).Query().Json() // 返回查询结果的 json 字符串, 并支持格式化日期 results, err := engine.sql(sql, 2).QueryWithDateFormat(" ").Json() // 返回查询结果的 xml 字符串, 并支持格式化日期 results, err := engine.sql(sql, 2).QueryWithDateFormat(" ").Xml() sql := "select * from user where id =?id and userid=?userid" parammap := map[string]interface{{"id": 6, "userid": 1 results, err := engine.sql(sql, &parammap).query().xmlindent("", " ", "article" ) 第 3 种方式 sql_id_3_1 := "sql_3_1" // 配置文件中 sql 标签的 id 属性,SqlMap 的 key results, err := engine.sqlmapclient(sql_id_3_1, 7, 17).Query().Json() // 返回查询结果的 json 字符串 sql_id_3_2 := "sql_3_2" // 配置文件中 sql 标签的 id 属性,SqlMap 的 key parammap := map[string]interface{{"id": 6, "userid": 1 // 支持参数使用 map 存放 results, err := engine.sqlmapclient(sql_id_3_2, &parammap).query().xml() // 返回查询结果的 xml 字符串 第 4 种方式 sql_key_4_1 := "select.example.stpl"

74 查询返回 json 或 xml 字符串 parammap_4_1 := map[string]interface{{"id": 6, "userid": 1 results, err := engine.sqltemplateclient(sql_key_4_1, &parammap_4_1).query().js on()

75 链式查询据操返回某条记录的某个字段的值 链式查询据操返回某条记录的某个字段的值 链式查询据操返回某条记录的某个字段的值 第 1 种方式 // 返回查询结果的第一条数据的 id 列的值 id := engine.sql(sql, 2).Query().Results[0]["id"] 第 2 种方式 // 返回查询结果的第一条数据的 id 列的值 id := engine.sqlmapclient(key, 2).Query().Results[0]["id"] // 返回查询结果的第一条数据的 id 列的值 id := engine.sqlmapclient(key, &parammap).query().results[0]["id"] 第 3 种方式 // 返回查询结果的第一条数据的 id 列的值 id := engine.sqltemplateclient(key, &parammap).query().results[0]["id"]

76 SqlTemplateClient 执行过程 SqlTemplateClient 执行过程 SqlTemplateClient 的执行过程 示例代码 : sql_key_4_1 := "select.example.stpl" // 模板文件名,SqlTemplate 的 key // 执行的 sql:select * from user where id=7 // 如部分参数未使用, 请记得使用对应类型 0 值, 如此处 name 参数值为空字符串, 模板使用指南请详见 pon go2 parammap_4_1 := map[string]interface{{"count": 2, "id": 7, "name": "" results, err := engine.sqltemplateclient(sql_key_4_1, &parammap_4_1).query().li st() // 执行的 sql:select * from user where name='xormplus' // 如部分参数未使用, 请记得使用对应类型 0 值, 如此处 id 参数值为 0, 模板使用指南请详见 pongo2 parammap_4_2 := map[string]interface{{"id": 0, "count": 0, "name": "xormplus" results, err := engine.sqltemplateclient(sql_key_4_1, &parammap_4_2).query().li st() // 执行的 sql:select * from user where name='xormplus' sql_key_7_1 := "select.example.stpl" // 配置文件名,SqlTemplate 的 key var users []User parammap_7_1 := map[string]interface{{"id": 0, "count": 0, "name": "xormplus" err := engine.sqltemplateclient(sql_key_7_1, &parammap_7_1).find(&users) 示例 SQL 模板 : select * from user where {% if count>1% id=?id {% else% name=?name {% endif % 过程说明 : 1 SqlTemplateClient 函数第二个参数存在时, 则按约定需为 *map[string]interface{ 类型, 该参数包含 2 部分内容, 一部分为执行 pongo2 的 SQL 模板所需的执行参数, 另一部分为通过执行 pongo2 的 SQL 模板获得的预编译 SQL 所需的 sql 参数 2 SqlTemplateClient 函数会执行 pongo2 的 SQL 模板获得的预编译 SQL 后, 将 map 参数继续传递给后面的 Query,Find,Search,Execute 等函数继续执行预编译 SQL

77 SqlTemplateClient 执行过程 3 以示例代码的第一部分为例,SqlTemplateClient 函数, 会依据传入的模板名称 sql_key_4_1 找到指定的执行 SQL 模板, 依据传入的参数 parammap_4_1 执行模板, 获得预编译 SQL 为 select * from user where id=?, 之后将预编译 SQL 和 parammap_4_1 传递给 Query 函数执行, 最 终执行的 SQL 为 select * from user where id=7 4 最后请注意不要在 SqlTemplate 中硬拼接 SQL, 这样做存在 SQL 注入风险, 请使用预编译 SQL 合理使用 SqlTemplate

78 关于数据库分页查询 关于数据库分页查询 关于数据库分页查询 在前面的章节中介绍了如下的链式调用方式来获取查询结果集的部分切片 // 例如以下例子, 是取结果集的第 1 条到第 50 条记录 results, err := engine.sql(sql).query().listpage(1,50) 但这并不是数据库分页查询, 而是将查询结果集获取到本地取部分切片 xorm 也同样支持数据库分页查询, 有如下四种方式 : 方式一 :orm 方式数据库分页查询 使用 Limit 函数进行数据库分页查询,Limit(int, int) 限制获取的数目, 第一个参数为条数, 第二个参数表示开始 位置, 如果不传则为 0 var log []Log err = engine.limit(5, 2).Find(&log) 方式二 : 执行原生 sql 语句方式数据库分页查询 使用 Sql,SqlMapClient,SqlTemplateClient 函数执行原生 sql 语句进行数据库分页查询 results, err := engine.sql("select * FROM log LIMIT? OFFSET?", 5, 2).Query(). List() var log []Log err := engine.sql("select * FROM log LIMIT? OFFSET?", 5, 2).Find(&log) 方式三 : 使用原生 sql 语句配合 Limit 函数或 OrderBy 函数方式进行数据库分页查询 有时, 我们需要跨表查询多张表或者 join 多张表进行数据库分页查询, 而 oracle 和 mssql 数据库分页语句相对复杂和多样, 我们希望更关注业务逻辑, 此时可以使用原生 sql 语句配合 Limit 函数或 OrderBy 函数方式进行数据库分页查询 对于 oracle,postgresql,mysql 等数据库, 可以使用原生 sql 语句配合 Limit 函数进行数据库分页查询 注意, 这些数据库并不支持像 mssql 数据库那样使用原生 sql 语句配合 Limit 函数及 OrderBy 函数进行数据库分页查询 results, err := engine.sql("select * FROM log").limit(5, 2).Query().List()

79 关于数据库分页查询 var ptlog []PtTimelineLog err = engine.sql("select * FROM log").limit(5, 2).Find(&ptlog) 对于 postgresql,mysql 数据库生成的分页查询 sql 语句为 SELECT * FROM log LIMIT 5 OFFSET 2 在 oracle 数据库中同样也可以使用以上方式进行数据库分页查询, 举例如下 : querysql := "QUERY_EQU_ALL_RFID" //sqlmap id devicecode:= "210601" // 设备编号 pagenumber:=2 // 第 2 页 pageitemnumber:=10 // 每页显示数量 first := (pagenumber - 1) * pageitemnumber results, err := engine.sqlmapclient(querysql, devicecode).limit(pageitemnumber, first).query().list() sqlmap 中配置的 sql 语句如下 : SELECT ID,EQU_ID,EQU_NAME,EQU_TYPE_CODE,EQU_TYPE_NAME,COMPONENT_CODE,COMPONENT_ NAME,RFID,RFID_VERIFY,ISSUE_TIME,VERIFY_TIME,POS_X,POS_Y,ACCOUNT,ACCOUNT_NAME f rom VIEW_EQU_ALL_RFID where EQU_TYPE_CODE=? oracle 数据库生成执行的分页查询 SQL 语句如下, 其中 xormdvceqcj97xqysacy36rehk 是一个 ROWNUM 伪 列, 查询返回结果集 []map[string]interface{ 中将不会存在, 如果使用 Find 函数返回结果集, 定义的结构体中无 需定义此字段 SELECT aat.* FROM (SELECT at.*,rownum xormdvceqcj97xqysacy36rehk FROM (SELECT ID,EQU_ID,EQU_NAME,EQU_TYPE_CODE,EQU_TYPE_NAME,COMPONENT_CODE,COMPONENT_NAME,RFID, RFID_VERIFY,ISSUE_TIME,VERIFY_TIME,POS_X,POS_Y,ACCOUNT,ACCOUNT_NAME from VIEW_E QU_ALL_RFID where EQU_TYPE_CODE='210601') at WHERE ROWNUM <= 20) aat WHERE xorm dvceqcj97xqysacy36rehk > 10 对于 mssql 数据库, 由于数据库分页语句的本身的特性, 可以使用原生 sql 语句配合 Limit 函数及 OrderBy 函数进行 数据库分页查询 注意, 在 mssql 数据库中这种方式进行数据库分页查询,Limit 函数及 OrderBy 函数必须一起使 用 // 示例 1 results, err := engine.sql("select t_tx_counts.id,t_tx_counts.count,t_tx_code.c ode from t_tx_code,t_tx_counts where t_tx_code.id=t_tx_counts.id and t_tx_code

80 关于数据库分页查询 id>?", 2). OrderBy("count").Limit(5, 1).Query().List() // 示例 2 var results []Result err = engine.sql("select t_tx_counts.id,t_tx_counts.count,t_tx_code.code from t _tx_code,t_tx_counts where t_tx_code.id=t_tx_counts.id and t_tx_code.id>?", 2). OrderBy("id DESC").Limit(5, 0).Find(&results) 示例 1 生成的 sql 语句如下, 其中 xormv2rgnvawh4qpt5qntkt4pz 是一个 ROW_NUMBER 伪列, 查询返回结果 集 []map[string]interface{ 中将不会存在, 如果使用 Find 函数返回结果集, 定义的结构体中无需定义此字段 SELECT sq.* FROM (SELECT ROW_NUMBER() OVER (ORDER BY count) AS xormv2rgnvawh4qp T5Qntkt4PZ, T_TX_COUNTS.ID,T_TX_COUNTS.COUNT,T_TX_CODE.CODE FROM T_TX_CODE,T_TX _COUNTS WHERE T_TX_CODE.ID=T_TX_COUNTS.ID AND T_TX_CODE.ID>2) AS sq WHERE xormv 2rGnVAWh4qPT5Qntkt4PZ BETWEEN 2 AND 6 示例 2 生成的 sql 语句如下 SELECT TOP 5 T_TX_COUNTS.ID,T_TX_COUNTS.COUNT,T_TX_CODE.CODE FROM T_TX_CODE,T_T X_COUNTS WHERE T_TX_CODE.ID=T_TX_COUNTS.ID AND T_TX_CODE.ID>2 ORDER BY id DESC 方式四 : 使用 sql builder 来构建 sql 语句进行数据库分页查询 sql builder 相关内容请阅读原版 xorm 提供了一个 SQL Builder 库 github.com/go-xorm/builder

81 更新数据 更新数据 # 更新数据 支持执行 SQL 与 ORM 两种方式查询数据

82 ORM 方式更新数据 ORM 方式更新数据 ORM 方式更新数据 更新数据使用 Update 方法

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

84 乐观锁 Version 乐观锁 Version 乐观锁 Version 要使用乐观锁, 需要使用 version 标记 type User struct { Id int64 Name string Version int xorm:"version" 在 Insert 时,version 标记的字段将会被设置为 1, 在 Update 时,Update 的内容必须包含 version 原来的值 var user User engine.id(1).get(&user) // SELECT * FROM user WHERE id =? engine.id(1).update(&user) // UPDATE user SET..., version = version + 1 WHERE id =? AND version =?

85 更新时间 Updated 更新时间 Updated 更新时间 Updated Updated 可以让您在记录插入或每次记录更新时自动更新数据库中的标记字段为当前时间, 需要在 xorm 标记中使用 updated 标记, 如下所示进行标记, 对应的字段可以为 time.time 或者自定义的 time.time 或者 int,int64 等 int 类型 type User struct { Id int64 Name string UpdatedAt time.time `xorm:"updated"` 在 Insert(), InsertOne(), Update() 方法被调用时,updated 标记的字段将会被自动更新为当前时间, 如下所示 : var user User engine.id(1).get(&user) // SELECT * FROM user WHERE id =? engine.id(1).update(&user) // UPDATE user SET..., updaetd_at =? WHERE id =? 如果你希望临时不自动插入时间, 则可以组合 NoAutoTime() 方法 : engine.noautotime().insert(&user) 这个在从一张表拷贝字段到另一张表时比较有用

86 执行 SQL 命令更新数据 执行 SQL 命令更新数据执行 SQL 命令更新数据 也可以直接执行一个 SQL 命令, 即执行 Update 操作 第 1 种方式 sql ="update user set age =? where name =?" res, err := engine.exec(sql, 1, "xorm") 第 2 种方式 sql_2 := "update user set age =? where name =?" affected, err := engine.sql(sql_2, 1, "xorm").execute() 第 3 种方式 //SqlMap 中 key 为 "sql_i_1" 配置的 Sql 语句为 :update user set age =? where name =? sql_i_1 := "sql_i_1" affected, err := engine.sqlmapclient(sql_i_1, 1, "xorm").execute() //SqlMap 中 key 为 "sql_i_2" 配置的 Sql 语句为 :update user set age =?age where name =?name sql_i_2 := "sql_i_2" parammap_i := map[string]interface{{"age": 1, "name": "xorm" affected, err := engine.sqlmapclient(sql_i_2, &parammap_i).execute() 第 4 种方式 sql_i_3 := "insert.example.stpl" parammap_i_t := map[string]interface{{"age": 1, "name": "xorm" affected, err := engine.sqltemplateclient(sql_i_3, &parammap_i_t).execute() 注 : 除以上 4 种方式外, 本库还支持另外 3 种方式, 由于这 3 种方式支持一次性批量混合 CRUD 操作, 返回多个结果集, 且支持多种参数组合形式, 内容较多, 场景比较复杂, 因此不在此处赘述 欲了解另外 3 种方式相关内容您可移步批量 SQL 操作章节, 此 4 种方式将在此章节单独说明

87 删除数据 删除数据 删除数据 支持执行 SQL 与 ORM 两种方式查询数据

88 ORM 方式删除数据 ORM 方式删除数据 ORM 方式删除数据 删除数据 Delete 方法

89 Delete 方法 Delete 方法 Delete 方法 删除数据 Delete 方法, 参数为 struct 的指针并且成为查询条件 user := new(user) affected, err := engine.id(id).delete(user) Delete 的返回值第一个参数为删除的记录数, 第二个参数为错误 注意 : 当删除时, 如果 user 中包含有 bool,float64 或者 float32 类型, 有可能会使删除失败 具体请查看 FAQ

90 软删除 Deleted 软删除 Deleted 软删除 Deleted Deleted 可以让您不真正的删除数据, 而是标记一个删除时间 使用此特性需要在 xorm 标记中使用 deleted 标记, 如下所示进行标记, 对应的字段必须为 time.time 类型 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) // 此时将可以真正的删除记录

91 执行 SQL 命令删除数据 执行 SQL 命令删除数据执行 SQL 命令删除数据 也可以直接执行一个 SQL 命令, 即执行 Delete 操作 第 1 种方式 sql ="delete from user where id =?" res, err := engine.exec(sql, 1) 第 2 种方式 sql_2 := "delete from user where id =?" affected, err := engine.sql(sql_2, 1).Execute() 第 3 种方式 //SqlMap 中 key 为 "sql_i_1" 配置的 Sql 语句为 :delete from user where id =? sql_i_1 := "sql_i_1" affected, err := engine.sqlmapclient(sql_i_1, 1).Execute() //SqlMap 中 key 为 "sql_i_2" 配置的 Sql 语句为 :delete from user where id =?id sql_i_2 := "sql_i_2" parammap_i := map[string]interface{{"id": 1 affected, err := engine.sqlmapclient(sql_i_2, &parammap_i).execute() 第 4 种方式 sql_i_3 := "insert.example.stpl" parammap_i_t := map[string]interface{{"id": 1 affected, err := engine.sqltemplateclient(sql_i_3, &parammap_i_t).execute() 注 : 除以上 4 种方式外, 本库还支持另外 3 种方式, 由于这 3 种方式支持一次性批量混合 CRUD 操作, 返回多个结果集, 且支持多种参数组合形式, 内容较多, 场景比较复杂, 因此不在此处赘述 欲了解另外 3 种方式相关内容您可移步批量 SQL 操作章节, 此 4 种方式将在此章节单独说明

92 事务处理 事务处理 事务处理 事务支持, 同时支持简单事务模型和嵌套事务模型 ( 支持类 JAVA Spring 的事务传播机制 )

93 简单事务模型 简单事务模型简单事务使用 本版本 xorm 同时支持简单事务模型和嵌套事务模型 大多数情况下我们可以使用简单事务模型来进行事务处理 当使用简单事务模型进行事务处理时, 需要创建 Session 对象, 另外当使用 Sql() SqlMapClient() SqlTemplateClient() 方法进行操作时也推荐手工创建 Session 对象方式管理 Session 在进行事物处理时, 可以混用 ORM 方法和 RAW 方法 注意如果您使用的是 mysql, 数据库引擎为 innodb 事务才有效,myisam 引擎是不支持事务的 示例代码如下 : session := engine.newsession() defer session.close() // add Begin() before any action err := session.begin() user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Crea ted: time.now() _, err = session.insert(&user1) if err!= nil { session.rollback() return user2 := Userinfo{Username: "yyy" _, err = session.where("id =?", 2).Update(&user2) if err!= nil { session.rollback() return _, err = session.exec("delete from userinfo where username =?", user2.username) if err!= nil { session.rollback() return // add Commit() after all actions err = session.commit() if err!= nil { return

94 嵌套事务模型 嵌套事务模型嵌套事务模型使用 上一章节我们介绍了, 简单事务模型的使用, 大多数场景使用简单事务模型即可满足我们的需求, 但实际业务开发过程中我们会发现依然还有一些特殊场景, 我们需要使用嵌套事务来进行事务处理 本 xorm 版本也提供了嵌套事务的支持 当使用嵌套事务模型进行事务处理时, 同样也需要创建 Session 对象, 与使用简单事务模型进行事务处理不同在于, 使用 session.begin() 创建简单事务时, 直接在同一个 session 下操作, 而使用嵌套事务模型进行事务处理时候, 使用 session.begintrans() 创建嵌套事务时, 将返回 Transaction 实例, 后续操作则在同一个 Transaction 实例下操作 在进行具体数据库操作时候, 则使用 tx.session() API 可以获得当前事务所持有的 session 会话, 从而进行 Get(),Find(),Execute() 等具体数据库操作 示例代码如下 : session := engine.newsession() defer session.close() // add BeginTrans() before any action tx, err := session.begintrans() if err!= nil { return user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Crea ted: time.now() _, err = tx.session().insert(&user1) if err!= nil { tx.rollback() return user2 := Userinfo{Username: "yyy" _, err = tx.session().where("id =?", 2).Update(&user2) if err!= nil { tx.rollbacktrans() return _, err = tx.session().exec("delete from userinfo where username =?", user2.use rname) if err!= nil { tx.rollbacktrans() return _, err = tx.session().sqlmapclient("delete.userinfo", user2.username).execute()

95 嵌套事务模型 if err!= nil { tx.rollbacktrans() return // add CommitTrans() after all actions err = tx.committrans() if err!= nil {... return

96 八种事务类型及事务传播机制 八种事务类型及事务传播机制 八种事务类型及事务传播机制 八种事务类型 事务类型 PROPAGATION_REQUIRED PROPAGATION_SUPPORTS PROPAGATION_MANDATORY PROPAGATION_REQUIRES_NEW PROPAGATION_NOT_SUPPORTED PROPAGATION_NEVER PROPAGATION_NESTED PROPAGATION_NOT_REQUIRED 说明 表示如果当前事务存在, 则支持当前事务 否则, 会启动一个新的事务 xorm 中默认事务类型 表示如果当前事务存在, 则支持当前事务, 如果当前没有事务, 就以非事务方式执行 表示如果当前事务存在, 则支持当前事务, 如果当前没有事务, 则返回事务嵌套错误 表示新建一个全新 Session 开启一个全新事务, 如果当前存在事务, 则把当前事务挂起 表示以非事务方式执行操作, 如果当前存在事务, 则新建一个 Session 以非事务方式执行操作, 把当前事务挂起 表示以非事务方式执行操作, 如果当前存在事务, 则返回事务嵌套错误 表示如果当前事务存在, 则在嵌套事务内执行, 如嵌套事务回滚, 则只会在嵌套事务内回滚, 不会影响当前事务 如果当前没有事务, 则进行与 PROPAGATION_REQUIRED 类似的操作 表示如果当前没有事务, 就新建一个事务, 否则返回错误 事务传播机制 1. PROPAGATION_REQUIRED PROPAGATION_REQUIRED 类型的事务, 如果当前 Session 中如果已经开启了数据库事务, 并且有程序在控制这个事务 那么在处理 REQUIRED 行为时将会忽略 commit/rollback 操作, 这是加入已有事务的特征 当前 Session 中如果没有事务那么会开启新一个新事务

97 八种事务类型及事务传播机制 时间 事务 1 事务 2 T1 开始事务 T2 操作 1... T3 加入事务 1 T4 操作 2... T5 操作 3... T6 递交事务 说明 : 事务 1 是当前 Session 中存在的事务, 事务 2 是们我们新建的 PROPAGATION_REQUIRED 类型的事务 2.PROPAGATION_SUPPORTS ROPAGATION_SUPPORTS 类型的事务和 PROPAGATION_REQUIRED 类型的事务的区别在于, 当前 Session 中如果没有事务那么 PROPAGATION_SUPPORTS 类型的事务会以非事务方式执行 3. PROPAGATION_MANDATORY PROPAGATION_MANDATORY 类型的事务和 PROPAGATION_REQUIRED 类型的事务的区别在于, 当前 Session 中如果没有事务那么 PROPAGATION_MANDATORY 类型的事务在开启时会返回事务嵌套错误 4. PROPAGATION_REQUIRES_NEW PROPAGATION_REQUIRES_NEW 类型的事务强调事务独立性 它保证了每个事务状态管理范围内锁使用的数据库连接是彼此不一样的 例如独立事务会满足, 事务 1 中存在事务 2, 当事务 2 递交的时候不影响事务 1 两个事务之间不存在相互关联关系 开启事务 2 时, 事务 1 会挂起, 等事务 2 提交完成后, 会恢复事务 1, 继续执行 当前 Session 中如果没有事务那么会开启新一个新事务 时间 事务 1 事务 2 T1 开始事务 T2 操作 1... T3 挂起事务 1 T4 开始事务 T5 操作 2... T6 递交事务 T7 恢复事务 1 T8 操作 3... T9 递交事务 说明 : 事务 1 是当前 Session 中存在的事务, 事务 2 是们我们新建的 PROPAGATION_REQUIRES_NEW 类型的事务 事务 1 和事务 2 存在于不同 Session 中, 事务执行结束后, 请注意在适当的位置关闭事务所存在的 Session 5. PROPAGATION_NOT_SUPPORTED PROPAGATION_NOT_SUPPORTED 类型的事务与 PROPAGATION_REQUIRES_NEW 类型事务的区别在于,

98 八种事务类型及事务传播机制 PROPAGATION_NOT_SUPPORTED 类型的事务在挂起当前 Session 中已经开启了的数据库事务之后不会在尝试开启新的事务 时间 事务 1 ( 开启事务 ) 事务 2( 非事务 ) T1 开始事务 T2 操作 1... T3 挂起事务 1 T4 操作 2... T4 操作 3... T5 恢复事务 1 T6 操作 4... T7 递交事务 说明 : 事务 1 和事务 2 存在于不同 Session 中, 事务执行结束后, 请注意在适当的位置关闭事务所存在的 Session 6. PROPAGATION_NEVER PROPAGATION_NEVER 类型的事务, 强调了非事务 一旦发现当前 Session 中已存在的事务, 则返回事务嵌套错误 7. PROPAGATION_NESTED PROPAGATION_NESTED 类型的事务, 如果当前 Session 中存在的事务, 则运行开启一个嵌套的事务, 创建一个回滚的保存点 如果当前 Session 没有活动事务, 则创建一个新事务 内部事务的回滚不会对外部事务造成影响 如外部事务回滚, 则内部嵌套事务一起回滚 时间 事务 1 事务 2 T1 开始事务 T2 操作 1... T3 创建事务保存点 T4 操作 2... T5 递交 / 回滚保存点 T6 操作 3... T7 递交事务 8. PROPAGATION_NOT_REQUIRED PROPAGATION_NOT_REQUIRED 类型事务与 PROPAGATION_REQUIRED 的事务正好相反, 如果当前 Session 中如果已经开启了数据库事务, 则返回错误, 否则, 会启动一个新的事务 该类型可以保障创建的事务只会在嵌套事务层级中是顶层事务, 否则则会返回错误

99 简单事务相关 API 简单事务相关 API 简单事务相关 API Begin() 创建一个事务 session.begin() Commit() 提交事务 session.commit() Rollback() 回滚事务 session.rollback()

100 嵌套事务相关 API 嵌套事务相关 API 嵌套事务相关 API BeginTrans(transactionDefinition...int) 创建一个事务 session.begintrans() // 默认创建事务类型为 PROPAGATION_REQUIRED session.begintrans(xorm.propagation_supports) // 创建事务类型为 PROPAGATION_SUPPOR TS CommitTrans() 提交事务 tx.committrans() RollbackTrans() 回滚事务 tx.rollbacktrans() SavePoint(savePointID string) 设置 SavePoint tx.savepoint("xorm_save") RollbackToSavePoint(savePointID string) 回滚至 SavePoint tx.rollbacktosavepoint("xorm_save") Session() 获得当前事务所在 Session, 以便进行后续数据库操作, 当 PROPAGATION_REQUIRES_NEW 或 PROPAGATION_NOT_SUPPORTED 类型事务时候, 都有可能创建新 Session 进行后续操作, 该函数可以获得这

101 嵌套事务相关 API 个新 Session 建议事务中都使用本函数获取 Session 进行后续操作 tx.session()

102 嵌套事务示例代码 嵌套事务示例代码 事务示例代码 func main(){ session := db.newsession() defer session.close() TX1(session) TX4(session) func TX1(session *Session){ tx1, err := session.begintrans(xorm.propagation_required) tx1.session().sql(sql).execute()... TX2(session) TX3(session) tx1.committrans() func TX2(session *Session){ tx2, err := session.begintrans(xorm.propagation_nested)... tx2.committrans() func TX3(session *Session){ tx3, err := session.begintrans(xorm.propagation_mandatory)... TX4(session) tx3.committrans() func TX4(session *Session){ tx4, err := session.begintrans(xorm.propagation_requires_new) // 注意这里事务所在的 Session 是一个全新 Session, 需要注意使用完毕后关闭该 Session defer tx4.session().close()... tx4.committrans() 注意 : 1 嵌套事务相关 API 在使用过程中注意关闭使用完的 Session, 特别是事务类型为 PROPAGATION_REQUIRES_NEW, 会新建一个全新 Session, 此全新 Session 在使用完后请特别注意不要忘记关

103 嵌套事务示例代码 闭 2 注意使用事务时对表的操作, 请合理使用 sql 操作, 不要造成锁表

104 主从数据库 (Master/Slave) 读写分离 主从数据库 (Master/Slave) 读写分离 主从数据库 (Master/Slave) 数据库读写分离 xorm 支持主从数据库 (Master/Slave) 读写分离

105 创建引擎组 创建引擎组创建引擎组 在 xorm 中, 通过创建引擎组 EngineGroup 来实现对从数据库 (Master/Slave) 读写分离的支持 在创建引擎章节中, 我们已经介绍过了, 在 xorm 里面, 可以同时存在多个 Orm 引擎, 一个 Orm 引擎称为 Engine, 一个 Engine 一般只对应一个数据库, 而 EngineGroup 一般则对应一组数据库 EngineGroup 通过调用 xorm.newenginegroup 生成, 如 : import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { conns := []string{ var err error eg, err = xorm.newenginegroup("postgres", conns) or import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { var err error master, err := xorm.newengine("postgres", st:5432/test?sslmode=disable") if err!= nil { return

106 创建引擎组 slave1, err := xorm.newengine("postgres", st:5432/test1?sslmode=disable") if err!= nil { return slave2, err := xorm.newengine("postgres", st:5432/test2?sslmode=disable") if err!= nil { return slaves := []*xorm.engine{slave1, slave2 eg, err = xorm.newenginegroup(master, slaves) 创建完成 EngineGroup 之后, 并没有立即连接数据库, 此时可以通过 eg.ping() 来进行数据库的连接测试是否可以连接到数据库, 该方法会依次调用引擎组中每个 Engine 的 Ping 方法 另外对于某些数据库有连接超时设置的, 可以通过起一个定期 Ping 的 Go 程来保持连接鲜活 EngineGroup 可以通过 eg.close() 来手动关闭, 但是一般情况下可以不用关闭, 在程序退出时会自动关闭 NewEngineGroup 方法 func NewEngineGroup(args1 interface{, args2 interface{, policies...grouppoli 前两个参数的使用示例如上, 有两种模式 模式一 : 通过给定 DriverName,DataSourceName 来创建引擎组, 每个引擎使用相同的 Driver 每个引擎的 DataSourceNames 是 []string 类型, 第一个元素是 Master 的 DataSourceName, 之后的元素是 Slave 的 DataSourceName 模式一 : 通过给定 *xorm.engine, []*xorm.engine 来创建引擎组, 每个引擎可以使用不同的 Driver 第一个参数为 Master 的 *xorm.engine, 第二个参数为 Slave 的 []*xorm.engine NewEngineGroup 方法, 第三个参数为 policies, 为 Slave 给定负载策略, 该参数将在负载策略章节详 细介绍, 如示例中未指定, 则默认为轮询负载策略 Master 方法 func (eg *EngineGroup) Master() *Engine 返回 Master 数据库引擎 Slave 方法 func (eg *EngineGroup) Slave() *Engine 依据给定的负载策略返回一个 Slave 数据库引擎 Slaves 方法 func (eg *EngineGroup) Slaves() []*Engine 返回所以 Slave 数据库引擎

107 创建引擎组 GetSlave 方法 func (eg *EngineGroup) GetSlave(i int) *Engine 依据一组 Slave 数据库引擎 []*xorm.engine 下标返回指定 Slave 数据库引擎 通过给定 DriverName, DataSourceName 来创建引擎组, 则 DataSourceName 的第二个元素的数据库为下标 0 的 Slave 数据库引擎 SetPolicy 方法 func (eg *EngineGroup) SetPolicy(policy GroupPolicy) *EngineGroup 设置引擎组负载策略

108 负载策略 负载策略负载策略 通过 xorm.newenginegroup 创建 EngineGroup 时, 第三个参数为 policies, 我们可以通过该参数来指定 Slave 访问的负载策略 如创建 EngineGroup 时未指定, 则默认使用轮询的负载策略 xorm 中内置五种负载策略, 分别为随机访问负载策略, 权重随机访问负载策略, 轮询访问负载策略, 权重轮询访问负载策略和最小连接数访问负载策略 开发者也可以通过实现 GroupPolicy 接口, 来实现自定义负载策略 1. 随机访问负载策略 import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { conns := []string{ var err error eg, err = xorm.newenginegroup("postgres", conns, xorm.randompolicy()) 2. 权重随机访问负载策略 import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { conns := []string{

109 负载策略 var err error // 此时设置的 test1 数据库和 test2 数据库的随机访问权重为 2 和 3 eg, err = xorm.newenginegroup("postgres", conns, xorm.weightrandompolicy([] int{2, 3)) 3. 轮询访问负载策略 import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { conns := []string{ var err error eg, err = xorm.newenginegroup("postgres", conns, xorm.roundrobinpolicy()) 4. 权重轮询访问负载策略 import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { conns := []string{ var err error // 此时设置的 test1 数据库和 test2 数据库的轮询访问权重为 2 和 3 eg, err = xorm.newenginegroup("postgres", conns, xorm.weightroundrobinpolicy ([]int{2, 3))

110 负载策略 5. 最小连接数访问负载策略 import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { conns := []string{ var err error eg, err = xorm.newenginegroup("postgres", conns, xorm.leastconnpolicy()) 6. 自定义负载策略 实现 GroupPolicy 接口来实现自定义负载策略 type GroupPolicy interface { Slave(*EngineGroup) *Engine

111 引擎组其他配置方法 引擎组其他配置方法 引擎组其他配置方法 日志 日志是一个接口, 通过设置日志, 可以显示 SQL, 警告以及错误等, 默认的显示级别为 INFO EngineGroup.ShowSQL(true), 则会在控制台打印出生成的 SQL 语句 ; EngineGroup.Logger().SetLevel(core.LOG_DEBUG), 则会在控制台打印调试及以上的信息 ; EngineGroup.ShowExecTime(true), 显示 SQL 语句执行时间 EngineGroup.SetLogger(), 如果希望将信息不仅打印到控制台, 而是保存为文件, 那么可以通过类似如下的代码实现,NewSimpleLogger(w io.writer) 接收一个 io.writer 接口来将数据写入到对应的设施中, 使用方法和 Engine 中的 SetLogger 方法一致 EngineGroup.RegisterSqlMap(), 注册 SqlMap 配置 EngineGroup.RegisterSqlTemplate(), 注册 SqlTemplate 配置 以上方法为引擎组的统一设置, 如果您希望引擎组的每个 engine 有自己的单独设置, 可以使用 EngineGroup.Master(), EngineGroup.GetSlave() 来获得具体数据库的 engine 来进行单独设置 连接池 EngineGroup 内部支持连接池接口和对应的函数 如果需要设置连接池的空闲数大小, 可以使用 EngineGroup.SetMaxIdleConns() 来实现 如果需要设置最大打开连接数, 则可以使用 EngineGroup.SetMaxOpenConns() 来实现 如果需要设置连接可以重用的最大时间, 则可以使用 EngineGroup.SetConnMaxLifetime() 来实现 以上方法为引擎组的统一设置, 如果您希望引擎组的每个 engine 有自己的单独设置, 可以使用 EngineGroup.Master(), EngineGroup.GetSlave() 来获得具体数据库的 engine 来进行单独设置 名称映射规则 如果需要设置名称映射规则, 可以使用 EngineGroup.SetMapper() 来实现, 使用方法和 Engine 中的 SetMapper() 一致 如果需要设置表名称映射规则, 可以使用 EngineGroup.SetTableMapper() 来实现, 使用方法和 Engine 中的 SetTableMapper() 一致 如果需要设置表字段名称映射规则, 可以使用 EngineGroup.SetColumnMapper() 来实现, 使用方法和 Engine 中的 SetColumnMapperr() 一致

112 引擎组其他配置方法 以上方法为引擎组的统一设置, 如果您希望引擎组的每个 engine 有自己的单独设置, 可以使用 EngineGroup.Master(), EngineGroup.GetSlave() 来获得具体数据库的 engine 来进行单独设置

113 数据库读写分离 数据库读写分离 数据库读写分离 创建好 EngineGroup 之后, 我们将可以对主从数据库 (Master/Slave) 进行 SQL 操作 例如 : import ( _ "github.com/lib/pq" "github.com/xormplus/xorm" ) var eg *xorm.enginegroup func main() { conns := []string{ var err error eg, err = xorm.newenginegroup("postgres", conns) if err!= nil { return user := new(user) user.name = "myname" affected, err := eg.insert(user) // INSERT INTO user (name) values (?) if err!= nil { return EngineGroup 对数据库的 SQL 操作方法集和 Engine 是完全一致的, 上例中则是对数据库进行 Insert 操作, 写操作 和数据库事务操作都将在 Master 数据库中执行 而读操作则是依据负载策略在某个 Slave 中执行 EngineGroup 的 Session EngineGroup 和 Engine 一样可以通过 NewSession() 方法来创建 EngineGroup 的 Session, 下例中, 在同一个 EngineGroup 的 Session 中, 执行写操作是在 Master 数据库中执行, 执行读操作时则在 Slave 数据库执行 xorm 并不推荐在同一个 EngineGroup 的 Session 中同时进行读写操作, 如需要, 请使用事务, 事务操作将全

114 数据库读写分离 部在 Master 数据库中执行读写操作 session := eg.newsession() defer session.close() user := new(user) user.name = "myname" affected, err := session.insert(user) // INSERT INTO user (name) values (?) if err!= nil { return _, err = session.exec("delete from userinfo where username =?", user2.username) if err!= nil { return sql := "select * from userinfo" results, err := engine.query(sql) if err!= nil { return _, err = session.exec("delete from userinfo where username =?", user2.username) if err!= nil { return

115 批量混合 SQL 操作 批量混合 SQL 操作批量混合 SQL 操作 批量混合 SQL 操作 API 在了解批量混合 SQL 操作 API 前, 请先了解一下 Sql 执行单元的定义 Sql 执行单元定义 当 sqls 为 string 时候, 则 Sql 执行单元为该字符串的内容当 sqlkeys 为 string 时, 则 Sql 执行单元为所对应的 SqlMap 配置项或 SqlTemplate 模板当 sqls 为 []string 或 map[string]string 时候, 则 Sql 执行单元为相关元素的字符串内容当 sqlkeys 为 []string 或 map[string]string 时候, 则 Sql 执行单元为以相关元素为 key 所对应的 SqlMap 配置项或 SqlTemplate 模板 Sql 执行单元的具体内容, 必须以 "select", "insert", "delete", "update", "create", "drop" 为起始内容, 但后续内容不会继续做检查, 请合理定义 Sql 执行单元内容 当执行单元内容不是以上起始内容, 则对应索引或 key 返回的结果集为 nil, 请注意对返回结果集的 nil 判断 Sql 执行单元并非单条 Sql 语句, 当执行 insert,delete,update,create,drop 操作时候, 可以为多条 Sql 语句, 这里需要对应的数据库的 SQL 语法支持 如在一个执行单元批量执行多条 Sql, 返回结果集作为一组所有执行单元的大结果集中的一个元素, 这个结果集的数据类型为 map[string]interface{, 只有 2 个键值对, 一个键值对的 key 为 LastInsertId, 一个键值对的 key 为 RowsAffected, 请控制好执行粒度 另外, 目前不是所有数据库都支持返回 LastInsertId, 目前还在设计更通用的 API 来实现所有数据库都能支持此功能 当执行 select 操作时候, 执行单元的 Sql 语句必须为一条, 返回结果集作为一组所有执行单元的大结果集中的一个元素 insert,delete,update,create,drop 操作不能和 select 操作混合定义在同一个执行单元中最后,Sql 执行单元基于以上约定, 请合理组织 Sqls(sqls, parmas...) 方法说明 : 1. sqls 参数 * sqls 参数数据类型为 interface{ 类型, 但实际参数类型检查时, 只支持 string,[]string 和 map[ string]string 三中类型, 使用其他类型均会返回参数类型错误 * 使用 string 类型则为执行单条 Sql 执行单元 ( 传送门 :[Sql 执行单元定义 ](#SQL)),Execute() 方法返回的结果集数据类型为 [][]map[string]interface{, 只有 1 个元素 * 使用 []string 则 Execute() 方法为有序执行多条 Sql 执行单元,Execute() 方法返回的结果集数据类型为 [][]map[string]interface{ 类型, 结果集元素个数与 sqls 参数的元素个数相同, 每个元素索引与返回结果集的索引一一对应 * 使用 map[string]string 类型则 Execute() 方法为无序执行多条 Sql 执行单元,Execute() 方法返回

116 批量混合 SQL 操作 的结果集数据类型为 map[string][]map[string]interface{, 结果集 map 的 key 与返回结果集的 key 一一对应 2. parmas... 参数 * 可以接收 0 个参数或则 1 个参数, 当所有执行单元都无需执行参数时候, 可以不传此参数 * parmas 参数数据类型为 interface{, 但实际参数类型检查时, 只支持 map[string]interface{, []map[string]interface{ 和 map[string]map[string]interface{ 三种类型, 使用其他类型均会返回参数类型错误 * 使用 map[string]interface{ 类型时候,sqls 参数类型必须为 string 类型, 即 map[string]inte rface{ 类型为单条 Sql 执行单元的参数 * 使用 []map[string]interface{ 类型时候,sqls 参数类型可以为 string 类型, 此时只有第一个元素 [0]map[string]interface{ 会被提取, 之后的元素将不起任何作用 同时,sqls 参数类型也可以为 [] string 类型, 这种参数组合是最常用的组合形式之一,sqls 参数的索引和 parmas 参数的索引一一对应 当某个索引所对应的 Sql 执行单元是无参数的时候, 请将此索引的值设为 nil, 即 parmas[i] = nil * 使用 map[string]map[string]interface{ 类型时,sqls 参数类型必须为 map[string]string 类型, 这种参数组合是最常用的组合形式之一,sqls 参数的 key 和 parmas 参数的 key 一一对应 当某个 key 所对应的 Sql 执行单元是无参数的时候, 请将此 key 的值设为 nil, 即 parmas[key] = nil SqlMapsClient(sqlkeys, parmas...) 方法说明 : 1. sqlkeys 参数 * sqlkeys 参数数据类型为 interface{ 类型, 但实际参数类型检查时, 只支持 string,[]string 和 m ap[string]string 三中类型, 使用其他类型均会返回参数类型错误 * 使用 string 类型则为执行单条 Sql 执行单元 (Sql 执行单元定义 ), 即在 xorm 种缓存的 SqlMap 中的 key 所对应的配置项,Execute() 方法返回的结果集数据类型为 [][]map[string]interface{, 只有 1 个元素 * 使用 []string 则 Execute() 方法为有序执行多条 Sql 执行单元,Execute() 方法返回的结果集数据类型为 [][]map[string]interface{ 类型, 结果集元素个数与 sqls 参数的元素个数相同,sqlkeys 的索引与返回结果集的索引一一对应,sqlkeys 存储的是每个元素的值是 xorm 缓存的 SqlMap 的 key * 使用 map[string]string 类型则 Execute() 方法为无序执行多条 Sql 执行单元,Execute() 方法返回的结果集数据类型为 map[string][]map[string]interface{,sqlkeys 的 key 与返回结果集的 key 一一对应,sqlkeys 存储的是每个键值对的值是 xorm 缓存的 SqlMap 的 key 2. parmas... 参数 * 可以接收 0 个参数或则 1 个参数, 当所有执行单元都无需执行参数时候, 可以不传此参数 * parmas 参数数据类型为 interface{, 但实际参数类型检查时, 只支持 map[string]interface{, []map[string]interface{ 和 map[string]map[string]interface{ 三种类型, 使用其他类型均会返回参数类型错误 * 使用 map[string]interface{ 类型时候,sqlkeys 参数类型必须为 string 类型, 即 map[string]i nterface{ 类型为单条 Sql 执行单元的参数 效果等同 SqlMapClient() 方法 ( 请注意本方法名为 SqlMa psclient) * 使用 []map[string]interface{ 类型时候,sqlkeys 参数类型支持两种 :

117 批量混合 SQL 操作 * 第 1 种为 string 类型, 此时只有第一个元素 [0]map[string]interface{ 会被提取, 之后的元素将不起任何作用 * 第 2 种为 []string 类型, 这种参数组合是最常用的组合形式之一,sqlkeys 参数的索引和 parmas 参数的索引一一对应 当某个索引所对应的 Sql 执行单元是无参数的时候, 请将此索引的值设为 nil, 即 par mas[i] = nil * 使用 map[string]map[string]interface{ 类型时,sqlkeys 参数类型必须为 map[string]stri ng 类型, 这种参数组合是最常用的组合形式之一,sqlkeys 参数的 key 和 parmas 参数的 key 一一对应 当某个 key 所对应的 Sql 执行单元是无参数的时候, 请将此 key 的值设为 nil, 即 parmas[key] = nil SqlTemplatesClient(sqlkeys, parmas...) 方法说明 : 1. sqlkeys 参数 * sqlkeys 参数数据类型为 interface{ 类型, 但实际参数类型检查时, 只支持 string,[]string 和 m ap[string]string 三中类型, 使用其他类型均会返回参数类型错误 * 使用 string 类型则为执行单条 Sql 执行单元 (Sql 执行单元定义 ), 即在 xorm 缓存的 SqlTemplate 中的 key 所对应的模板,Execute() 方法返回的结果集数据类型为 [][]map[string]interface{, 只有 1 个元素 * 使用 []string 则 Execute() 方法为有序执行多条 Sql 执行单元,Execute() 方法返回的结果集数据类型为 [][]map[string]interface{ 类型, 结果集元素个数与 sqls 参数的元素个数相同,sqlkeys 的索引与返回结果集的索引一一对应,sqlkeys 存储的是每个元素的值是 xorm 缓存的 SqlTemplate 的 key * 使用 map[string]string 类型则 Execute() 方法为无序执行多条 Sql 执行单元,Execute() 方法返回的结果集数据类型为 map[string][]map[string]interface{,sqlkeys 的 key 与返回结果集的 key 一一对应,sqlkeys 存储的是每个键值对的值是 xorm 缓存的 SqlTemplate 的 key 2. parmas... 参数 * 可以接收 0 个参数或则 1 个参数, 当所有执行单元都无需执行参数时候, 可以不传此参数 * parmas 参数数据类型为 interface{, 但实际参数类型检查时, 只支持 map[string]interface{, []map[string]interface{ 和 map[string]map[string]interface{ 三种类型, 使用其他类型均会返回参数类型错误 * 使用 map[string]interface{ 类型时候,sqlkeys 参数类型必须为 string 类型, 即 map[string]i nterface{ 类型为单条 Sql 执行单元的参数 效果等同 SqlMapClient() 方法 ( 请注意本方法名为 SqlMa psclient) * 使用 []map[string]interface{ 类型时候,sqlkeys 参数类型支持两种 : * 第 1 种为 string 类型, 此时只有第一个元素 [0]map[string]interface{ 会被提取, 之后的元素将不起任何作用 ; * 第 2 种为 []string 类型, 这种参数组合是最常用的组合形式之一,sqlkeys 参数的索引和 parmas 参数的索引一一对应 当某个索引所对应的 Sql 执行单元是无参数的时候, 请将此索引的值设为 nil, 即 par mas[i] = nil * 使用 map[string]map[string]interface{ 类型时,sqlkeys 参数类型必须为 map[string]stri ng 类型, 这种参数组合是最常用的组合形式之一,sqlkeys 参数的 key 和 parmas 参数的 key 一一对应 当某个 key 所对应的 Sql 执行单元是无参数的时候, 请将此 key 的值设为 nil, 即 parmas[key] = nil Execute() 方法说明 :

118 批量混合 SQL 操作 一共 3 个返回值,([][]map[string]interface{, map[string][]map[string]interface{, error) 当以上 3 个方法的 sqls 或 sqlkeys 参数为 string 或 []string 时为有序执行 Sql 执行单元, 故返回结果集为第一个返回值,Slice 存储, 第二返回值为 nil 当以上 3 个方法的 sqls 或 sqlkeys 参数为 map[string]string 时为无序执行 Sql 执行单元, 返回结果集为第二个返回值,map 存储, 第一个返回值为 nil 当以上 3 个方法执行中出现错误, 则第三个返回值有值, 前 2 个返回值均为 nil // 第一种方式, 可以从 Engine 对象轻松进行使用, 该方式自动管理事务, 注意如果您使用的是 mysql, 数据库引擎为 innodb 事务才有效,myisam 引擎是不支持事务的 engine.sqls(sqls, parmas...).execute() engine.sqlmapsclient(sqlkeys, parmas...).execute() engine.sqltemplatesclient(sqlkeys, parmas...).execute() // 第 2 种方式, 手动创建 Session 对象进行调用, 该方式需要您手动管理事务 session := engine.newsession() defer session.close() // add Begin() before any action tx,err := session.begintrans() _, err = tx.session().exec("delete from userinfo where username =?", user2.use rname) if err!= nil { tx.rollbacktrans() return //Execuet 返回值有 3 个, 分别为 slice,map,error 类型 results, _, err = tx.session().sqls(sqls, parmas...).execute() if err!= nil { tx.rollbacktrans() return _, results, err = tx.session().sqlmapsclient(sqlkeys, parmas...).execute() if err!= nil { tx.rollbacktrans() return results, _, err = tx.session().sqltemplatesclient(sqlkeys, parmas...).execute() if err!= nil { tx.rollbacktrans() return

119 批量混合 SQL 操作 // add Commit() after all actions err = tx.committrans() if err!= nil { return // 支持两种返回结果集 //Slice 形式类似如下 /* [ [ { "id": "6", "name": "xorm", { "id": "7", "name": "xormplus", { "id": "8", "name": "ibatis" ], [ { "LastInsertId": 0, "RowsAffected": 0 ], [ { "LastInsertId": 0, "RowsAffected": 0 ], [ { "LastInsertId": 13, "RowsAffected": 1 ] ] */ //Map 形式类似如下 /* { "deleteuser": [ {

120 批量混合 SQL 操作 */ "LastInsertId": 0, "RowsAffected": 0 ], "insertuser": [ { "LastInsertId": 11, "RowsAffected": 1 ], "updateuser": [ { "LastInsertId": 0, "RowsAffected": 0 ], "userlist": [ { "id": "3", "name": "xorm", { "id": "4", "name": "xormplus", ]

121 SQL Builder SQL Builder SQL Builder SQL Builder 使用样例 原版 xorm 提供了一个 SQL Builder 库 github.com/go-xorm/builder 用来支持 SQL Builder 功能, 本定制版本也 支持该 SQL Builder 库, 使用样例如下, 详细文档请参阅 github.com/go-xorm/builder 相关文档 err := engine.where(builder.notin("a", 1, 2).And(builder.In("b", "c", "d", "e")) ).Find(&users) // SELECT id, name... FROM user WHERE a NOT IN (?,?) AND b IN (?,?,?) 当然, 如果您喜欢 SQL Builder 方式组装 SQL, 您也可以使用其他的第三方 SQL Builder 库来完成 SQL Builder 操 作组装 SQL 语句交由 xorm 执行 推荐如下第三方 SQL Builder 库 : github.com/doug-martin/goqu github.com/shuoli84/sqlm github.com/doug-martin/goqu github.com/masterminds/squirrel github.com/elgris/sqrl 此外,github.com/elgris/golang-sql-builder-benchmark 这个 github 库还提供了部分 SQL Builder 的 benchmark, 大家可以依据自己的喜好和性能考量等多方面因素自由选用

122 缓存 缓存缓存 xorm 内置了一致性缓存支持, 不过默认并没有开启 要开启缓存, 需要在 engine 创建完后进行配置, 如 : 启用一个全局的内存缓存 cacher := xorm.newlrucacher(xorm.newmemorystore(), 1000) engine.setdefaultcacher(cacher) 上述代码采用了 LRU 算法的一个缓存, 缓存方式是存放到内存中, 缓存 struct 的记录数为 1000 条, 缓存针对的范 围是所有具有主键的表, 没有主键的表中的数据将不会被缓存 如果只想针对部分表, 则 : cacher := xorm.newlrucacher(xorm.newmemorystore(), 1000) engine.mapcacher(&user, cacher) 如果要禁用某个表的缓存, 则 : engine.mapcacher(&user, nil) 设置完之后, 其它代码基本上就不需要改动了, 缓存系统已经在后台运行 当前实现了内存存储的 CacheStore 接口 MemoryStore, 如果需要采用其它设备存储, 可以实现 CacheStore 接 口 1. 不过需要特别注意不适用缓存或者需要手动编码的地方 : 2. 当使用了 Distinct,Having,GroupBy 方法将不会使用缓存 3. 在 Get 或者 Find 时使用了 Cols,Omit 方法, 则在开启缓存后此方法无效, 系统仍旧会取出这个表中的所有字段 由于存在 ORM 和 RAW 两种方式操作数据库, 故在使用 Exec 方法执行了方法之后 (RAW 方式 ), 可能会导致缓存 与数据库不一致的地方 因此如果启用缓存, 尽量避免使用类 Exec 的方法 如果必须使用, 则需要在使用了 Exec 之后调用 ClearCache 手动做缓存清除的工作, 或则在您在您的系统内部自行实现系统级的缓存 比如 : engine.exec("update user set name =? where id =?", "xlw", 1) engine.clearcache(new(user))

123 事件 事件事件 xorm 支持两种方式的事件, 一种是在 Struct 中的特定方法来作为事件的方法, 一种是在执行语句的过程中执行事件 在 Struct 中作为成员方法的事件如下 : BeforeInsert() 在将此 struct 插入到数据库之前执行 BeforeUpdate() 在将此 struct 更新到数据库之前执行 BeforeDelete() 在将此 struct 对应的条件数据从数据库删除之前执行 func BeforeSet(name string, cell xorm.cell) 在 Get 或 Find 方法中, 当数据已经从数据库查询出来, 而在设置到结构体之前调用,name 为数据库字段名 称,cell 为数据库中的字段值 func AfterSet(name string, cell xorm.cell) 在 Get 或 Find 方法中, 当数据已经从数据库查询出来, 而在设置到结构体之后调用,name 为数据库字段名 称,cell 为数据库中的字段值 AfterInsert() 在将此 struct 成功插入到数据库之后执行 AfterUpdate() 在将此 struct 成功更新到数据库之后执行 AfterDelete() 在将此 struct 对应的条件数据成功从数据库删除之后执行 在语句执行过程中的事件方法为 : Before(beforeFunc interface{)

124 事件 临时执行某个方法之前执行 before := func(bean interface{){ fmt.println("before", bean) engine.before(before).insert(&obj) After(afterFunc interface{) 临时执行某个方法之后执行 after := func(bean interface{){ fmt.println("after", bean) engine.after(after).insert(&obj) 其中 beforefunc 和 afterfunc 的原型为 func(bean interface{)

125 数据导出 数据导出 数据导出 xorm 提供了多种数据导出方式, 支持多种文件格式的数据导出

126 Dump 数据库结构和数据 Dump 数据库结构和数据 Dump 数据库结构和数据 如果需要在程序中 Dump 数据库的结构和数据可以调用 engine.dumpall(w io.writer) 和 engine.dumpallfile(fpath string) DumpAll 方法接收一个 io.writer 接口来保存 Dump 出的数据库结构和数据的 SQL 语句, 这个方法导出的 SQL 语句并不能通用 只针对当前 engine 所对应的数据库支持的 SQL Import 执行数据库 SQL 脚本 如果你需要将保存在文件或者其它存储设施中的 SQL 脚本执行, 那么可以调用 engine.import(r io.reader) 和 engine.importfile(fpath string) 同样, 这里需要对应的数据库的 SQL 语法支持

127 查询结果集导出 csv tsv xml json xlsx yaml html 查询结果集导出 csv tsv xml json xlsx yaml html 查询结果集导出 csv tsv xml json xlsx yaml html xorm 查询结果集支持导出 csv tsv xml json xlsx yaml html 七种文件格式 以导出 xlsx 文件格式为例, 代码如下 err := engine.sql("select * from category").query().saveasxlsx("1.xlsx", []stri ng{"id", "name", "counts", "orders", "createtime", "pid", "lastupdatetime", "st atus", 0777) if err!= nil { t.fatal(err) SaveAsCSV(filename string, headers []string, perm os.filemode) 导出 CSV 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确定 列的顺序,perm 为文件权限位 SaveAsTSV(filename string, headers []string, perm os.filemode) 导出 TSV 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确定 列的顺序,perm 为文件权限位 SaveAsHTML(filename string, headers []string, perm os.filemode) 导出 HTML 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 SaveAsXML(filename string, headers []string, perm os.filemode) 导出 XML 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确定 列的顺序,perm 为文件权限位 SaveAsXMLWithTagNamePrefixIndent(tagName string, prifix string, indent string, filename string, headers []string, perm os.filemode)

128 查询结果集导出 csv tsv xml json xlsx yaml html 导出指定格式化的 XML 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确定列的顺序,perm 为文件权限位 SaveAsYAML(filename string, headers []string, perm os.filemode) 导出 YAML 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 SaveAsJSON(filename string, headers []string, perm os.filemode) 导出 JSON 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 SaveAsXLSX(filename string, headers []string, perm os.filemode) 导出 XLSX 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 注意 : 当某列值有可能为 null 的时候, 需要使用各数据库类似 IFNULL 函数查询取值 由于查询出来的结果集是 []map[string]interface 类型,NULL 字段将不会在 map 中保有 key

129 多 Sheet 页数据导出 多 Sheet 页数据导出多 Sheet 页数据导出 上一章介绍了单一查询结果集导出到单一文件的相关 API 但有时我们也会遇到多个结果集需要导出到单一文件的需求,xorm 同样提供了相关 API 来支持该类需求 样例代码如下 : _, results, err := engine.sqls(map[string]string{"category": "select * from cat egory", "category-16-17": "select * from category where id in (16,17)").Execute () if err!= nil { t.fatal(err) databook, err := xorm.newdatabookwithdata( map[string]string{ "category": "category", "category-16-17": "category-16-17", results, true, map[string][]string{ "category": []string{"id", "name", "counts", "orders", "createtim e", "pid", "lastupdatetime", "status", "category-16-17": []string{"id", "name", "counts", "orders", "createtim e", "pid", "lastupdatetime", "status") if err!= nil { t.fatal(err) err = databook.saveasxlsx("c:/2.xlsx", 0777) if err!= nil { t.fatal(err) err = databook.saveashtml("c:/2.html", 0777) if err!= nil { t.fatal(err) err = databook.saveasjson("c:/2.json", 0777) if err!= nil { t.fatal(err) err = databook.saveasxml("c:/2.xml", 0777) if err!= nil {

130 多 Sheet 页数据导出 t.fatal(err) err = databook.saveasyaml("c:/2.yaml", 0777) if err!= nil { t.fatal(err) 相关 API 介绍 NewDatabook() 新建一个 Databook NewDatabookWithData(sheetName map[string]string, data interface{, mustmatch bool, headers...map[string][]string) 依据数据结果集新建一个 Databook 1. sheetname, 数据类型为 map,key 与 data,headers 的 key 保持一致,value 则为每个 sheet 页的名称 2. data, 数据类型为 interface{, 但实际只允许接收 map[string]*tablib.dataset, 和 map[string] []map[string]interface{ 两种数据类型 其中 key 与 sheetname,headers 保持一致 3. mustmatch, 是否检查字段个数与数据匹配 4. headers, 数据类型为...map[string][]string, 当 data 传入数据类型为 map[string]*tablib.dataset, 则此参数可以省略不传, 即使传入也不起作用 当 data 传入数据类型为 map[string][]map[string]interface{ 时, headers 的 key 则与 sheetname,data 保持一致,value 为 []string 类型, 用于确定该 sheet 输出的字段顺序 headers 参数个数最多为 1, 大于 1, 则参数格式错误 以上 2 个方法返回对象为 type Databook struct { XDatabook *tablib.databook Databook 具有如下方法 AddSheet(title string, data interface{, headers...[]string) 新增一个 sheet 1. title, 数据类型为 string, 新增 sheet 的名字 2. data, 新增 sheet 的数据, 数据类型为 interface{, 但实际只允许接收 *tablib.dataset 和 []map[string]interface{ 两种数据类型

131 多 Sheet 页数据导出 3. headers, 数据类型为 []string 类型, 当 data 传入参数数据类型为 *tablib.dataset, 该参数可以省略, 即使传 入也不起作用 当 data 传入数据类型为 []map[string]interface{, 则 headers 必须传入一个 []string 类型参 数, 用于确定该 sheet 输出的字段顺序 headers 参数个数最多为 1, 大于 1, 则参数格式错误 SaveAsHTML(filename string, perm os.filemode) 导出 HTML 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 SaveAsJSON(filename string, perm os.filemode) 导出 JSON 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 SaveAsXLSX(filename string, perm os.filemode) 导出 XLSX 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 SaveAsXML(filename string, perm os.filemode) 导出 XML 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确定 列的顺序,perm 为文件权限位 SaveAsYAML(filename string, perm os.filemode) 导出 YAML 文件,filename 为完整路径,headers 为每列的列名, 需要结果集中有该字段, 此处 []string 是为了确 定列的顺序,perm 为文件权限位 Sheet(title string) 获取指定名称的 sheet Sheets() 获取 Databook 中的所有 sheet, 数据类型为 map[string]tablib.sheet Size() 返回 Databook 中 sheet 个数 Wipe() 清空 Databook 中的所有 sheet

132 日志 日志 日志 日志是一个接口, 通过设置日志, 可以显示 SQL, 警告以及错误等, 默认的显示级别为 INFO engine.showsql(true), 则会在控制台打印出生成的 SQL 语句 ; engine.logger().setlevel(core.log_debug), 则会在控制台打印调试及以上的信息 ; 如果希望将信息不仅打印到控制台, 而是保存为文件, 那么可以通过类似如下的代码实现, NewSimpleLogger(w io.writer) 接收一个 io.writer 接口来将数据写入到对应的设施中 f, err := os.create("sql.log") if err!= nil { println(err.error()) return engine.setlogger(xorm.newsimplelogger(f)) 当然, 如果希望将日志记录到 syslog 中, 也可以如下 : logwriter, err := syslog.new(syslog.log_debug, "rest-xorm-example") if err!= nil { log.fatalf("fail to create xorm system logger: %v\n", err) logger := xorm.newsimplelogger(logwriter) logger.showsql(true) engine.setlogger(logger)

133 连接池 连接池 连接池 engine 内部支持连接池接口和对应的函数 如果需要设置连接池的空闲数大小, 可以使用 engine.setmaxidleconns() 来实现 如果需要设置最大打开连接数, 则可以使用 engine.setmaxopenconns() 来实现

134 xorm 工具 xorm 工具 xorm 工具 github.com/go-xorm/cmd 是一组数据库操作命令行工具 [ 传送门 ] 有如下可用的命令 : reverse 反转一个数据库结构, 生成代码 shell 通用的数据库操作客户端, 可对数据库结构和数据操作 dump Dump 数据库中所有结构和数据到标准输出 source 从标注输入中执行 SQL 文件 driver 列出所有支持的数据库驱动 github.com/xormplus/tools 是一个批量加密 xorm SqlMap 配置文件和 SqlTemplate 模板的 GUI 工具 [ 传送门 ]

135 常见问题 常见问题 常见问题 如何使用 Like? 答 : engine.where("column like?", "%"+char+"%").find 怎么同时使用 xorm 的 tag 和 json 的 tag? 答 : 使用空格 type User struct { Name string `json:"name" xorm:"name"` 我的 struct 里面包含 bool 类型, 为什么它不能作为条件也没法用 Update 更新? 答 : 默认 bool 类型因为无法判断是否为空, 所以不会自动作为条件也不会作为 Update 的内容 可以使用 UseBool 函数, 也可以使用 Cols 函数 engine.cols("bool_field").update(&struct{boolfield:true) // UPDATE struct SET bool_field = true 我的 struct 里面包含 float64 和 float32 类型, 为什么用他们作为查询条件总是不正确? 答 : 默认 float32 和 float64 映射到数据库中为 float,real,double 这几种类型, 这几种数据库类型数据库的实现一般 都是非精确的 因此作为相等条件查询有可能不会返回正确的结果 如果一定要作为查询条件, 请将数据库中的 类型定义为 Numeric 或者 Decimal type account struct { money float64 `xorm:"numeric"` 为什么 Update 时 Sqlite3 返回的 affected 和其它数据库不一样? 答 :Sqlite3 默认 Update 时返回的是 update 的查询条件的记录数条数, 不管记录是否真的有更新 而 Mysql 和 Postgres 默认情况下都是只返回记录中有字段改变的记录数

136 常见问题 xorm 有几种命名映射规则? 答 : 目前支持 SnakeMapper, SameMapper 和 GonicMapper 三种 SnakeMapper 支持结构体和成员以驼峰式命名而数据库表和字段以下划线连接命名 ;SameMapper 支持结构体和数据库的命名保持一致的映射 GonicMapper 在 SnakeMapper 的基础上对一些特定名词, 比如 ID 的映射会映射为 id, 而不是像 SnakeMapper 那样为 i_d xorm 支持复合主键吗? 答 : 支持 在定义时, 如果有多个字段标记了 pk, 则这些字段自动成为复合主键, 顺序为在 struct 中出现的顺 序 在使用 Id 方法时, 可以用 Id(xorm.PK{1, 2) 的方式来用 xorm 如何使用 Join? 答 : 一般我们配合 Join() 和 extends 标记来进行, 比如我们要对两个表进行 Join 操作, 我们可以这样 : type Userinfo struct { Id int64 Name string DetailId int64 type Userdetail struct { Id int64 Gender int type User struct { Userinfo `xorm:"extends"` Userdetail `xorm:"extends"` var users = make([]user, 0) err := engine.table(&userinfo{).join("left", "userdetail", "userinfo.detail_id = userdetail.id").find(&users) 请注意这里的 Userinfo 在 User 中的位置必须在 Userdetail 的前面, 因为他在 join 语句中的顺序在 userdetail 前面 如果顺序不对, 那么对于同名的列, 有可能会赋值出错 当然, 如果 Join 语句比较复杂, 我们也可以直接用 Sql 函数 err := engine.sql("select * from userinfo, userdetail where userinfo.detail_id = userdetail.id").find(&users) 如果有自动增长的字段,Insert 如何写? 答 :Insert 时, 如果需要自增字段填充为自动增长的数值, 请保持自增

137 常见问题 字段为 0; 如果自增字段为非 0, 自增字段将会被作为普通字段插入 如何设置数据库时区? 答 : location, err = time.loadlocation("asia/shanghai") engine.tzlocation = location

138 感谢支持 感谢支持感谢支持 如果这项目对您有很大帮助, 您愿意支持这个项目的进一步开发和这个项目的持续维护 你可以扫描下面的二维码, 让我喝一杯咖啡或豆奶 非常感谢您的捐赠, 感谢您对开源项目的支持, 谢谢 ( 支付宝 )