<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          zormGo 輕量 ORM

          聯(lián)合創(chuàng)作 · 2023-09-30 01:40

          Go輕量ORM,零依賴,支持達夢(dm)、金倉(kingbase) 、神通(shentong) 、南通(gbase)TDenginemysqlpostgresqloraclemssqlsqlite、db2 、clickhouse...

          源碼: https://gitee.com/chunanyong/zorm
          官網: https://zorm.cn
          測試用例: https://gitee.com/wuxiangege/zorm-examples/

          • 基于原生sql語句編寫,學習成本更低.
          • 代碼生成器
          • 代碼精簡,主體3000行,零依賴5000行,注釋詳細,方便定制修改.
          • 支持事務傳播,這是zorm誕生的主要原因
          • 支持mysql,postgresql,oracle,mssql,sqlite,db2,dm(達夢),kingbase(人大金倉),shentong(神通),gbase(南通),TDengine,clickhouse
          • 支持多庫和讀寫分離
          • 不支持聯(lián)合主鍵,變通認為無主鍵,業(yè)務控制實現(xiàn)(艱難取舍)
          • 支持seata,hptx,dbpack分布式事務,支持全局事務托管,不修改業(yè)務代碼,零侵入分布式事務
          • 支持clickhouse,更新,刪除語句使用SQL92標準語法

          支持國產數(shù)據(jù)庫

          達夢(dm)

          配置zorm.DataSourceConfig的 DriverName:dm ,Dialect:dm
          達夢數(shù)據(jù)庫驅動: gitee.com/chunanyong/dm
          達夢的text類型會映射為dm.DmClob,string不能接收,需要實現(xiàn)zorm.CustomDriverValueConver接口,自定義擴展處理

          import (
          	// 00.引入數(shù)據(jù)庫驅動
          	"gitee.com/chunanyong/dm"
          	"io"
          )
          
          // CustomDMText 實現(xiàn)ICustomDriverValueConver接口,擴展自定義類型,例如 達夢數(shù)據(jù)庫TEXT類型,映射出來的是dm.DmClob類型,無法使用string類型直接接收
          type CustomDMText struct{}
          
          // GetDriverValue 根據(jù)數(shù)據(jù)庫列類型,返回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 數(shù)據(jù)庫列類型,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 進行測試,生產環(huán)境建議使用官方驅動.
          注意修改 data/kingbase.conf中 ora_input_emptystr_isnull = false,因為golang沒有null值,一般數(shù)據(jù)庫都是not null,golang的string默認是'',如果這個設置為true,數(shù)據(jù)庫就會把值設置為null,和字段屬性not null 沖突,因此報錯.

          神通(shentong)

          建議使用官方驅動,配置zorm.DataSourceConfig的 DriverName:aci ,Dialect:shentong

          南通(gbase)

          暫時還未找到官方golang驅動,配置zorm.DataSourceConfig的 DriverName:gbase ,Dialect:gbase
          暫時先使用odbc驅動,DriverName:odbc ,Dialect:gbase

          TDengine

          • 因TDengine驅動不支持事務,需要設置DisableTransaction=true
          • 配置zorm.DataSourceConfig的 DriverName:taosSql或者taosRestful, Dialect:tdengine
          • zorm.DataSourceConfig的TDengineInsertsColumnName TDengine批量insert語句中是否有列名.默認false沒有列名,插入值和數(shù)據(jù)庫列順序保持一致,減少語句長度
          • 測試用例: https://www.yuque.com/u27016943/nrgi00/dnru3f
          • TDengine已收錄: https://github.com/taosdata/awesome-tdengine/#orm

          數(shù)據(jù)庫腳本和實體類

          生成實體類或手動編寫,建議使用代碼生成器 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"`
          
          	// ------------------數(shù)據(jù)庫字段結束,自定義字段寫在下面---------------// 
          	// 如果查詢的字段在column tag中沒有找到,就會根據(jù)名稱(不區(qū)分大小寫,支持 _ 下劃線轉駝峰)映射到struct的屬性上
          
          	// 模擬自定義的字段Active
          	Active int
          }
          
          // GetTableName 獲取表名稱
          // IEntityStruct 接口的方法,實體類需要實現(xiàn)!!!
          func (entity *demoStruct) GetTableName() string {
          	return demoStructTableName
          }
          
          // GetPKColumnName 獲取數(shù)據(jù)庫表的主鍵字段名稱.因為要兼容Map,只能是數(shù)據(jù)庫的字段名稱
          // 不支持聯(lián)合主鍵,變通認為無主鍵,業(yè)務控制實現(xiàn)(艱難取舍)
          // 如果沒有主鍵,也需要實現(xiàn)這個方法, return "" 即可
          // IEntityStruct 接口的方法,實體類需要實現(xiàn)!!!
          func (entity *demoStruct) GetPKColumnName() string {
          	// 如果沒有主鍵
          	// return ""
          	return "id"
          }
          
          
          // GetDefaultValue 獲取列的默認值Map,用于Insert和Update Struct對象,返回map的key是Struct屬性名,value是默認值,value可以是nil.不能是類型的默認值,比如int類型設置默認值為0
          // BindContextDefaultValue 優(yōu)先級高于 GetDefaultValue
          
          //func (entity *EntityStruct) GetDefaultValue() map[string]interface{} {
          //	return map[string]interface{}{"CreateTime": time.Now(),"Active":nil}
          //}
          
          // newDemoStruct 創(chuàng)建一個默認對象
          func newDemoStruct() demoStruct {
          	demo := demoStruct{
          		// 如果Id=="",保存時zorm會調用zorm.FuncGenerateStringID(ctx),默認時間戳+隨機數(shù),也可以自己定義實現(xiàn)方式,例如 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作為載體
          // 占位符統(tǒng)一使用?,zorm會根據(jù)數(shù)據(jù)庫類型,自動替換占位符,例如postgresql數(shù)據(jù)庫把?替換成$1,$2...
          // zorm使用 ctx context.Context 參數(shù)實現(xiàn)事務傳播,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.引入數(shù)據(jù)庫驅動
          	_ "github.com/go-sql-driver/mysql"
          )
          
          // dbDao 代表一個數(shù)據(jù)庫,如果有多個數(shù)據(jù)庫,就對應聲明多個DBDao
          var dbDao *zorm.DBDao
          
          // 01.初始化DBDao
          func init() {
          
          	// 自定義zorm日志輸出
          	// zorm.LogCallDepth = 4 // 日志調用的層級
          	// zorm.FuncLogError = myFuncLogError // 記錄異常日志的函數(shù)
          	// zorm.FuncLogPanic = myFuncLogPanic // 記錄panic日志,默認使用defaultLogError實現(xiàn)
          	// zorm.FuncPrintSQL = myFuncPrintSQL // 打印sql的函數(shù)
          
          	// 自定義日志輸出格式,把FuncPrintSQL函數(shù)重新賦值
          	// log.SetFlags(log.LstdFlags)
          	// zorm.FuncPrintSQL = zorm.FuncPrintSQL
          
          	// 自定義主鍵生成
          	// zorm.FuncGenerateStringID=funcmyId
          
          	// 自定義Tag列名
          	// zorm.FuncWrapFieldTagName=funcmyTagName
          
          	// 自定義decimal類型實現(xiàn),例如github.com/shopspring/decimal
          	// zorm.FuncDecimalValue=funcmyDecimal
          
          	// Go數(shù)據(jù)庫驅動列表:https://github.com/golang/go/wiki/SQLDrivers
          
          	// dbDaoConfig 數(shù)據(jù)庫的配置.這里只是模擬,生產應該是讀取配置配置文件,構造DataSourceConfig
          	dbDaoConfig := zorm.DataSourceConfig{
          		// DSN 數(shù)據(jù)庫的連接字符串,parseTime=true會自動轉換為time格式,默認查詢出來的是[]byte數(shù)組.&loc=Local用于設置時區(qū)
          		DSN: "root:root@tcp(127.0.0.1:3306)/zorm?charset=utf8&parseTime=true&loc=Local",
          		// DriverName 數(shù)據(jù)庫驅動名稱:mysql,postgres,oracle(go-ora),sqlserver,sqlite3,go_ibm_db,clickhouse,dm,kingbase,aci,taosSql|taosRestful 和Dialect對應
          		// sql.Open(DriverName,DSN) DriverName就是驅動的sql.Open第一個字符串參數(shù),根據(jù)驅動實際情況獲取
          		DriverName: "mysql",
          		// Dialect 數(shù)據(jù)庫方言:mysql,postgresql,oracle,mssql,sqlite,db2,clickhouse,dm,kingbase,shentong,tdengine 和 DriverName 對應
          		Dialect: "mysql",
          		// MaxOpenConns 數(shù)據(jù)庫最大連接數(shù) 默認50
          		MaxOpenConns: 50,
          		// MaxIdleConns 數(shù)據(jù)庫最大空閑連接數(shù) 默認50
          		MaxIdleConns: 50,
          		// ConnMaxLifetimeSecond 連接存活秒時間. 默認600(10分鐘)后連接被銷毀重建.避免數(shù)據(jù)庫主動斷開連接,造成死連接.MySQL默認wait_timeout 28800秒(8小時)
          		ConnMaxLifetimeSecond: 600,
          		// SlowSQLMillis 慢sql的時間閾值,單位毫秒.小于0是禁用SQL語句輸出;等于0是只輸出SQL語句,不計算執(zhí)行時間;大于0是計算SQL執(zhí)行時間,并且>=SlowSQLMillis值
          		SlowSQLMillis: 0,
          		// DefaultTxOptions 事務隔離級別的默認配置,默認為nil
          		// DefaultTxOptions: nil,
          		// 如果是使用分布式事務,建議使用默認配置
          		// DefaultTxOptions: &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false},
          
          		// FuncGlobalTransaction seata/hptx全局分布式事務的適配函數(shù),返回IGlobalTransaction接口的實現(xiàn)
          		// 業(yè)務必須調用 ctx,_=zorm.BindContextEnableGlobalTransaction(ctx) 開啟全局分布事務
          		// FuncGlobalTransaction : MyFuncGlobalTransaction,
          
          		// SQLDB 使用現(xiàn)有的數(shù)據(jù)庫連接,優(yōu)先級高于DSN
          		// SQLDB : nil,
          
          		// DisableTransaction 禁用事務,默認false,如果設置了DisableTransaction=true,Transaction方法失效,不再要求有事務,為了處理某些數(shù)據(jù)庫不支持事務,比如TDengine
          		// 禁用事務應該有驅動偽造事務API,不應該有orm實現(xiàn),clickhouse的驅動就是這樣做的
          		// DisableTransaction :false,
          
          		// TDengineInsertsColumnName TDengine批量insert語句中是否有列名.默認false沒有列名,插入值和數(shù)據(jù)庫列順序保持一致,減少語句長度
          		// TDengineInsertsColumnName :false,
          	}
          
          	// 根據(jù)dbDaoConfig創(chuàng)建dbDao, 一個數(shù)據(jù)庫只執(zhí)行一次,第一個執(zhí)行的數(shù)據(jù)庫為 defaultDao,后續(xù)zorm.xxx方法,默認使用的就是defaultDao
          	dbDao, _ = zorm.NewDBDao(&dbDaoConfig)
          }
          
          // TestInsert 02.測試保存Struct對象
          func TestInsert(t *testing.T) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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) {
          		// 創(chuàng)建一個demo對象
          		demo := newDemoStruct()
          
          		// 保存對象,參數(shù)是對象指針.如果主鍵是自增,會賦值到對象的主鍵屬性
          		_, 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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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)
          
          		// 創(chuàng)建對象1
          		demo1 := newDemoStruct()
          		demo1.UserName = "demo1"
          		// 創(chuàng)建對象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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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) {
          		// 創(chuàng)建一個EntityMap,需要傳入表名
          		entityMap := zorm.NewEntityMap(demoStructTableName)
          		// 設置主鍵名稱
          		entityMap.PkColumnName = "id"
          		// 如果是自增序列,設置序列的值
          		// entityMap.PkSequence = "mySequence"
          
          		// Set 設置數(shù)據(jù)庫的字段值
          		// 如果主鍵是自增或者序列,不要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)
          
          		// 執(zhí)行
          		_, 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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	_, 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 ,entityMap2)
          
          		// 執(zhí)行
          		_, err := zorm.InsertEntityMapSlice(ctx, entityMapSlice)
          
          		// 如果返回的err不是nil,事務就會回滾
          		return nil, err
          	})
          	// 標記測試失敗
          	if err != nil {
          		t.Errorf("錯誤:%v", err)
          	}
          }
          
          // TestQueryRow 06.測試查詢一個struct對象
          func TestQueryRow(t *testing.T) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 聲明一個對象的指針,用于承載返回的數(shù)據(jù)
          	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 第一個參數(shù)是語句,后面的參數(shù)是對應的值,值的順序要正確.語句統(tǒng)一使用?,zorm會處理數(shù)據(jù)庫的差異
          	// in (?) 參數(shù)必須有()括號,不能 in ?
          	finder.Append("WHERE id=? and active in(?)", "20210630163227149563000042432429", []int{0, 1})
          
          	// 如何使用like
          	// finder.Append("WHERE id like ? ", "20210630163227149563000042432429%")
          
          	// 執(zhí)行查詢,has為true表示數(shù)據(jù)庫有數(shù)據(jù)
          	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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 構造查詢用的finder
          	// finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
          	finder := zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo
          	// finder.Append 第一個參數(shù)是語句,后面的參數(shù)是對應的值,值的順序要正確.語句統(tǒng)一使用?,zorm會處理數(shù)據(jù)庫的差異
          	// in (?) 參數(shù)必須有()括號,不能 in ?
          	finder.Append("WHERE id=? and active in(?)", "20210630163227149563000042432429", []int{0, 1})
          	// 執(zhí)行查詢
          	resultMap, err := zorm.QueryRowMap(ctx, finder)
          
          	if err != nil { // 標記測試失敗
          		t.Errorf("錯誤:%v", err)
          	}
          	// 打印結果
          	fmt.Println(resultMap)
          }
          
          // TestQuery 08.測試查詢對象列表
          func TestQuery(t *testing.T) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 創(chuàng)建用于接收結果的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
          	// 創(chuàng)建分頁對象,查詢完成后,page對象可以直接給前端分頁組件使用
          	page := zorm.NewPage()
          	page.PageNo = 1   // 查詢第1頁,默認是1
          	page.PageSize = 20 // 每頁20條,默認是20
          
          	// 不查詢總條數(shù)
          	// finder.SelectTotalCount = false
          
          	// 如果是特別復雜的語句,造成count語句構造失敗,可以手動指定count語句
          	// countFinder := zorm.NewFinder().Append("select count(*) from (")
          	// countFinder.AppendFinder(finder)
          	// countFinder.Append(") tempcountfinder")
          	// finder.CountFinder = countFinder
          
          	// 執(zhí)行查詢
          	err := zorm.Query(ctx, finder, &list, page)
          	if err != nil { // 標記測試失敗
          		t.Errorf("錯誤:%v", err)
          	}
          	// 打印結果
          	fmt.Println("總條數(shù):", page.TotalCount, "  列表:", list)
          }
          
          // TestQueryMap 09.測試查詢map列表,用于不方便使用struct的場景,一條記錄是一個map對象
          func TestQueryMap(t *testing.T) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 構造查詢用的finder
          	// finder := zorm.NewSelectFinder(demoStructTableName) // select * from t_demo
          	finder := zorm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo
          	// 創(chuàng)建分頁對象,查詢完成后,page對象可以直接給前端分頁組件使用
          	page := zorm.NewPage()
          	page.PageNo = 1   // 查詢第1頁,默認是1
          	page.PageSize = 20 // 每頁20條,默認是20
          
          	// 不查詢總條數(shù)
          	// finder.SelectTotalCount = false
          
          	// 如果是特別復雜的語句,造成count語句構造失敗,可以手動指定count語句
          	// countFinder := zorm.NewFinder().Append("select count(*) from (")
          	// countFinder.AppendFinder(finder)
          	// countFinder.Append(") tempcountfinder")
          	// finder.CountFinder = countFinder
          
          	// 執(zhí)行查詢
          	listMap, err := zorm.QueryMap(ctx, finder, page)
          	if err != nil { // 標記測試失敗
          		t.Errorf("錯誤:%v", err)
          	}
          	// 打印結果
          	fmt.Println("總條數(shù):", page.TotalCount, "  列表:", listMap)
          }
          
          // TestUpdateNotZeroValue 10.更新struct對象,只更新不為零值的字段.主鍵必須有值
          func TestUpdateNotZeroValue(t *testing.T) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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) {
          		// 聲明一個對象的指針,用于更新數(shù)據(jù)
          		demo := demoStruct{}
          		demo.Id = "20210630163227149563000042432429"
          		demo.UserName = "UpdateNotZeroValue"
          
                  // 指定必須更新的數(shù)據(jù)庫字段,只對UpdateNotZeroValue方法有效.cols是數(shù)據(jù)庫列名切片
          		// ctx里bind的值zorm不會清空,使用時不要覆蓋原始的ctx或者不要傳給多個UpdateNotZeroValue方法.
          		// newCtx, _ := zorm.BindContextMustUpdateCols(ctx, []string{"active"})
          		// _, err := zorm.UpdateNotZeroValue(newCtx, &demo)
          
          		// 更新 "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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// BindContextOnlyUpdateCols 指定僅更新的數(shù)據(jù)庫字段,只對Update方法有效.cols是數(shù)據(jù)庫列名切片
              // ctx里bind的值zorm不會清空,使用時不要覆蓋原始的ctx或者不要傳給多個Update方法.
          	// ctx, _ = zorm.BindContextOnlyUpdateCols(ctx, []string{"userName", "active"})
          
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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) {
          
          		// 聲明一個對象的指針,用于更新數(shù)據(jù)
          		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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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) {
          		// 創(chuàng)建一個EntityMap,需要傳入表名
          		entityMap := zorm.NewEntityMap(demoStructTableName)
          		// 設置主鍵名稱
          		entityMap.PkColumnName = "id"
          		// Set 設置數(shù)據(jù)庫的字段值,主鍵必須有值
          		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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 需要手動開啟事務,匿名函數(shù)返回的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) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	demo := demoStruct{}
          	finder := zorm.NewFinder().Append("call testproc(?) ", "u_10001")
          	zorm.QueryRow(ctx, finder, &demo)
          	fmt.Println(demo)
          }
          
          // TestFunc 16.測試調用自定義函數(shù)
          func TestFunc(t *testing.T) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	userName := ""
          	finder := zorm.NewFinder().Append("select testfunc(?) ", "u_10001")
          	zorm.QueryRow(ctx, finder, &userName)
          	fmt.Println(userName)
          }
          
          // TestOther 17.其他的一些說明.非常感謝您能看到這一行
          func TestOther(t *testing.T) {
          	// ctx 一般一個請求一個ctx,正常應該有web層傳入,例如gin的c.Request.Context().這里只是模擬
          	var ctx = context.Background()
          
          	// 場景1.多個數(shù)據(jù)庫.通過對應數(shù)據(jù)庫的dbDao,調用BindContextDBConnection函數(shù),把這個數(shù)據(jù)庫的連接綁定到返回的ctx上,然后把ctx傳遞到zorm的函數(shù)即可
          	// 也可以重寫FuncReadWriteStrategy函數(shù),通過ctx設置不同的key,返回指定數(shù)據(jù)庫的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的函數(shù)
          	list, _ := zorm.QueryMap(newCtx, finder, nil)
          	fmt.Println(list)
          
          	// 場景2.單個數(shù)據(jù)庫的讀寫分離.設置讀寫分離的策略函數(shù).
          	zorm.FuncReadWriteStrategy = myReadWriteStrategy
          
          	// 場景3.如果是多個數(shù)據(jù)庫,每個數(shù)據(jù)庫還讀寫分離,按照 場景1 處理. 
          	// 也可以重寫FuncReadWriteStrategy函數(shù),通過ctx設置不同的key,返回指定數(shù)據(jù)庫的DBDao
          
          }
          
          // myReadWriteStrategy 數(shù)據(jù)庫的讀寫分離的策略 rwType=0 read,rwType=1 write
          // 也可以通過ctx設置不同的key,返回指定數(shù)據(jù)庫的DBDao
          func myReadWriteStrategy(ctx context.Context, rwType int) (*zorm.DBDao, error) {
          	// 根據(jù)自己的業(yè)務場景,返回需要的讀寫dao,每次需要數(shù)據(jù)庫的連接的時候,會調用這個函數(shù)
          	// if rwType == 0 {
          	// 	return dbReadDao
          	// }
          	// return dbWriteDao
          
          	return DbDao, nil
          }
          
          // --------------------------------------------
          // ICustomDriverValueConver接口,參見達夢的例子
          
          // --------------------------------------------
          // OverrideFunc 重寫ZORM的函數(shù),當你使用這個函數(shù)時,你必須知道自己在做什么
          
          //oldInsertFunc 默認的Insert實現(xiàn)
          var oldInsertFunc func(ctx context.Context, entity IEntityStruct) (int, error)
          
          //newInsertFunc 新的Insert實現(xiàn)
          var newInsertFunc = func(ctx context.Context, entity IEntityStruct) (int, error) {
          	fmt.Println("Insert前")
          	i, err := oldInsertFunc(ctx, entity)
          	fmt.Println("Insert后")
          	return i, err
          }
          
          // 在init函數(shù)中注冊覆蓋老的函數(shù)
          func init() {
          	ok, oldFunc, err := zorm.OverrideFunc("Insert", newInsertFunc)
          	if ok && err == nil {
          		oldInsertFunc = oldFunc.(func(ctx context.Context, entity IEntityStruct) (int, error))
          	}
          }
          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          編輯 分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          編輯 分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  最新中文字幕在线 | 18美女毛片 | 超碰在线观 | 国产成人久久777777 | 久草国产在线视频 |