2017-12-29 16:03:30 +08:00
|
|
|
|
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
|
|
|
|
|
//
|
|
|
|
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
|
|
|
|
// If a copy of the MIT was not distributed with this file,
|
|
|
|
|
|
// You can obtain one at https://gitee.com/johng/gf.
|
2017-12-31 18:19:58 +08:00
|
|
|
|
|
2018-04-18 18:05:46 +08:00
|
|
|
|
// 数据库ORM.
|
2018-08-08 20:09:52 +08:00
|
|
|
|
// 默认内置支持MySQL, 其他数据库需要手动import对应的数据库引擎第三方包.
|
2017-11-23 10:21:28 +08:00
|
|
|
|
package gdb
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2018-08-08 09:09:28 +08:00
|
|
|
|
"fmt"
|
2018-08-08 20:09:52 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"database/sql"
|
2018-08-08 09:09:28 +08:00
|
|
|
|
"gitee.com/johng/gf/g/container/gmap"
|
|
|
|
|
|
"gitee.com/johng/gf/g/container/gring"
|
|
|
|
|
|
"gitee.com/johng/gf/g/container/gtype"
|
|
|
|
|
|
"gitee.com/johng/gf/g/os/gcache"
|
|
|
|
|
|
"gitee.com/johng/gf/g/util/grand"
|
2018-10-22 11:13:00 +08:00
|
|
|
|
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
|
2018-10-09 13:33:00 +08:00
|
|
|
|
"gitee.com/johng/gf/g/container/gvar"
|
2017-11-23 10:21:28 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2018-08-08 09:09:28 +08:00
|
|
|
|
OPTION_INSERT = 0
|
|
|
|
|
|
OPTION_REPLACE = 1
|
|
|
|
|
|
OPTION_SAVE = 2
|
|
|
|
|
|
OPTION_IGNORE = 3
|
2017-11-23 10:21:28 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库操作接口
|
|
|
|
|
|
type Link interface {
|
2018-08-08 09:09:28 +08:00
|
|
|
|
// 打开数据库连接,建立数据库操作对象
|
|
|
|
|
|
Open(c *ConfigNode) (*sql.DB, error)
|
|
|
|
|
|
|
|
|
|
|
|
// SQL操作方法
|
|
|
|
|
|
Query(q string, args ...interface{}) (*sql.Rows, error)
|
|
|
|
|
|
Exec(q string, args ...interface{}) (sql.Result, error)
|
|
|
|
|
|
Prepare(q string) (*sql.Stmt, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库查询
|
|
|
|
|
|
GetAll(q string, args ...interface{}) (Result, error)
|
|
|
|
|
|
GetOne(q string, args ...interface{}) (Record, error)
|
|
|
|
|
|
GetValue(q string, args ...interface{}) (Value, error)
|
|
|
|
|
|
|
|
|
|
|
|
// Ping
|
|
|
|
|
|
PingMaster() error
|
|
|
|
|
|
PingSlave() error
|
|
|
|
|
|
|
|
|
|
|
|
// 连接属性设置
|
|
|
|
|
|
SetMaxIdleConns(n int)
|
|
|
|
|
|
SetMaxOpenConns(n int)
|
2018-10-13 20:29:27 +08:00
|
|
|
|
SetConnMaxLifetime(n int)
|
2018-08-08 09:09:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 开启事务操作
|
|
|
|
|
|
Begin() (*Tx, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 数据表插入/更新/保存操作
|
|
|
|
|
|
Insert(table string, data Map) (sql.Result, error)
|
|
|
|
|
|
Replace(table string, data Map) (sql.Result, error)
|
|
|
|
|
|
Save(table string, data Map) (sql.Result, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 数据表插入/更新/保存操作(批量)
|
|
|
|
|
|
BatchInsert(table string, list List, batch int) (sql.Result, error)
|
|
|
|
|
|
BatchReplace(table string, list List, batch int) (sql.Result, error)
|
|
|
|
|
|
BatchSave(table string, list List, batch int) (sql.Result, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 数据修改/删除
|
|
|
|
|
|
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
|
|
|
|
|
|
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
|
|
|
|
|
|
|
|
|
|
|
|
// 创建链式操作对象(Table为From的别名)
|
|
|
|
|
|
Table(tables string) *Model
|
|
|
|
|
|
From(tables string) *Model
|
|
|
|
|
|
|
|
|
|
|
|
// 内部方法
|
|
|
|
|
|
insert(table string, data Map, option uint8) (sql.Result, error)
|
|
|
|
|
|
batchInsert(table string, list List, batch int, option uint8) (sql.Result, error)
|
|
|
|
|
|
|
|
|
|
|
|
getQuoteCharLeft() string
|
|
|
|
|
|
getQuoteCharRight() string
|
|
|
|
|
|
handleSqlBeforeExec(q *string) *string
|
2017-11-23 10:21:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库链接对象
|
2018-03-09 17:55:42 +08:00
|
|
|
|
type Db struct {
|
2018-10-13 20:29:27 +08:00
|
|
|
|
link Link // 底层数据库类型管理对象
|
|
|
|
|
|
group string // 配置分组名称
|
|
|
|
|
|
charl string // SQL安全符号(左)
|
|
|
|
|
|
charr string // SQL安全符号(右)
|
|
|
|
|
|
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
|
|
|
|
|
|
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
|
|
|
|
|
|
cache *gcache.Cache // 查询缓存,需要注意的是,事务查询不支持缓存
|
|
|
|
|
|
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
|
|
|
|
|
|
maxOpenConnCount *gtype.Int // 连接池最大打开的连接数
|
|
|
|
|
|
maxConnLifetime *gtype.Int // (单位秒)连接对象可重复使用的时间长度
|
2018-07-05 19:36:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 执行的SQL对象
|
|
|
|
|
|
type Sql struct {
|
2018-08-08 09:09:28 +08:00
|
|
|
|
Sql string // SQL语句(可能带有预处理占位符)
|
|
|
|
|
|
Args []interface{} // 预处理参数值列表
|
|
|
|
|
|
Error error // 执行结果(nil为成功)
|
2018-08-08 20:37:15 +08:00
|
|
|
|
Start int64 // 执行开始时间(毫秒)
|
|
|
|
|
|
End int64 // 执行结束时间(毫秒)
|
2018-08-08 09:09:28 +08:00
|
|
|
|
Func string // 执行方法名称
|
2017-11-23 10:21:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-01 10:09:57 +08:00
|
|
|
|
// 返回数据表记录值
|
2018-10-09 13:33:00 +08:00
|
|
|
|
type Value = *gvar.Var
|
2018-05-01 10:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 返回数据表记录Map
|
2018-08-08 09:09:28 +08:00
|
|
|
|
type Record map[string]Value
|
2018-05-01 10:09:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 返回数据表记录List
|
2018-08-08 09:09:28 +08:00
|
|
|
|
type Result []Record
|
2017-11-23 10:21:28 +08:00
|
|
|
|
|
2018-05-25 18:20:40 +08:00
|
|
|
|
// 关联数组,绑定一条数据表记录(使用别名)
|
2018-08-08 09:09:28 +08:00
|
|
|
|
type Map = map[string]interface{}
|
2018-05-25 18:20:40 +08:00
|
|
|
|
|
|
|
|
|
|
// 关联数组列表(索引从0开始的数组),绑定多条记录(使用别名)
|
2018-08-08 09:09:28 +08:00
|
|
|
|
type List = []Map
|
2017-11-23 10:21:28 +08:00
|
|
|
|
|
2018-04-27 13:59:02 +08:00
|
|
|
|
// MySQL接口对象
|
|
|
|
|
|
var linkMysql = &dbmysql{}
|
|
|
|
|
|
|
|
|
|
|
|
// PostgreSQL接口对象
|
|
|
|
|
|
var linkPgsql = &dbpgsql{}
|
|
|
|
|
|
|
2018-08-08 20:09:52 +08:00
|
|
|
|
// Sqlite接口对象
|
|
|
|
|
|
// @author wxkj<wxscz@qq.com>
|
2018-08-08 09:09:28 +08:00
|
|
|
|
var linkSqlite = &dbsqlite{}
|
|
|
|
|
|
|
2018-07-23 19:17:17 +08:00
|
|
|
|
// 数据库查询缓存对象map,使用数据库连接名称作为键名,键值为查询缓存对象
|
2018-08-08 09:09:28 +08:00
|
|
|
|
var dbCaches = gmap.NewStringInterfaceMap()
|
2018-07-23 19:17:17 +08:00
|
|
|
|
|
2018-03-05 17:15:11 +08:00
|
|
|
|
// 使用默认/指定分组配置进行连接,数据库集群配置项:default
|
2018-08-08 09:09:28 +08:00
|
|
|
|
func New(groupName ...string) (*Db, error) {
|
2018-10-13 20:29:27 +08:00
|
|
|
|
group := config.d
|
2018-08-08 09:09:28 +08:00
|
|
|
|
if len(groupName) > 0 {
|
2018-10-13 20:29:27 +08:00
|
|
|
|
group = groupName[0]
|
2018-08-08 09:09:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
config.RLock()
|
|
|
|
|
|
defer config.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
if len(config.c) < 1 {
|
|
|
|
|
|
return nil, errors.New("empty database configuration")
|
|
|
|
|
|
}
|
2018-10-13 20:29:27 +08:00
|
|
|
|
if _, ok := config.c[group]; ok {
|
|
|
|
|
|
if node, err := getConfigNodeByGroup(group, true); err == nil {
|
|
|
|
|
|
link, err := getLinkByType(node.Type)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
db := &Db {
|
|
|
|
|
|
link : link,
|
|
|
|
|
|
group : group,
|
|
|
|
|
|
charl : link.getQuoteCharLeft(),
|
|
|
|
|
|
charr : link.getQuoteCharRight(),
|
|
|
|
|
|
debug : gtype.NewBool(),
|
|
|
|
|
|
maxIdleConnCount : gtype.NewInt(),
|
|
|
|
|
|
maxOpenConnCount : gtype.NewInt(),
|
|
|
|
|
|
maxConnLifetime : gtype.NewInt(),
|
|
|
|
|
|
}
|
|
|
|
|
|
db.cache = dbCaches.GetOrSetFuncLock(group, func() interface{} {
|
|
|
|
|
|
return gcache.New()
|
|
|
|
|
|
}).(*gcache.Cache)
|
|
|
|
|
|
return db, nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2018-08-08 09:09:28 +08:00
|
|
|
|
} else {
|
2018-10-13 20:29:27 +08:00
|
|
|
|
return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
|
2018-08-08 09:09:28 +08:00
|
|
|
|
}
|
2017-11-23 10:21:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-10-13 20:29:27 +08:00
|
|
|
|
// 获取指定数据库角色的一个配置项,内部根据权重计算负载均衡
|
|
|
|
|
|
func getConfigNodeByGroup(group string, master bool) (*ConfigNode, error) {
|
|
|
|
|
|
if list, ok := config.c[group]; ok {
|
|
|
|
|
|
// 将master, slave集群列表拆分出来
|
|
|
|
|
|
masterList := make(ConfigGroup, 0)
|
|
|
|
|
|
slaveList := make(ConfigGroup, 0)
|
|
|
|
|
|
for i := 0; i < len(list); i++ {
|
|
|
|
|
|
if list[i].Role == "slave" {
|
|
|
|
|
|
slaveList = append(slaveList, list[i])
|
|
|
|
|
|
} else {
|
|
|
|
|
|
masterList = append(masterList, list[i])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(masterList) < 1 {
|
|
|
|
|
|
return nil, errors.New("at least one master node configuration's need to make sense")
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(slaveList) < 1 {
|
|
|
|
|
|
slaveList = masterList
|
|
|
|
|
|
}
|
|
|
|
|
|
if master {
|
|
|
|
|
|
return getConfigNodeByPriority(masterList), nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return getConfigNodeByPriority(slaveList), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return nil, errors.New(fmt.Sprintf("empty database configuration for item name '%s'", group))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-11-23 10:21:28 +08:00
|
|
|
|
// 按照负载均衡算法(优先级配置)从数据库集群中选择一个配置节点出来使用
|
2018-04-27 11:38:26 +08:00
|
|
|
|
// 算法说明举例,
|
|
|
|
|
|
// 1、假如2个节点的priority都是1,那么随机大小范围为[0, 199];
|
|
|
|
|
|
// 2、那么节点1的权重范围为[0, 99],节点2的权重范围为[100, 199],比例为1:1;
|
|
|
|
|
|
// 3、假如计算出的随机数为99;
|
|
|
|
|
|
// 4、那么选择的配置为节点1;
|
2018-08-08 09:09:28 +08:00
|
|
|
|
func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
|
|
|
|
|
|
if len(cg) < 2 {
|
|
|
|
|
|
return &cg[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
var total int
|
|
|
|
|
|
for i := 0; i < len(cg); i++ {
|
|
|
|
|
|
total += cg[i].Priority * 100
|
|
|
|
|
|
}
|
|
|
|
|
|
// 不能取到末尾的边界点
|
|
|
|
|
|
r := grand.Rand(0, total)
|
|
|
|
|
|
if r > 0 {
|
|
|
|
|
|
r -= 1
|
|
|
|
|
|
}
|
|
|
|
|
|
min := 0
|
|
|
|
|
|
max := 0
|
|
|
|
|
|
for i := 0; i < len(cg); i++ {
|
|
|
|
|
|
max = min + cg[i].Priority*100
|
|
|
|
|
|
//fmt.Printf("r: %d, min: %d, max: %d\n", r, min, max)
|
|
|
|
|
|
if r >= min && r < max {
|
|
|
|
|
|
return &cg[i]
|
|
|
|
|
|
} else {
|
|
|
|
|
|
min = max
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
2017-11-23 10:21:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-10-13 20:29:27 +08:00
|
|
|
|
// 根据配置的数据库;类型获得Link接口对象
|
|
|
|
|
|
func getLinkByType(dbType string) (Link, error) {
|
|
|
|
|
|
switch dbType {
|
|
|
|
|
|
case "mysql":
|
|
|
|
|
|
return linkMysql, nil
|
|
|
|
|
|
case "pgsql":
|
|
|
|
|
|
return linkPgsql, nil
|
|
|
|
|
|
case "sqlite":
|
|
|
|
|
|
return linkSqlite, nil
|
|
|
|
|
|
default:
|
|
|
|
|
|
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
|
2018-08-08 09:09:28 +08:00
|
|
|
|
}
|
2018-10-13 20:29:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获得底层数据库链接对象
|
|
|
|
|
|
func (db *Db) getSqlDb(master bool) (*sql.DB, error) {
|
|
|
|
|
|
node, err := getConfigNodeByGroup(db.group, master)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
link, err := getLinkByType(node.Type)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
sqlDb, err := link.Open(node)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
if node.MaxIdleConnCount > 0 {
|
|
|
|
|
|
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
|
|
|
|
|
|
}
|
|
|
|
|
|
if n := db.maxIdleConnCount.Val(); n > 0 {
|
|
|
|
|
|
sqlDb.SetMaxIdleConns(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if node.MaxOpenConnCount > 0 {
|
|
|
|
|
|
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
|
|
|
|
|
|
}
|
|
|
|
|
|
if n := db.maxOpenConnCount.Val(); n > 0 {
|
|
|
|
|
|
sqlDb.SetMaxOpenConns(n)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if node.MaxConnLifetime > 0 {
|
|
|
|
|
|
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
|
|
|
|
|
|
}
|
|
|
|
|
|
if n := db.maxConnLifetime.Val(); n > 0 {
|
|
|
|
|
|
sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
|
|
|
|
|
|
}
|
|
|
|
|
|
return sqlDb, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建底层数据库master链接对象
|
|
|
|
|
|
func (db *Db) Master() (*sql.DB, error) {
|
|
|
|
|
|
return db.getSqlDb(true)
|
|
|
|
|
|
}
|
2018-08-08 09:09:28 +08:00
|
|
|
|
|
2018-10-13 20:29:27 +08:00
|
|
|
|
// 创建底层数据库slave链接对象
|
|
|
|
|
|
func (db *Db) Slave() (*sql.DB, error) {
|
|
|
|
|
|
return db.getSqlDb(false)
|
2017-11-23 10:21:28 +08:00
|
|
|
|
}
|