xorm/docs/QuickStart.md

18 KiB
Raw Permalink Blame History

xorm 快速入门

1.创建Orm引擎

在xorm里面可以同时存在多个Orm引擎一个Orm引擎称为Engine。因此在使用前必须调用NewEngine

import (
	_ "github.com/Go-SQL-Driver/MySQL"
	"github.com/lunny/xorm"
)
engine, err := xorm.NewEngine("mysql", "root:123@/test?charset=utf8")
defer engine.Close()

or

import (
	_ "github.com/mattn/go-sqlite3"
	"github.com/lunny/xorm"
	)
engine, err = xorm.NewEngine("sqlite3", "./test.db")
defer engine.Close()

一般如果只针对一个数据库进行操作只需要创建一个Engine即可。Engine支持在多GoRutine下使用。

xorm当前支持四种驱动如下

NewEngine传入的参数和sql.Open传入的参数完全相同,因此,使用哪个驱动前,请查看此驱动中关于传入参数的说明文档。

在engine创建完成后可以进行一些设置

1.设置engine.ShowSQL = true则会在控制台打印出生成的SQL语句如果希望用其它方式记录则可以engine.Logger赋值为一个io.Writer的实现。比如记录到Log文件则可以

f, err := os.Create("sql.log")
	if err != nil {
		println(err.Error())
		return
	}
engine.Logger = f

2.engine内部支持连接池接口默认使用的Go所实现的连接池同时自带了另外两种实现一种是不使用连接池另一种为一个自实现的连接池。推荐使用Go所实现的连接池。如果要使用自己实现的连接池可以实现xorm.IConnectPool并通过engine.SetPool进行设置。 如果需要设置连接池的空闲数大小,可以使用engine.Pool.SetIdleConns()来实现。

3.设置engine.ShowDebug = true,则会在控制台打印调试信息。

2.定义表结构体

xorm支持将一个struct映射为数据库中对应的一张表。映射规则如下

2.1.名称映射规则

名称映射规则主要负责结构体名称到表名和结构体field到表字段的名称映射。由xorm.IMapper接口的实现者来管理xorm内置了两种IMapper实现SnakeMapperSameMapper。SnakeMapper支持struct为驼峰式命名表结构为下划线命名之间的转换SameMapper支持相同的命名。当前SnakeMapper为默认值当需要改变时在engine创建完成后使用

engine.Mapper = SameMapper{}

当然如果你使用了别的命名规则映射方案也可以自己实现一个IMapper。

2.2.使用Table和Tag改变名称映射

如果所有的命名都是按照IMapper的映射来操作的那当然是最理想的。但是如果碰到某个表名或者某个字段名跟映射规则不匹配时我们就需要别的机制来改变。

通过engine.Table()方法可以改变struct对应的数据库表的名称通过sturct中field对应的Tag中使用xorm:"'table_name'"可以使该field对应的Column名称为指定名称。这里使用两个单引号将Column名称括起来是为了防止名称冲突因为我们在Tag中还可以对这个Column进行更多的定义。如果名称不冲突的情况单引号也可以不使用。

2.3.Column属性定义

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

type User struct {
	Id 	 int64
	Name string  `xorm:"varchar(25) not null unique 'usr_name'"`
}

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

具体的映射规则如下另Tag中的关键字均不区分大小写字段名区分大小写

name当前field对应的字段的名称可选如不写则自动根据field名字和转换规则命名
pk是否是Primary Key当前仅支持int64类型
当前支持30多种字段类型详情参见 [字段类型](https://github.com/lunny/xorm/blob/master/docs/COLUMNTYPE.md)字段类型
autoincr是否是自增
[not ]null是否可以为空
unique或unique(uniquename)是否是唯一如不加括号则该字段不允许重复如加上括号则括号中为联合唯一索引的名字此时如果有另外一个或多个字段和本unique的uniquename相同则这些uniquename相同的字段组成联合唯一索引
index或index(indexname)是否是索引如不加括号则该字段自身为索引如加上括号则括号中为联合索引的名字此时如果有另外一个或多个字段和本index的indexname相同则这些indexname相同的字段组成联合索引
extends应用于一个匿名结构体之上,表示此匿名结构体的成员也映射到数据库中
-这个Field将不进行字段映射
->这个Field将只写入到数据库而不从数据库读取
<-这个Field将只从数据库读取而不写入到数据库
created这个Field将在Insert时自动赋值为当前时间
updated这个Field将在Insert或Update时自动赋值为当前时间
default 0设置默认值紧跟的内容如果是Varchar等需要加上单引号

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

  • 1.如果field名称为Id而且类型为int64的话会被xorm视为主键并且拥有自增属性。如果想用Id以外的名字做为主键名可以在对应的Tag上加上xorm:"pk"来定义主键。

  • 2.string类型默认映射为varchar(255)如果需要不同的定义可以在tag中自定义

  • 3.支持type MyString string等自定义的field支持Slice, Map等field成员这些成员默认存储为Text类型并且默认将使用Json格式来序列化和反序列化。也支持数据库字段类型为Blob类型如果是Blob类型则先使用Jsong格式序列化再转成[]byte格式。当然[]byte或者[]uint8默认为Blob类型并且都已二进制方式存储。

  • 4.实现了Conversion接口的类型或者结构体将根据接口的转换方式在类型和数据库记录之间进行相互转换。

type Conversion interface {
	FromDB([]byte) error
	ToDB() ([]byte, error)
}

3.创建表

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

在创建表时会判断表是否已经创建如果已经创建则不再创建。在创建表的过程中如果在tag中定义了索引则索引也会自动创建。

3.1.同步数据库结构

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

  1. 自动检测和创建表 2自动检测和新增表中的字段 3自动检测和创建索引和唯一索引

调用方法如下:

err := engine.Sync(new(User))

4.删除表

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

5.插入数据

插入数据使用Insert方法Insert方法的参数可以是一个或多个Struct的指针一个或多个Struct的Slice的指针。 如果传入的是Slice并且当数据库支持批量插入时Insert会使用批量插入的方式进行插入。

user := new(User)
user.Name = "myname"
affcted, err := engine.Insert(user)

在插入成功后如果该结构体有PK字段则PK字段会被自动赋值为数据库中的id

fmt.Println(user.Id)

6.查询和统计数据

所有的查询条件不区分调用顺序但必须在调用GetFindCount这三个函数之前调用。同时需要注意的一点是在调用的参数中所有的字符字段名均为映射后的数据库的字段名而不是field的名字。

6.1.查询条件方法

查询和统计主要使用Get, Find, Count三个方法。在进行查询时可以使用多个方法来形成查询条件,条件函数如下:

  • Id(int64) 传入一个PK字段的值作为查询条件

  • Where(string, …interface{}) 和Where语句中的条件基本相同作为条件

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

engine.Cols("age, name").Update(&user)

or

engine.Cols("age", "name").Update(&user)
  • Sql(string, …interface{}) 执行指定的Sql语句并把结果映射到结构体

  • Table() 指定特殊的Table名如不加此函数则根据系统的IMapper自动映射的表名进行查询

  • Asc(…string) 指定字段名正序排序

  • Desc(…string) 指定字段名逆序排序

  • OrderBy() 按照指定的顺序进行排序

  • NoAutoTime() 如果此方法执行则此次生成的语句中Created和Updated字段将不自动赋值为当前时间

  • In(string, …interface{}) 某字段在一些值中

  • Table(nameOrStructPtr interface{}) 传入表名称或者结构体指针如果传入的是结构体指针则按照IMapper的规则提取出表名

  • Limit(int, …int) 限制获取的数目,第一个参数为条数,第二个参数为可选,表示开始位置

  • Join(string,string,string) 第一个参数为连接类型当前支持INNER, LEFT OUTER, CROSS中的一个值第二个参数为表名第三个参数为连接条件

  • GroupBy(string) Groupby的参数字符串

  • Having(string) Having的参数字符串

  • Cascade(bool) 是否自动关联查询field中的数据如果struct的field也是一个struct并且映射为某个Id则可以在查询时自动调用Get方法查询出对应的数据。

6.2.Get方法

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

如:

  1. 根据Id来获得单条数据:
user := new(User)
has, err := engine.Id(id).Get(user)
  1. 根据Where来获得单条数据
user := new(User)
has, err := engine.Where("name=?", "xlw").Get(user)
  1. 根据user结构体中已有的非空数据来获得单条数据
user := &User{Id:1}
has, err := engine.Get(user)

或者其它条件

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

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

6.3.Find方法

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

  1. 传入Slice用于返回数据
var everyone []Userinfo
err := engine.Find(&everyone)
  1. 传入Map用户返回数据map必须为map[int64]Userinfo的形式map的key为id
users := make(map[int64]Userinfo)
err := engine.Find(&users)
  1. 也可以加入条件
users := make([]Userinfo, 0)
err := engine.Where("age > ? or name=?)", 30, "xlw").Limit(20, 10).Find(&users)

6.4.Count方法

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

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

<a name="65" id-"65">

6.5.匿名结构体成员

如果在struct中拥有一个struct并且在Tag中标记为extends那么该结构体的成员将作为本结构体的成员进行映射。

请查看Examples中的derive.go文件。

7.更新数据

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

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

8.删除数据

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

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

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

9.执行SQL查询

也可以直接执行一个SQL查询即Select命令。在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。

sql := "select * from userinfo"
results, err := engine.Query(sql)

10.执行SQL命令

也可以直接执行一个SQL命令即执行Insert Update Delete 等操作。同样在Postgres中支持原始SQL语句中使用 ` 和 ? 符号。

sql = "update userinfo set username=? where id=?"
res, err := engine.Exec(sql, "xiaolun", 1) 

11.事务处理

当使用事务处理时需要创建Session对象。

// add Begin() before any action
err := session.Begin()	
user1 := Userinfo{Username: "xiaoxiao", Departname: "dev", Alias: "lunny", Created: 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
}

12.缓存

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. 在Get或者Find时使用了Cols方法在开启缓存后此方法无效系统仍旧会取出这个表中的所有字段。

  2. 在使用Exec方法执行了方法之后可能会导致缓存与数据库不一致的地方。因此如果启用缓存尽量避免使用Exec。如果必须使用则需要在使用了Exec之后调用ClearCache手动做缓存清除的工作。比如

engine.Exec("update user set name = ? where id = ?", "xlw", 1)
engine.ClearCache(new(User))

缓存的实现原理如下图所示:

cache design

12.Examples

请访问https://github.com/lunny/xorm/tree/master/examples

13.案例

14.讨论

请加入QQ群280360085 进行讨论。