zorm

Go 轻量 ORM
授权协议 Apache
开发语言 Google Go
所属分类 程序开发、 ORM/持久层框架
软件类型 开源软件
地区 国产
投 递 者 阮阳曦
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

Go轻量ORM,零依赖,支持达梦(dm)、金仓(kingbase) 、神通(shentong) 、南通(gbase)TDenginemysqlpostgresqloraclemssqlsqlite、db2 、clickhouse...

源码地址: https://gitee.com/chunanyong/zorm
官网: https://zorm.cn
测试用例: https://gitee.com/wuxiangege/zorm-examples/

  • 基于原生sql语句编写,学习成本更低.
  • 自带代码生成器
  • 代码精简,主体2500行,零依赖4200行,注释详细,方便定制修改.
  • 支持事务传播,这是zorm诞生的主要原因
  • 支持mysql,postgresql,oracle,mssql,sqlite,db2,dm(达梦),kingbase(人大金仓),shentong(神通),gbase(南通),TDengine,clickhouse
  • 支持多库和读写分离
  • 不支持联合主键,变通认为无主键,业务控制实现(艰难取舍)
  • 支持seata,hptx,dbpack分布式事务,支持全局事务托管,不修改业务代码,零侵入分布式事务
  • 支持clickhouse,更新,删除语句使用SQL92标准语法.clickhouse-go官方驱动不支持批量insert语法,建议使用https://github.com/mailru/go-clickhouse

支持国产数据库

达梦(dm)

配置zorm.DataSourceConfig的 DriverName:dm ,Dialect:dm
达梦数据库驱动: gitee.com/chunanyong/dm
达梦的text类型会映射为dm.DmClob,string不能接收,需要实现zorm.CustomDriverValueConver接口,自定义扩展处理

import (
	// 00.引入数据库驱动
	"gitee.com/chunanyong/dm"
	"io"
)

// CustomDMText 实现ICustomDriverValueConver接口,扩展自定义类型,例如 达梦数据库TEXT类型,映射出来的是dm.DmClob类型,无法使用string类型直接接收
type CustomDMText struct{}

// GetDriverValue 根据数据库列类型,返回driver.Value的实例,struct属性类型
// map接收或者字段不存在,无法获取到structFieldType,会传入nil
func (dmtext CustomDMText) GetDriverValue(ctx context.Context, columnType *sql.ColumnType, structFieldType *reflect.Type) (driver.Value, error) {
	// 如果需要使用structFieldType,需要先判断是否为nil
	// if structFieldType != nil {
	// }

	return &dm.DmClob{}, nil
}

// ConverDriverValue 数据库列类型,GetDriverValue返回的driver.Value的临时接收值,struct属性类型
// map接收或者字段不存在,无法获取到structFieldType,会传入nil
// 返回符合接收类型值的指针,指针,指针!!!!
func (dmtext CustomDMText) ConverDriverValue(ctx context.Context, columnType *sql.ColumnType, tempDriverValue driver.Value, structFieldType *reflect.Type) (interface{}, error) {
	// 如果需要使用structFieldType,需要先判断是否为nil
	// if structFieldType != nil {
	// }

	// 类型转换
	dmClob, isok := tempDriverValue.(*dm.DmClob)
	if !isok {
		return tempDriverValue, errors.New("->ConverDriverValue-->转换至*dm.DmClob类型失败")
	}
	if dmClob == nil || !dmClob.Valid {
		return new(string), nil
	}
	// 获取长度
	dmlen, errLength := dmClob.GetLength()
	if errLength != nil {
		return dmClob, errLength
	}

	// int64转成int类型
	strInt64 := strconv.FormatInt(dmlen, 10)
	dmlenInt, errAtoi := strconv.Atoi(strInt64)
	if errAtoi != nil {
		return dmClob, errAtoi
	}

	// 读取字符串
	str, errReadString := dmClob.ReadString(1, dmlenInt)

	// 处理空字符串或NULL造成的EOF错误
	if errReadString == io.EOF {
		return new(string), nil
	}

	return &str, errReadString
}
// RegisterCustomDriverValueConver 注册自定义的字段处理逻辑,用于驱动无法直接转换的场景,例如达梦的 TEXT 无法直接转化成 string
// 一般是放到init方法里进行注册
func init() {
	// dialectColumnType 值是 Dialect.字段类型 ,例如 dm.TEXT
    zorm.RegisterCustomDriverValueConver("dm.TEXT", CustomDMText{})
}

金仓(kingbase)

配置zorm.DataSourceConfig的 DriverName:kingbase ,Dialect:kingbase
金仓驱动说明: https://help.kingbase.com.cn/doc-view-8108.html
金仓kingbase 8核心是基于postgresql 9.6,可以使用 https://github.com/lib/pq 进行测试,生产环境建议使用官方驱动.
注意修改 data/kingbase.conf中 ora_input_emptystr_isnull = false,因为golang没有null值,一般数据库都是not null,golang的string默认是'',如果这个设置为true,数据库就会把值设置为null,和字段属性not null 冲突,因此报错.

神通(shentong)

建议使用官方驱动,配置zorm.DataSourceConfig的 DriverName:aci ,Dialect:shentong

南通(gbase)

暂时还未找到官方golang驱动,配置zorm.DataSourceConfig的 DriverName:gbase ,Dialect:gbase
暂时先使用odbc驱动,DriverName:odbc ,Dialect:gbase

TDengine

数据库脚本和实体类

生成实体类或手动编写,建议使用代码生成器 https://gitee.com/zhou-a-xing/zorm-generate-struct

package testzorm

import (
	"time"

	"gitee.com/chunanyong/zorm"
)

//建表语句

/*

DROP TABLE IF EXISTS `t_demo`;
CREATE TABLE `t_demo`  (
  `id` varchar(50)  NOT NULL COMMENT '主键',
  `userName` varchar(30)  NOT NULL COMMENT '姓名',
  `password` varchar(50)  NOT NULL COMMENT '密码',
  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
  `active` int  COMMENT '是否有效(0否,1是)',
  PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4  COMMENT = '例子' ;

*/

//demoStructTableName 表名常量,方便直接调用
const demoStructTableName = "t_demo"

// demoStruct 例子
type demoStruct struct {
	//引入默认的struct,隔离IEntityStruct的方法改动
	zorm.EntityStruct

	//Id 主键
	Id string `column:"id"`

	//UserName 姓名
	UserName string `column:"userName"`

	//Password 密码
	Password string `column:"password"`

	//CreateTime <no value>
	CreateTime time.Time `column:"createTime"`

	//Active 是否有效(0否,1是)
	//Active int `column:"active"`

	//------------------数据库字段结束,自定义字段写在下面---------------//
	//如果查询的字段在column tag中没有找到,就会根据名称(不区分大小写,支持 _ 下划线转驼峰)映射到struct的属性上

	//模拟自定义的字段Active
	Active int
}

//GetTableName 获取表名称
//IEntityStruct 接口的方法,实体类需要实现!!!
func (entity *demoStruct) GetTableName() string {
	return demoStructTableName
}

//GetPKColumnName 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称
//不支持联合主键,变通认为无主键,业务控制实现(艰难取舍)
//如果没有主键,也需要实现这个方法, return "" 即可
//IEntityStruct 接口的方法,实体类需要实现!!!
func (entity *demoStruct) GetPKColumnName() string {
	//如果没有主键
	//return ""
	return "id"
}

//newDemoStruct 创建一个默认对象
func newDemoStruct() demoStruct {
	demo := demoStruct{
		//如果Id=="",保存时zorm会调用zorm.FuncGenerateStringID(ctx),默认时间戳+随机数,也可以自己定义实现方式,例如 zorm.FuncGenerateStringID=funcmyId
		Id:         zorm.FuncGenerateStringID(ctx),
		UserName:   "defaultUserName",
		Password:   "defaultPassword",
		Active:     1,
		CreateTime: time.Now(),
	}
	return demo
}

测试用例即文档

测试用例: https://gitee.com/wuxiangege/zorm-examples


// testzorm 使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体
// 占位符统一使用?,zorm会根据数据库类型,自动替换占位符,例如postgresql数据库把?替换成$1,$2...
// zorm使用 ctx context.Context 参数实现事务传播,ctx从web层传递进来即可,例如gin的c.Request.Context()
// zorm的事务操作需要显式使用zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {})开启
package testzorm

import (
	"context"
	"fmt"
	"testing"
	"time"

	"gitee.com/chunanyong/zorm"

	// 00.引入数据库驱动
	_ "github.com/go-sql-driver/mysql"
)

// dbDao 代表一个数据库,如果有多个数据库,就对应声明多个DBDao
var dbDao *zorm.DBDao

// ctx默认应该有 web层传入,例如gin的c.Request.Context().这里只是模拟
var ctx = context.Background()

// 01.初始化DBDao
func init() {

	// 自定义zorm日志输出
	// zorm.LogCallDepth = 4 // 日志调用的层级
	// zorm.FuncLogError = myFuncLogError // 记录异常日志的函数
	// zorm.FuncLogPanic = myFuncLogPanic // 记录panic日志,默认使用defaultLogError实现
	// zorm.FuncPrintSQL = myFuncPrintSQL // 打印sql的函数

	// 自定义日志输出格式,把FuncPrintSQL函数重新赋值
	// log.SetFlags(log.LstdFlags)
	// zorm.FuncPrintSQL = zorm.FuncPrintSQL

    // 自定义主键生成
	// zorm.FuncGenerateStringID=funcmyId

	// 自定义decimal类型实现
	// zorm.FuncDecimalValue=funcmyDecimal

    // Go数据库驱动列表:https://github.com/golang/go/wiki/SQLDrivers

	// dbDaoConfig 数据库的配置.这里只是模拟,生产应该是读取配置配置文件,构造DataSourceConfig
	dbDaoConfig := zorm.DataSourceConfig{
		// DSN 数据库的连接字符串,parseTime=true会自动转换为time格式,默认查询出来的是[]byte数组
		DSN: "root:root@tcp(127.0.0.1:3306)/zorm?charset=utf8&parseTime=true",
		// DriverName 数据库驱动名称:mysql,postgres,oci8,sqlserver,sqlite3,go_ibm_db,clickhouse,dm,kingbase,aci,taosSql|taosRestful 和Dialect对应
		// sql.Open(DriverName,DSN) DriverName就是驱动的sql.Open第一个字符串参数,根据驱动实际情况获取
		DriverName: "mysql",
		// Dialect 数据库方言:mysql,postgresql,oracle,mssql,sqlite,db2,clickhouse,dm,kingbase,shentong,tdengine 和 DriverName 对应
		Dialect: "mysql",
		// MaxOpenConns 数据库最大连接数 默认50
		MaxOpenConns: 50,
		// MaxIdleConns 数据库最大空闲连接数 默认50
		MaxIdleConns: 50,
		// ConnMaxLifetimeSecond 连接存活秒时间. 默认600(10分钟)后连接被销毁重建.避免数据库主动断开连接,造成死连接.MySQL默认wait_timeout 28800秒(8小时)
		ConnMaxLifetimeSecond: 600,
		// SlowSQLMillis 慢sql的时间阈值,单位毫秒.小于0是禁用SQL语句输出;等于0是只输出SQL语句,不计算执行时间;大于0是计算SQL执行时间,并且>=SlowSQLMillis值
		SlowSQLMillis: 0,
		// DefaultTxOptions 事务隔离级别的默认配置,默认为nil
		// DefaultTxOptions: nil,
		// 如果是使用分布式事务,建议使用默认配置
		// DefaultTxOptions: &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false},

		// FuncGlobalTransaction seata/hptx全局分布式事务的适配函数,返回IGlobalTransaction接口的实现
		// 业务必须调用 ctx,_=zorm.BindContextEnableGlobalTransaction(ctx) 开启全局分布事务
	    // FuncGlobalTransaction : MyFuncGlobalTransaction,

	    // SQLDB 使用现有的数据库连接,优先级高于DSN
	    // SQLDB : nil,

	    // DisableTransaction 禁用事务,默认false,如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务,为了处理某些数据库不支持事务,比如TDengine
	    // 禁用事务应该有驱动伪造事务API,不应该有orm实现,clickhouse的驱动就是这样做的
	    // DisableTransaction :false,
	}

	// 根据dbDaoConfig创建dbDao, 一个数据库只执行一次,第一个执行的数据库为 defaultDao,后续zorm.xxx方法,默认使用的就是defaultDao
	dbDao, _ = zorm.NewDBDao(&dbDaoConfig)
}

// TestInsert 02.测试保存Struct对象
func TestInsert(t *testing.T) {

	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		// 创建一个demo对象
		demo := newDemoStruct()

		// 保存对象,参数是对象指针.如果主键是自增,会赋值到对象的主键属性
		_, err := zorm.Insert(ctx, &demo)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	// 标记测试失败
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}

// TestInsertSlice 03.测试批量保存Struct对象的Slice
// 如果是自增主键,无法对Struct对象里的主键属性赋值
func TestInsertSlice(t *testing.T) {

	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {

		// slice存放的类型是zorm.IEntityStruct!!!使用IEntityStruct接口,兼容Struct实体类
		demoSlice := make([]zorm.IEntityStruct, 0)

		// 创建对象1
		demo1 := newDemoStruct()
		demo1.UserName = "demo1"
		// 创建对象2
		demo2 := newDemoStruct()
		demo2.UserName = "demo2"

		demoSlice = append(demoSlice, &demo1, &demo2)

		// 批量保存对象,如果主键是自增,无法保存自增的ID到对象里.
		_, err := zorm.InsertSlice(ctx, demoSlice)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	// 标记测试失败
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}

// TestInsertEntityMap 04.测试保存EntityMap对象,用于不方便使用struct的场景,使用Map作为载体
func TestInsertEntityMap(t *testing.T) {

	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		// 创建一个EntityMap,需要传入表名
		entityMap := zorm.NewEntityMap(demoStructTableName)
		// 设置主键名称
		entityMap.PkColumnName = "id"
		// 如果是自增序列,设置序列的值
		// entityMap.PkSequence = "mySequence"

		// Set 设置数据库的字段值
		// 如果主键是自增或者序列,不要entityMap.Set主键的值
		entityMap.Set("id", zorm.FuncGenerateStringID(ctx))
		entityMap.Set("userName", "entityMap-userName")
		entityMap.Set("password", "entityMap-password")
		entityMap.Set("createTime", time.Now())
		entityMap.Set("active", 1)

		// 执行
		_, err := zorm.InsertEntityMap(ctx, entityMap)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	// 标记测试失败
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}


// TestInsertEntityMapSlice 05.测试批量保存[]IEntityMap,用于不方便使用struct的场景,使用Map作为载体
func TestInsertEntityMapSlice(t *testing.T) {
	_, err := Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		entityMapSlice := make([]IEntityMap, 0)
		entityMap1 := NewEntityMap(demoStructTableName)
		entityMap1.PkColumnName = "id"
		entityMap1.Set("id", zorm.FuncGenerateStringID(ctx))
		entityMap1.Set("userName", "entityMap-userName1")
		entityMap1.Set("password", "entityMap-password1")
		entityMap1.Set("createTime", time.Now())
		entityMap1.Set("active", 1)

		entityMap2 := NewEntityMap(demoStructTableName)
		entityMap2.PkColumnName = "id"
		entityMap2.Set("id", zorm.FuncGenerateStringID(ctx))
		entityMap2.Set("userName", "entityMap-userName2")
		entityMap2.Set("password", "entityMap-password2")
		entityMap2.Set("createTime", time.Now())
		entityMap2.Set("active", 2)

		entityMapSlice = append(entityMapSlice, entityMap1)
		entityMapSlice = append(entityMapSlice, entityMap2)

		// 执行
		_, err := InsertEntityMapSlice(ctx, entityMapSlice)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	// 标记测试失败
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}

// TestQueryRow 06.测试查询一个struct对象
func TestQueryRow(t *testing.T) {

	// 声明一个对象的指针,用于承载返回的数据
	demo := demoStruct{}

	// 构造查询用的finder
	// finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
	// finder := zorm.NewSelectFinder(demoStructTableName, "id,userName") // select id,userName from t_demo
	finder := zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo
	// finder默认启用了sql注入检查,禁止语句中拼接 ' 单引号,可以设置 finder.InjectionCheck = false 解开限制

	// finder.Append 第一个参数是语句,后面的参数是对应的值,值的顺序要正确.语句统一使用?,zorm会处理数据库的差异
	// in (?) 参数必须有()括号,不能 in ?
	finder.Append("WHERE id=? and active in(?)", "20210630163227149563000042432429", []int{0, 1})

	// 如何使用like
	// finder.Append("WHERE id like ? ", "20210630163227149563000042432429%")

	// 执行查询,has为true表示数据库有数据
	has, err := zorm.QueryRow(ctx, finder, &demo)

	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}
	// 打印结果
	fmt.Println(demo)
}

// TestQueryRowMap 07.测试查询map接收结果,用于不太适合struct的场景,比较灵活
func TestQueryRowMap(t *testing.T) {

	// 构造查询用的finder
	// finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
	finder := zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo
	// finder.Append 第一个参数是语句,后面的参数是对应的值,值的顺序要正确.语句统一使用?,zorm会处理数据库的差异
	// in (?) 参数必须有()括号,不能 in ?
	finder.Append("WHERE id=? and active in(?)", "20210630163227149563000042432429", []int{0, 1})
	// 执行查询
	resultMap, err := zorm.QueryRowMap(ctx, finder)

	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}
	// 打印结果
	fmt.Println(resultMap)
}

// TestQuery 08.测试查询对象列表
func TestQuery(t *testing.T) {

	// 创建用于接收结果的slice
	list := make([]demoStruct, 0)

	// 构造查询用的finder
	// finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
	finder := zorm.NewFinder().Append("SELECT id FROM " + demoStructTableName) // select * from t_demo
	// 创建分页对象,查询完成后,page对象可以直接给前端分页组件使用
	page := zorm.NewPage()
	page.PageNo = 1   // 查询第1页,默认是1
	page.PageSize = 20 // 每页20条,默认是20

	// 不查询总条数
	// finder.SelectTotalCount = false

    // 如果是特别复杂的语句,造成count语句构造失败,可以手动指定分页语句
	// countFinder := zorm.NewFinder().Append("select count(*) from (")
	// countFinder.AppendFinder(finder)
	// countFinder.Append(") tempcountfinder")
	// finder.CountFinder = countFinder

	// 执行查询
	err := zorm.Query(ctx, finder, &list, page)
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}
	// 打印结果
	fmt.Println("总条数:", page.TotalCount, "  列表:", list)
}

// TestQueryMap 09.测试查询map列表,用于不方便使用struct的场景,一条记录是一个map对象
func TestQueryMap(t *testing.T) {
	// 构造查询用的finder
	// finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
    finder := zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo
	// 创建分页对象,查询完成后,page对象可以直接给前端分页组件使用
	page := zorm.NewPage()
	page.PageNo = 1   // 查询第1页,默认是1
	page.PageSize = 20 // 每页20条,默认是20

	// 不查询总条数
	// finder.SelectTotalCount = false

    // 如果是特别复杂的语句,造成count语句构造失败,可以手动指定分页语句
	// countFinder := zorm.NewFinder().Append("select count(*) from (")
	// countFinder.AppendFinder(finder)
	// countFinder.Append(") tempcountfinder")
	// finder.CountFinder = countFinder

	// 执行查询
	listMap, err := zorm.QueryMap(ctx, finder, page)
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}
	// 打印结果
	fmt.Println("总条数:", page.TotalCount, "  列表:", listMap)
}

// TestUpdateNotZeroValue 10.更新struct对象,只更新不为零值的字段.主键必须有值
func TestUpdateNotZeroValue(t *testing.T) {

	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		// 声明一个对象的指针,用于更新数据
		demo := demoStruct{}
		demo.Id = "20210630163227149563000042432429"
		demo.UserName = "UpdateNotZeroValue"

		// 更新 "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["UpdateNotZeroValue","20210630163227149563000042432429"]
		_, err := zorm.UpdateNotZeroValue(ctx, &demo)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}

}

// TestUpdate 11.更新struct对象,更新所有字段.主键必须有值
func TestUpdate(t *testing.T) {

	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {

		// 声明一个对象的指针,用于更新数据
		demo := demoStruct{}
		demo.Id = "20210630163227149563000042432429"
		demo.UserName = "TestUpdate"

		_, err := zorm.Update(ctx, &demo)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}
}

// TestUpdateFinder 12.通过finder更新,zorm最灵活的方式,可以编写任何更新语句,甚至手动编写insert语句
func TestUpdateFinder(t *testing.T) {
	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		// finder := zorm.NewUpdateFinder(demoStructTableName) // UPDATE t_demo SET
		// finder := zorm.NewDeleteFinder(demoStructTableName)  // DELETE FROM t_demo
		finder := zorm.NewFinder().Append("UPDATE").Append(demoStructTableName).Append("SET") // UPDATE t_demo SET
		finder.Append("userName=?,active=?", "TestUpdateFinder", 1).Append("WHERE id=?", "20210630163227149563000042432429")

		// 更新 "sql":"UPDATE t_demo SET  userName=?,active=? WHERE id=?","args":["TestUpdateFinder",1,"20210630163227149563000042432429"]
		_, err := zorm.UpdateFinder(ctx, finder)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}

}

// TestUpdateEntityMap 13.更新一个EntityMap,主键必须有值
func TestUpdateEntityMap(t *testing.T) {
	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		// 创建一个EntityMap,需要传入表名
		entityMap := zorm.NewEntityMap(demoStructTableName)
		// 设置主键名称
		entityMap.PkColumnName = "id"
		// Set 设置数据库的字段值,主键必须有值
		entityMap.Set("id", "20210630163227149563000042432429")
		entityMap.Set("userName", "TestUpdateEntityMap")
		// 更新 "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["TestUpdateEntityMap","20210630163227149563000042432429"]
		_, err := zorm.UpdateEntityMap(ctx, entityMap)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}

}

// TestDelete 14.删除一个struct对象,主键必须有值
func TestDelete(t *testing.T) {
	// 需要手动开启事务,匿名函数返回的error如果不是nil,事务就会回滚.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
    // 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别
	// 例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
	_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		demo := demoStruct{}
		demo.Id = "20210630163227149563000042432429"

		// 删除 "sql":"DELETE FROM t_demo WHERE id=?","args":["20210630163227149563000042432429"]
		_, err := zorm.Delete(ctx, &demo)

		// 如果返回的err不是nil,事务就会回滚
		return nil, err
	})
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}

}

// TestProc 15.测试调用存储过程
func TestProc(t *testing.T) {
	demo := demoStruct{}
	finder := zorm.NewFinder().Append("call testproc(?) ", "u_10001")
	zorm.QueryRow(ctx, finder, &demo)
	fmt.Println(demo)
}

// TestFunc 16.测试调用自定义函数
func TestFunc(t *testing.T) {
	userName := ""
	finder := zorm.NewFinder().Append("select testfunc(?) ", "u_10001")
	zorm.QueryRow(ctx, finder, &userName)
	fmt.Println(userName)
}

// TestOther 17.其他的一些说明.非常感谢您能看到这一行
func TestOther(t *testing.T) {

	// 场景1.多个数据库.通过对应数据库的dbDao,调用BindContextDBConnection函数,把这个数据库的连接绑定到返回的ctx上,然后把ctx传递到zorm的函数即可
	// 也可以重写FuncReadWriteStrategy函数,通过ctx设置不同的key,返回指定数据库的DBDao
	newCtx, err := dbDao.BindContextDBConnection(ctx)
	if err != nil { // 标记测试失败
		t.Errorf("错误:%v", err)
	}

	finder := zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo
	// 把新产生的newCtx传递到zorm的函数
	list, _ := zorm.QueryMap(newCtx, finder, nil)
	fmt.Println(list)

	// 场景2.单个数据库的读写分离.设置读写分离的策略函数.
	zorm.FuncReadWriteStrategy = myReadWriteStrategy

	// 场景3.如果是多个数据库,每个数据库还读写分离,按照 场景1 处理. 
	// 也可以重写FuncReadWriteStrategy函数,通过ctx设置不同的key,返回指定数据库的DBDao

}

// myReadWriteStrategy 数据库的读写分离的策略 rwType=0 read,rwType=1 write
// 也可以通过ctx设置不同的key,返回指定数据库的DBDao
func myReadWriteStrategy(ctx context.Context, rwType int) (*zorm.DBDao, error) {
	// 根据自己的业务场景,返回需要的读写dao,每次需要数据库的连接的时候,会调用这个函数
	// if rwType == 0 {
	// 	return dbReadDao
	// }
	// return dbWriteDao

	return DbDao, nil
}

// --------------------------------------------
// ICustomDriverValueConver接口,参见达梦的例子

// --------------------------------------------
// OverrideFunc 重写ZORM的函数,当你使用这个函数时,你必须知道自己在做什么
  • 介绍 go(golang)轻量级ORM,零依赖,零侵入分布式事务,支持达梦(dm),金仓(kingbase),神通(shentong),南大通用(gbase),mysql,postgresql,oracle,mssql,sqlite,clickhouse数据库. 源码地址:https://gitee.com/chunanyong/zorm 作者博客:https://www.jiagou.com 交

  • // testzorm 使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体 // 占位符统一使用?,zorm会根据数据库类型,自动替换占位符,例如postgresql数据库把?替换成$1,$2... // zorm使用 ctx context.Context 参数实现事务传播,ctx从web层传递进来即可,例如gin的c.Request.Context() // zorm的

  • // testzorm 使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体 // 占位符统一使用?,zorm会根据数据库类型,自动替换占位符,例如postgresql数据库把?替换成$1,$2... // zorm使用 ctx context.Context 参数实现事务传播,ctx从web层传递进来即可,例如gin的c.Request.Context() // zorm的

 相关资料
  • 问题内容: 除了无法找到设置“ this”变量的好方法之外,我对Javascript有很好的理解。考虑: 没有最后四行,有没有办法做到这一点?这很烦人……我试图绑定一个匿名函数,我认为它是美丽而聪明的,但无济于事: 显然,将变量传递到myFunction是一个选项……但这不是这个问题的重点。 谢谢。 问题答案: 为JavaScript中的所有函数定义了两种方法,和。函数语法如下: 这些函数的作用是

  • 主要内容:使用普通函数创建 goroutine,使用匿名函数创建goroutine在编写 Socket 网络程序时,需要提前准备一个线程池为每一个 Socket 的收发包分配一个线程。开发人员需要在线程数量和 CPU 数量间建立一个对应关系,以保证每个任务能及时地被分配到 CPU 上进行处理,同时避免多个任务频繁地在线程间切换执行而损失效率。 虽然,线程池为逻辑编写者提供了线程分配的抽象机制。但是,如果面对随时随地可能发生的并发和线程处理需求,线程池就不是非常直观和方便了。能否

  • Jenkins Pipeline插件有一个称为“轻量级签出”的功能,其中主服务器仅从repo中提取Jenkinsfile,而不是整个repo。配置屏幕中有一个相应的复选框。我想在多分支管道中进行轻量级签出,但我在多分支配置屏幕中没有看到复选框。有什么想法如何实现这一点吗?我注意到一些关闭的问题表明此功能可用,但我无法找到任何有关如何实现它的细节。 相关资料: https://issues.jenk

  • 问题内容: 题 我正在寻找Java内存对象缓存API。有什么建议吗?您过去使用过什么解决方案? 当前 现在,我只是在使用地图: 要求 我需要扩展缓存以包括以下基本功能: 最大尺寸 生存时间 但是,我不需要更复杂的功能,例如: 来自多个进程的访问(缓存服务器) 持久性(到磁盘) 意见建议 内存中缓存: Guava CacheBuilder-活动开发。请参阅此演示文稿。 LRUMap-通过API配置。

  • 问题内容: 我目前在一个网站上工作,该网站必须存在于内存可用性非常低的VM上(目前被告知要达到512mb)。不幸的是,至少在不久的将来,数据库和Web应用程序必须是同一台服务器。 现在,我已经在这里通读了一些问题,并尝试进行自己的研究,但是这里有很多选择。从本质上讲,什么是可以安装的轻巧的数据库服务器?SQL或NoSQL并不重要;它不会占用大量数据库资源,但我现在不想随我现在选择的内容而受到限制。

  • 问题内容: 在解决了另一个愚蠢的日食问题之后,我想尝试获得尽可能轻巧,最小的Eclipse安装。 为了清楚起见,我使用eclipse做两件事: 编辑Java 调试Java 我通过Emacs / Zsh完成的所有其他工作(编辑JSP / XML / JS,文件管理,SVN签入等)。我还没有发现在Eclipse中进行工作以使这些任务高效甚至可靠的任何方面,因此我不希望与之相关的插件。 在eclipse

  • 问题内容: 我有一个将要推送的特定格式的XML文档。该文档将始终是同一类型,因此非常严格。 我需要对此进行解析,以便将其转换为JSON(嗯,这是一个混蛋版本,以便其他人可以将其与DOJO一起使用)。 我的问题是,我应该使用非常快速的轻量级(不需要SAX等)XML解析器(有什么想法吗?)还是编写我自己的,基本上可以转换为StringBuffer并在数组中旋转?基本上,我假设所有HTML解析器都将旋转

  • 本文向大家介绍jquery.zclip轻量级复制失效问题,包括了jquery.zclip轻量级复制失效问题的使用技巧和注意事项,需要的朋友参考一下 工作原理 利用一个透明的 Flash ,让其漂浮在按钮之上,这样其实点击的不是按钮而是 Flash ,也就可以使用 Flash 的复制功能了 以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持呐喊教程!