grm

package module
v0.0.0-...-4937dfd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2022 License: Apache-2.0 Imports: 17 Imported by: 0

README

Introduction

grm logo
This is a lightweight ORM,zero dependency, mysql,postgresql,oracle,mssql,sqlite databases.

Source address:https://github.com/athxx/grm
web site:https://grm.cn

go get github.com/athxx/grm 
  • Written based on native SQL statements,It is the streamlining and optimization of springrain.
  • Built-in code generator
  • The code is streamlined, main part 2500 lines, zero dependency 4000 lines, detailed comments, convenient for customization and modification.
  • Support transaction propagation, which is the main reason for the birth of grm
  • Support mysql, postgresql, oracle, mssql, sqlite
  • Support more databases, read and write separation.
  • The update performance of grm, gorm, and xorm is equivalent. The read performance of grm is twice as fast as that of gorm and xorm.
  • Does not support joint primary keys, alternatively thinks that there is no primary key, and business control is implemented (difficult choice)
  • Support clickhouse, update and delete statements use SQL92 standard syntax. The official clickhouse-go driver does not support batch insert syntax, it is recommended to use https://github.com/mailru/go-clickhouse

grm Production environment reference: UserStructService.go

Test case

// Grm uses native SQL statements and does not impose restrictions on SQL syntax. Statements use Finder as the carrier.
// Use "?" as a placeholder. , Grm automatically replaces placeholders based on the database type, 
// such as "?" in a PostgreSQL database, Replaced with $1, $2...
// Grm uses the ctx context. context parameter to propagate the transaction, and ctx is passed in from the web layer, such as gin's c.retest.context ().
// The transaction operation of grm needs to be displayed using grm.Transaction(ctx, func(ctx context.Context) (interface(), error) ()) to open

Database scripts and entity classes

https://github.com/athxx/readygo/blob/master/test/testgrm/demoStruct.go

Generate entity classes or write manually, it is recommended to use a code generator : https://github.com/athxx/readygo/tree/master/codegenerator


package testgrm

import (
	"time"

	"github.com/athxx/grm"
)

//Table building statement

/*

DROP TABLE IF EXISTS `t_demo`;
CREATE TABLE `t_demo`  (
  `id` varchar(50)  NOT NULL COMMENT 'Primary key',
  `userName` varchar(30)  NOT NULL COMMENT 'Name',
  `password` varchar(50)  NOT NULL COMMENT 'password',
  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
  `active` int  COMMENT 'Is it valid (0 no, 1 yes)',
  PRIMARY KEY (`id`)
) ENGINE = InnoDB CHARACTER SET = utf8mb4  COMMENT = 'example' ;

*/

//demoStructTableName  Table name constant, easy to call directly
const demoStructTableName = "t_demo"

// demoStruct example
type demoStruct struct {
	//Default structs are introduced to insulate IEntityStructs from method changes
	grm.EntityStruct

	//Id: Primary key
	Id string `column:"id"`

	//UserName: Name 
	UserName string `column:"userName"`

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

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

	//Active: Is it valid (0 no, 1 yes)
	//Active int `column:"active"`

	//------------------The end of the database field, the custom field is written below---------------//
	//If the query field is not found in the column tag, it will be mapped to the struct attribute based on the name (case insensitive, _ underscore to hump)

	//Custom field Active
	Active int
}

//TableName: Get the table name
func (entity *demoStruct) TableName() string {
	return demoStructTableName
}

//PK: Get the primary key field name of the database table. Because it is compatible with Map, it can only be the field name of the database.
func (entity *demoStruct) PK() string {
	return "id"
}

//newDemoStruct: Create a default object
func newDemoStruct() demoStruct {
	demo := demoStruct{
		// If Id=="",When saving, grm will call grm.Func Generate String ID(),
        // the default UUID string, or you can define your own implementation,E.g: grm.FuncGenerateStringID=funcmyId
		Id:         grm.FuncGenerateStringID(),
		UserName:   "defaultUserName",
		Password:   "defaultPassword",
		Active:     1,
		CreateTime: time.Now(),
	}
	return demo
}


Test cases are documents


// testgrm: Use native SQL statements, no restrictions on SQL syntax. Statements use Finder as a carrier
// Use "?" as a placeholder. , Grm automatically replaces placeholders based on the database type, 
// such as "?" in a PostgreSQL database, Replaced with $1, $2...
// Grm uses the ctx context. context parameter to propagate the transaction, and ctx is passed in from the web layer, such as gin's c.retest.context ().
// The transaction operation of grm needs to be displayed using grm.Transaction(ctx, func(ctx context.Context) (interface(), error) ()) to open
package testgrm

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

	"github.com/athxx/grm"

	//00.Introduce database driver
	_ "github.com/go-sql-driver/mysql"
)

//dbDao: Represents a database. If there are multiple databases, declare multiple DB Dao accordingly
var dbDao *grm.DBDao

// ctx should be passed in by the web layer by default, such as gin's c.Request.Context(). This is just a simulation.
var ctx = context.Background()

//01.Initialize DB Dao
func init() {

	//Custom grm log output
	//grm.LogCallDepth = 4 //Level of log call
	//grm.FuncLogError = myFuncLogError //Function to record exception log.
	//grm.FuncLogPanic = myFuncLogPanic //Record panic log, use Grm Error Log by default
	//grm.FuncPrintSQL = myFuncPrintSQL //A function that prints SQL

	//Customize the log output format and re-assign the Func Print SQL functio.
	//log.SetFlags(log.LstdFlags)
	//grm.FuncPrintSQL = grm.FuncPrintSQL

	//dbDaoConfig: Database configuration
	dbDaoConfig := grm.DBConfig{
		// DSN: Database connection string
		DSN: "root:root@tcp(127.0.0.1:3306)/readygo?charset=utf8&parseTime=true",
		// Database type (based on dialect judgment): mysql, postgresql,oracle, mssql, sqlite, clickhouse,
		Driver: "mysql",
		//MaxOpenConns: Maximum number of database connections Default 50
		MaxOpenConns: 50,
		//MaxIdleConns: The maximum number of free connections to the database default 50
		MaxIdleConns: 50,
		//MaxLifetime: The connection survival time in seconds. The connection is destroyed and rebuilt after the default 600 (10 minutes). 
        //To prevent the database from actively disconnecting and causing dead connections. MySQL default wait_timeout 28800 seconds (8 hours)
		MaxLifetime: 600,
		//PrintSQL: Print SQL. Func Print SQL will be used to record SQL
		PrintSQL: true,
		//DefaultTxOptions The default configuration of the transaction isolation level, the default is nil
		//DefaultTxOptions: nil,
		//如果是使用seata-golang分布式事务,建议使用默认配置
		//DefaultTxOptions: &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false},

		//SeataGlobalTx seata-golang分布式的适配函数,返回ISeataGlobalTx接口的实现
	    //SeataGlobalTx : MySeataGlobalTx,

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

	// Create dbDao according to dbDaoConfig, a database is executed only once,
    // the first executed database is defaultDao, and subsequent grm.xxx methods, defaultDao is used by default.
	dbDao, _ = grm.NewDao(&dbDaoConfig)
}

//TestInsert: 02.Test save Struct object
func TestInsert(t *testing.T) {

	//You need to manually start the transaction. 
    //If the error returned by the anonymous function is not nil, the transaction will be rolled back.
	//If the global DefaultTxOptions configuration does not meet the requirements, you can set the isolation level of the transaction before the grm.Transaction transaction method, such as ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}), if txOptions is nil , Use the global DefaultTxOptions
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//Create a demo object
		demo := newDemoStruct()

		// Save the object, the parameter is the object pointer. 
        // If the primary key is incremented, it will be assigned to the primary key attribute of the object
		_, err := grm.Insert(ctx, &demo)

		//If the returned err is not nil, the transaction will be rolled back.
		return nil, err
	})
	//Mark test failed.
	if err != nil {
		t.Errorf("err:%v", err)
	}
}

//TestInsertSlice 03.Test the Slice that saves Struct objects in batches.
//If it is an auto-increasing primary key, you cannot assign a value to the primary key attribute in the Struct object.
func TestInsertSlice(t *testing.T) {

	// You need to manually start the transaction. 
    // If the error returned by the anonymous function is not nil, the transaction will be rolled back.
	//If the global DefaultTxOptions configuration does not meet the requirements, you can set the isolation level of the transaction before the grm.Transaction transaction method, such as ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}), if txOptions is nil , Use the global DefaultTxOptions
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {

		//The type stored by slice is grm.I Entity Struct!!!, golang currently does not have generics, 
        //uses the I Entity Struct interface, and is compatible with the Struct entity class.
		demoSlice := make([]grm.IEntityStruct, 0)

		//Create object 1
		demo1 := newDemoStruct()
		demo1.UserName = "demo1"
		//Create object 2
		demo2 := newDemoStruct()
		demo2.UserName = "demo2"

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

		//To save objects in batches, if the primary key is auto-increment, the auto-increment ID cannot be saved in the object.
		_, err := grm.InsertSlice(ctx, demoSlice)

		//If the returned err is not nil, the transaction will be rolled back.
		return nil, err
	})
	//Mark test failed.
	if err != nil {
		t.Errorf("错误:%v", err)
	}
}

//TestInsertEntityMap 04.Test to save the Entity Map object for scenarios where it is not convenient to use struct, using Map as a carrier
func TestInsertEntityMap(t *testing.T) {

	// You need to manually start the transaction. If the error returned by the anonymous function is not nil, the transaction will be rolled back.
	//If the global DefaultTxOptions configuration does not meet the requirements, you can set the isolation level of the transaction before the grm.Transaction transaction method, such as ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}), if txOptions is nil , Use the global DefaultTxOptions
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//To create an Entity Map, you need to pass in the table name.
		entityMap := grm.NewEntityMap(demoStructTableName)
		//Set the primary key name.
		entityMap.PkColumnName = "id"
		//If it is an auto-increasing sequence, set the value of the sequence.
		//entityMap.PkSequence = "mySequence"

		//Set Set the field value of the database
		//If the primary key is auto-increment or sequence, don't entity Map.Set the value of the primary key.
		entityMap.Set("id", grm.FuncGenerateStringID())
		entityMap.Set("userName", "entityMap-userName")
		entityMap.Set("password", "entityMap-password")
		entityMap.Set("createTime", time.Now())
		entityMap.Set("active", 1)

		//carried out
		_, err := grm.InsertEntityMap(ctx, entityMap)

		//If the returned err is not nil, the transaction will be rolled back
		return nil, err
	})
	//Mark test failed
	if err != nil {
		t.Errorf("error:%v", err)
	}
}

//TestQueryRow 05.Test query a struct object
func TestQueryRow(t *testing.T) {

	//Declare a pointer to an object to carry the returned data.
	demo := &demoStruct{}

	//Finder for constructing query.
	finder := grm.NewSelectFinder(demoStructTableName) // select * from t_demo
	//finder = grm.NewSelectFinder(demoStructTableName, "id,userName") // select id,userName from t_demo
	//finder = grm.NewFinder().Append("SELECT * FROM " + demoStructTableName) // select * from t_demo

	// finder.Append: The first parameter is the statement, and the following parameters are the corresponding values.
    // The order of the values ​​must be correct. Use the statement uniformly? Grm will handle the difference in the database
	finder.Append("WHERE id=? and active in(?)", "41b2aa4f-379a-4319-8af9-08472b6e514e", []int{0, 1})

	//Execute query
	has,err := grm.QueryRow(ctx, finder, demo)

	if err != nil { //Mark test failed
		t.Errorf("error:%v", err)
	}
	if has { //数据库存在数据
		//Print result
		fmt.Println(demo)
	}
	
}

//TestQueryRowMap 06.Test query map receiving results, used in scenarios that are not suitable for struct, more flexible
func TestQueryRowMap(t *testing.T) {

	//Finder for constructing query.
	finder := grm.NewSelectFinder(demoStructTableName) // select * from t_demo
	//finder.Append: The first parameter is the statement, and the following parameters are the corresponding values. 
    //The order of the values ​​must be correct. Use the statement uniformly? Grm will handle the difference in the database
	finder.Append("WHERE id=? and active in(?)", "41b2aa4f-379a-4319-8af9-08472b6e514e", []int{0, 1})
	//Execute query
	resultMap, err := grm.QueryRowMap(ctx, finder)

	if err != nil { //Mark test failed
		t.Errorf("error:%v", err)
	}
	//Print result
	fmt.Println(resultMap)
}

//TestQuery 07.Test query object list
func TestQuery(t *testing.T) {
	//Create a slice to receive the result
	list := make([]*demoStruct, 0)

	//Finder for constructing query
	finder := grm.NewSelectFinder(demoStructTableName) // select * from t_demo
	//Create a paging object. After the query is completed, the page object can be directly used by the front-end paging component.
	page := grm.NewPage()
	page.PageNo = 1    //Query page 1, default is 1
	page.PageSize = 20 //20 items per page, the default is 20

	//Execute query.如果想不分页,查询所有数据,page传入nil
	err := grm.Query(ctx, finder, &list, page)
	if err != nil { //Mark test failed
		t.Errorf("error:%v", err)
	}
	//Print result
	fmt.Println("Total number:", page.TotalCount, "  List:", list)
}

//TestQueryMap 08.Test query map list, used in scenarios where struct is not convenient, a record is a map object.
func TestQueryMap(t *testing.T) {
	//Finder for constructing query.
	finder := grm.NewSelectFinder(demoStructTableName) // select * from t_demo
	
	//Create a paging object. After the query is completed, the page object can be directly used by the front-end paging component。
	page := grm.NewPage()

	//Execute query
	listMap, err := grm.QueryMap(ctx, finder, page)
	if err != nil { //Mark test failed
		t.Errorf("error:%v", err)
	}
	//Print result.如果不想分页,查询所有数据,page传入nil
	fmt.Println("Total number:", page.TotalCount, "  List:", listMap)
}

//TestUpdateNotZeroValue 09.Update the struct object, only update fields that are not zero. The primary key must have a value.
func TestUpdateNotZeroValue(t *testing.T) {

	// You need to manually start the transaction. If the error returned by the anonymous function is not nil,
    // the transaction will be rolled back.
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//Declare a pointer to an object to update data
		demo := &demoStruct{}
		demo.Id = "41b2aa4f-379a-4319-8af9-08472b6e514e"
		demo.UserName = "UpdateNotZeroValue"

		//Update "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["UpdateNotZeroValue","41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := grm.UpdateNotZeroValue(ctx, demo)

		//If the returned err is not nil, the transaction will be rolled back.
		return nil, err
	})
	if err != nil { 
        //Mark test failed
		t.Errorf("error:%v", err)
	}
}

//TestUpdate 10.Update the struct object, update all fields. The primary key must have a value.
func TestUpdate(t *testing.T) {

	// You need to manually start the transaction. 
    // If the error returned by the anonymous function is not nil, the transaction will be rolled back.
	//If the global DefaultTxOptions configuration does not meet the requirements, you can set the isolation level of the transaction before the grm.Transaction transaction method, such as ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}), if txOptions is nil , Use the global DefaultTxOptions
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {

		//Declare a pointer to an object to update data.
		demo := &demoStruct{}
		demo.Id = "41b2aa4f-379a-4319-8af9-08472b6e514e"
		demo.UserName = "TestUpdate"

		_, err := grm.Update(ctx, demo)

		//If the returned err is not nil, the transaction will be rolled back.
		return nil, err
	})
	if err != nil { 
        //Mark test failed
		t.Errorf("error:%v", err)
	}
}

//TestUpdateFinder 11.Through finder update, grm is the most flexible way, you can write any update statement, 
// or even manually write insert statement
func TestUpdateFinder(t *testing.T) {
	//You need to manually start the transaction. If the error returned by the anonymous function is not nil, the transaction will be rolled back.
	//If the global DefaultTxOptions configuration does not meet the requirements, you can set the isolation level of the transaction before the grm.Transaction transaction method, such as ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}), if txOptions is nil , Use the global DefaultTxOptions
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		finder := grm.NewUpdateFinder(demoStructTableName) // UPDATE t_demo SET
		//finder = grm.NewDeleteFinder(demoStructTableName)  // DELETE FROM t_demo
		//finder = grm.NewFinder().Append("UPDATE").Append(demoStructTableName).Append("SET") // UPDATE t_demo SET
		finder.Append("userName=?,active=?", "TestUpdateFinder", 1).Append("WHERE id=?", "41b2aa4f-379a-4319-8af9-08472b6e514e")

		//Update "sql":"UPDATE t_demo SET  userName=?,active=? WHERE id=?","args":["TestUpdateFinder",1,"41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := grm.UpdateFinder(ctx, finder)

		//If the returned err is not nil, the transaction will be rolled back.
		return nil, err
	})
	if err != nil { //Mark test failed
		t.Errorf("error:%v", err)
	}
}

//TestUpdateEntityMap 12.Update an Entity Map, the primary key must have a value
func TestUpdateEntityMap(t *testing.T) {
	//You need to manually start the transaction. 
    //If the error returned by the anonymous function is not nil, the transaction will be rolled back.
	//If the global DefaultTxOptions configuration does not meet the requirements, you can set the isolation level of the transaction before the grm.Transaction transaction method, such as ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}), if txOptions is nil , Use the global DefaultTxOptions
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		//To create an Entity Map, you need to pass in the table name.
		entityMap := grm.NewEntityMap(demoStructTableName)
		//Set the primary key name.
		entityMap.PkColumnName = "id"
		//Set: Set the field value of the database, the primary key must have a value.
		entityMap.Set("id", "41b2aa4f-379a-4319-8af9-08472b6e514e")
		entityMap.Set("userName", "TestUpdateEntityMap")
		//Update "sql":"UPDATE t_demo SET userName=? WHERE id=?","args":["TestUpdateEntityMap","41b2aa4f-379a-4319-8af9-08472b6e514e"]
		_, err := grm.UpdateEntityMap(ctx, entityMap)

		//If the returned err is not nil, the transaction will be rolled back.
		return nil, err
	})
	if err != nil { 
        //Mark test failed
		t.Errorf("error:%v", err)
	}
}

//TestDelete 13.To delete a struct object, the primary key must have a value.
func TestDelete(t *testing.T) {
	//You need to manually start the transaction. If the error returned by the anonymous function is not nil, the transaction will be rolled back.
	//If the global DefaultTxOptions configuration does not meet the requirements, you can set the isolation level of the transaction before the grm.Transaction transaction method, such as ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false}), if txOptions is nil , Use the global DefaultTxOptions
	_, err := grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
		demo := &demoStruct{}
		demo.Id = "ae9987ac-0467-4fe2-a260-516c89292684"

		//delete: "sql":"DELETE FROM t_demo WHERE id=?","args":["ae9987ac-0467-4fe2-a260-516c89292684"]
		_, err := grm.Delete(ctx, demo)

		//If the returned err is not nil, the transaction will be rolled back.
		return nil, err
	})
	if err != nil { 
        //Mark test failed
		t.Errorf("error:%v", err)
	}
}


//TestProc 14.Test call stored procedure
func TestProc(t *testing.T) {
	demo := &demoStruct{}
	finder := grm.NewFinder().Append("call testproc(?) ", "u_10001")
	grm.QueryRow(ctx, finder, demo)
	fmt.Println(demo)
}

//TestFunc 15.Test call custom function.
func TestFunc(t *testing.T) {
	userName := ""
	finder := grm.NewFinder().Append("select testfunc(?) ", "u_10001")
	grm.QueryRow(ctx, finder, &userName)
	fmt.Println(userName)
}

//TestOther 16.Some other instructions. Thank you very much for seeing this line.
func TestOther(t *testing.T) {

	//Scenario 1. Multiple databases. Through the db Dao of the corresponding database, call the Bind Context DB Connection function, 
    //bind the connection of this database to the returned ctx, and then pass the ctx to the grm function.
	newCtx, err := dbDao.BindCtxDBConn(ctx)
	if err != nil {
         //Mark test failed
		t.Errorf("error:%v", err)
	}

	finder := grm.NewSelectFinder(demoStructTableName)
	//Pass the newly generated new Ctx to the function of grm.
	list, _ := grm.QueryRowMap(newCtx, finder, nil)
	fmt.Println(list)

	//Scenario 2. Read-write separation of a single database. 
    //Set the strategy function for read-write separation.
	grm.FuncReadWriteStrategy = myReadWriteStrategy

	//Scenario 3. If there are multiple databases, 
    //each database is also separated from reading and writing, and processed according to scenario 1.
}

//Strategies for the separation of read and write of a single database rwType=0 read,rwType=1 write
func myReadWriteStrategy(rwType int) *grm.DBDao {
	//According to your own business scenario, return the required read and write dao, and call this function every time you need a database connection
	return dbDao
}

//---------------------------------//

//To implement the interface of CustomDriverValueConver,extend the custom type, such as text type of dm database, the mapped type is dm.DmClob type , cannot use string type to receive directly.
type CustomDMText struct{}
//GetDriverValue according to the database column type and entity class field type, return driver.Value Instance. If the return value is nil, no type replacement is performed and the default method is used.
func (dmtext CustomDMText) GetDriverValue(columnType *sql.ColumnType, structFieldType *reflect.Type, finder *grm.Finder) (driver.Value, error) {
	return &dm.DmClob{}, nil
}

//ConverDriverValue database column type, entity class field type, GetDriverValue returned driver.Value New value, return the pointer according to the receiving type value, pointer, pointer!!!!
func (dmtext CustomDMText) ConverDriverValue(columnType *sql.ColumnType, structFieldType *reflect.Type, tempDriverValue driver.Value, finder *grm.Finder) (interface{}, error) {
	//Type conversion
	dmClob, isok := tempDriverValue.(*dm.DmClob)
	if !isok {
		return tempDriverValue, errors.New("Conversion to *dm.DmClob type failed")
	}

	//Get the length
	dmlen, errLength := dmClob.GetLength()
	if errLength != nil {
		return dmClob, errLength
	}

	//Convert int64 to int type
	strInt64 := strconv.FormatInt(dmlen, 10)
	dmlenInt, errAtoi := strconv.Atoi(strInt64)
	if errAtoi != nil {
		return dmClob, errAtoi
	}

	//Read string
	str, errReadString := dmClob.ReadString(1, dmlenInt)
	return &str, errReadString
}
//grm.CustomDriverValueMap for configuration driver.Value and the corresponding processing relationship, key is the string of drier.Value. For example *dm.DmClob
//It is usually added in the init method
grm.CustomDriverValueMap["*dm.DmClob"] = CustomDMText{}


Distributed transaction

Implement distributed transactions based on seata-golang.

Proxy mode

//DBConfig configuration DefaultTxOptions
//DefaultTxOptions: &sql.TxOptions{Isolation: sql.LevelDefault, ReadOnly: false},

// Introduce the dependency package of the V1 version, refer to the official example of V2
import (
"github.com/opentrx/mysql"
"github.com/transaction-wg/seata-golang/pkg/client"
"github.com/transaction-wg/seata-golang/pkg/client/config"
"github.com/transaction-wg/seata-golang/pkg/client/rm"
"github.com/transaction-wg/seata-golang/pkg/client/tm"
seataContext "github.com/transaction-wg/seata-golang/pkg/client/context"
)

//Configuration file path
var configPath = "./conf/client.yml"

func main() {

//Initial configuration
conf := config.InitConf(configPath)
//Initialize the RPC client
client.NewRpcClient()
//Register mysql driver
mysql.InitDataResourceManager()
mysql.RegisterResource(config.GetATConfig().DSN)
//sqlDB, err := sql.Open("mysql", config.GetATConfig().DSN)


//Subsequent normal initialization of grm must be placed after the initialization of seata mysql!!!

//................//
//tm registration transaction service, refer to the official example. (Global hosting is mainly to remove the proxy, zero intrusion to the business)
tm.Implement(svc.ProxySvc)
//................//


//Get the rootContext of seata
rootContext := seataContext.NewRootContext(ctx)
//rootContext := ctx.(*seataContext.RootContext)

//Create seata transaction
seataTx := tm.GetCurrentOrCreate(rootContext)

//Start transaction
seataTx.BeginWithTimeoutAndName(int32(6000), "transaction name", rootContext)

//Get the XID after the transaction is opened. It can be passed through the header of gin, or passed in other ways
xid:=rootContext.GetXID()

// Accept the passed XID and bind it to the local ctx
ctx =context.WithValue(ctx,mysql.XID,xid)

}

Global hosting mode


//Do not use proxy mode, global hosting, do not modify business code, zero intrusion to achieve distributed transactions
//tm.Implement(svc.ProxySvc)

// It is recommended to put the following code in a separate file
//................//

// GrmSeataGlobalTx wraps *tm.DefaultGlobalTx of seata, and implements the grm.ISeataGlobalTx interface
type GrmSeataGlobalTx struct {
*tm.DefaultGlobalTx
}

// MySeataGlobalTx grm adapts the function of seata distributed transaction, configure grm.DBConfig.SeataGlobalTx=MySeataGlobalTx
func MySeataGlobalTx(ctx context.Context) (grm.ISeataGlobalTx, context.Context, error) {
//Get the rootContext of seata
rootContext := seataContext.NewRootContext(ctx)
//Create seata transaction
seataTx := tm.GetCurrentOrCreate(rootContext)
//Use the grm.ISeataGlobalTx interface object to wrap the seata transaction and isolate the seata-golang dependency
seataGlobalTx := GrmSeataGlobalTx{seataTx}

return seataGlobalTx, rootContext, nil
}

//Implement the grm.ISeataGlobalTx interface
func (gtx GrmSeataGlobalTx) SeataBegin(ctx context.Context) error {
rootContext := ctx.(*seataContext.RootContext)
return gtx.BeginWithTimeout(int32(6000), rootContext)
}

func (gtx GrmSeataGlobalTx) SeataCommit(ctx context.Context) error {
rootContext := ctx.(*seataContext.RootContext)
return gtx.Commit(rootContext)
}

func (gtx GrmSeataGlobalTx) SeataRollback(ctx context.Context) error {
rootContext := ctx.(*seataContext.RootContext)
return gtx.Rollback(rootContext)
}

func (gtx GrmSeataGlobalTx) GetSeataXID(ctx context.Context) string {
rootContext := ctx.(*seataContext.RootContext)
return rootContext.GetXID()
}

//................//

Performance stress test

Test code:https://github.com/alphayan/goormbenchmark

Index description Total time, average number of nanoseconds per time, average memory allocated per time, average number of memory allocated per time.

The update performance of grm, gorm, and xorm is equivalent. The read performance of grm is twice as fast as that of gorm and xorm.

2000 times - Insert
      grm:     9.05s      4524909 ns/op    2146 B/op     33 allocs/op
      gorm:     9.60s      4800617 ns/op    5407 B/op    119 allocs/op
      xorm:    12.63s      6315205 ns/op    2365 B/op     56 allocs/op

    2000 times - BulkInsert 100 row
      xorm:    23.89s     11945333 ns/op  253812 B/op   4250 allocs/op
      gorm:     Don't support bulk insert - https://github.com/jinzhu/gorm/issues/255
      grm:     Don't support bulk insert

    2000 times - Update
      xorm:     0.39s       195846 ns/op    2529 B/op     87 allocs/op
      grm:     0.51s       253577 ns/op    2232 B/op     32 allocs/op
      gorm:     0.73s       366905 ns/op    9157 B/op    226 allocs/op

  2000 times - Read
      grm:     0.28s       141890 ns/op    1616 B/op     43 allocs/op
      gorm:     0.45s       223720 ns/op    5931 B/op    138 allocs/op
      xorm:     0.55s       276055 ns/op    8648 B/op    227 allocs/op

  2000 times - MultiRead limit 1000
      grm:    13.93s      6967146 ns/op  694286 B/op  23054 allocs/op
      gorm:    26.40s     13201878 ns/op 2392826 B/op  57031 allocs/op
      xorm:    30.77s     15382967 ns/op 1637098 B/op  72088 allocs/op

Documentation

Overview

Package grm 使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体 占位符统一使用?,grm会根据数据库类型,语句执行前会自动替换占位符,postgresql 把?替换成$1,$2...;mssql替换成@P1,@p2...;orace替换成:1,:2... grm使用 ctx context.Context 参数实现事务传播,ctx从web层传递进来即可,例如gin的c.Request.Context() grm的事务操作需要显示使用grm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {})开启 "package grm" Use native SQL statements, no restrictions on SQL syntax. Statements use Finder as a carrier Use placeholders uniformly "?" "grm" automatically replaces placeholders before statements are executed,depending on the database type. Replaced with $1, $2... ; Replace MSSQL with @p1,@p2... ; Orace is replaced by :1,:2..., "grm" uses the "ctx context.Context" parameter to achieve transaction propagation,and ctx can be passed in from the web layer, such as "gin's c.Request.Context()", "grm" Transaction operations need to be displayed using "grm.transaction" (ctx, func(ctx context.context) (interface{}, error) {})

Index

Constants

This section is empty.

Variables

View Source
var (
	//LogDepth Log Call Depth Record the log call level, used to locate the business layer code
	LogDepth                                         = 4
	LogErr   func(err string) error                  = logErr
	LogSQL   func(sqlStr string, args []interface{}) = logSQL
)
View Source
var CustomDriverValueMap = make(map[string]CustomDriverValueConvert)

CustomDriverValueMap 用于配置driver.Value和对应的处理关系,key是 drier.Value 的字符串,例如 *dm.DmClob 一般是放到init方法里进行添加

View Source
var FuncGenerateStringID func() string = generateStringID

FuncGenerateStringID Function to generate string ID by default. Convenient for custom extension

View Source
var FuncReadWriteStrategy func(rwType int) *DBDao = getDefaultDao

FuncReadWriteStrategy 单个数据库的读写分离的策略,用于外部复写实现自定义的逻辑,rwType=0 read,rwType=1 write 不能归属到DBDao里,BindCtxDBConn已经是指定数据库的连接了,和这个函数会冲突.就作为单数据库读写分离的处理方式 即便是放到DBDao里,因为是多库,BindCtxDBConn函数调用少不了,业务包装一个方法,指定一下读写获取一个DBDao效果是一样的,唯一就是需要根据业务指定一下读写,其实更灵活了 FuncReadWriteStrategy Single database read and write separation strategy,used for external replication to implement custom logic, rwType=0 read, rwType=1 write. "BindCtxDBConn" is already a connection to the specified database and will conflict with this function. As a single database read and write separation of processing

Functions

func Delete

func Delete(ctx context.Context, entity IEntityStruct) (int, error)

Delete 根据主键删除一个对象.必须是IEntityStruct类型 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection affected影响的行数,如果异常或者驱动不支持,返回-1

func ErrIsDuplicate

func ErrIsDuplicate(err error)

func ErrIsEmpty

func ErrIsEmpty(err error)

func Insert

func Insert(ctx context.Context, entity IEntityStruct) (int, error)

Insert 保存Struct对象,必须是IEntityStruct类型 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection affected影响的行数,如果异常或者驱动不支持,返回-1 Insert saves the Struct object, which must be of type IEntityStruct ctx cannot be nil, refer to grm.Transaction method to pass in ctx. Don't build dbConn yourself The number of rows affected by affected, if it is abnormal or the driver does not support it, return -1

func InsertEntityMap

func InsertEntityMap(ctx context.Context, entity IEntityMap) (int, error)

InsertEntityMap 保存*IEntityMap对象.使用Map保存数据,用于不方便使用struct的场景,如果主键是自增或者序列,不要entityMap.Set主键的值 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection affected影响的行数,如果异常或者驱动不支持,返回-1

func InsertSlice

func InsertSlice(ctx context.Context, entityStructSlice []IEntityStruct) (int, error)

InsertSlice 批量保存Struct Slice 数组对象,必须是[]IEntityStruct类型,golang目前没有泛型,使用IEntityStruct接口,兼容Struct实体类 如果是自增主键,无法对Struct对象里的主键属性赋值 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection affected影响的行数,如果异常或者驱动不支持,返回-1

func Query

func Query(ctx context.Context, finder *Finder, rowsSlicePtr interface{}, page *Page) error

Query 不要偷懒调用QueryMap,需要处理sql驱动支持的sql.Nullxxx的数据类型,也挺麻烦的 根据Finder和封装为指定的entity类型,entity必须是*[]struct类型,已经初始化好的数组,此方法只Append元素,这样调用方就不需要强制类型转换了 context必须传入,不能为空.如果想不分页,查询所有数据,page传入nil Query:Don't be lazy to call QueryMap, you need to deal with the sql,Nullxxx data type supported by the sql driver, which is also very troublesome According to the Finder and encapsulation for the specified entity type, the entity must be of the *[]struct type, which has been initialized,This method only Append elements, so the caller does not need to force type conversion context must be passed in and cannot be empty

func QueryMap

func QueryMap(ctx context.Context, finder *Finder, page *Page) ([]map[string]interface{}, error)

QueryMap 根据Finder查询,封装Map数组 根据数据库字段的类型,完成从[]byte到golang类型的映射,理论上其他查询方法都可以调用此方法,但是需要处理sql.Nullxxx等驱动支持的类型 context必须传入,不能为空 QueryMap According to Finder query, encapsulate Map array According to the type of database field, the mapping from []byte to golang type is completed. In theory,other query methods can call this method, but need to deal with types supported by drivers such as sql.Nullxxx context must be passed in and cannot be empty

func QueryRow

func QueryRow(ctx context.Context, finder *Finder, entity interface{}) (bool, error)

QueryRow 不要偷懒调用Query返回第一条,问题1.需要构建一个slice,问题2.调用方传递的对象其他值会被抛弃或者覆盖. 根据Finder和封装为指定的entity类型,entity必须是*struct类型或者基础类型的指针.把查询的数据赋值给entity,所以要求指针类型 context必须传入,不能为空 如果数据库是null,基本类型不支持,会返回异常,不做默认值处理,Query因为是列表,会设置为默认值 QueryRow Don't be lazy to call Query to return the first one Question 1. A slice needs to be constructed, and question 2. Other values of the object passed by the caller will be discarded or overwritten context must be passed in and cannot be empty

func QueryRowMap

func QueryRowMap(ctx context.Context, finder *Finder) (map[string]interface{}, error)

QueryRowMap 根据Finder查询,封装Map context必须传入,不能为空 QueryRowMap encapsulates Map according to Finder query context must be passed in and cannot be empty

func Transaction

func Transaction(ctx context.Context, doTransaction func(ctx context.Context) (interface{}, error)) (interface{}, error)

Transaction 的示例代码

  //匿名函数return的error如果不为nil,事务就会回滚
  grm.Transaction(ctx context.Context,func(ctx context.Context) (interface{}, error) {

	  //业务代码

	  //return的error如果不为nil,事务就会回滚
      return nil, nil
  })

事务方法,隔离dbConn相关的API.必须通过这个方法进行事务处理,统一事务方式 如果入参ctx中没有dbConn,使用defaultDao开启事务并最后提交 如果入参ctx有dbConn且没有事务,调用dbConn.begin()开启事务并最后提交 如果入参ctx有dbConn且有事务,只使用不提交,有开启方提交事务 但是如果遇到错误或者异常,虽然不是事务的开启方,也会回滚事务,让事务尽早回滚 在多库的场景,手动获取dbConn,然后绑定到一个新的context,传入进来 不要去掉匿名函数的context参数,因为如果Transaction的context中没有dbConn,会新建一个context并放入dbConn,此时的context指针已经变化,不能直接使用Transaction的context参数 bug(springrain)如果有大神修改了匿名函数内的参数名,例如改为ctx2,这样业务代码实际使用的是Transaction的context参数,如果为没有dbConn,会抛异常,如果有dbConn,实际就是一个对象.影响有限.也可以把匿名函数抽到外部 如果全局DefaultTxOptions配置不满足需求,可以在grm.Transaction事务方法前设置事务的隔离级别,例如 ctx, _ := dbDao.BindCtxTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault}),如果txOptions为nil,使用全局DefaultTxOptions return的error如果不为nil,事务就会回滚 分布式事务需要传递XID,接收方context.WithValue(ctx, "XID", XID)绑定到ctx 如果分支事务出现异常或者回滚,会立即回滚分布式事务 Transaction method, isolate db Connection related API. This method must be used for transaction processing and unified transaction mode If there is no db Connection in the input ctx, use default Dao to start the transaction and submit it finally If the input ctx has db Connection and no transaction, call db Connection.begin() to start the transaction and finally commit If the input ctx has a db Connection and a transaction, only use non-commit, and the open party submits the transaction If you encounter an error or exception, although it is not the initiator of the transaction, the transaction will be rolled back, so that the transaction can be rolled back as soon as possible In a multi-database scenario, manually obtain db Connection, then bind it to a new context and pass in Do not drop the anonymous function's context parameter, because if the Transaction context does not have a DBConnection, then a new context will be created and placed in the DBConnection The context pointer has changed and the Transaction context parameters cannot be used directly "bug (springrain)" If a great god changes the parameter name in the anonymous function, for example, change it to ctx 2, so that the business code actually uses the context parameter of Transaction. If there is no db Connection, an exception will be thrown. If there is a db Connection, the actual It is an object The impact is limited. Anonymous functions can also be extracted outside If the return error is not nil, the transaction will be rolled back

func Update

func Update(ctx context.Context, entity IEntityStruct) (int, error)

Update 更新struct所有属性,必须是IEntityStruct类型 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection

func UpdateEntityMap

func UpdateEntityMap(ctx context.Context, entity IEntityMap) (int, error)

UpdateEntityMap 更新IEntityMap对象.用于不方便使用struct的场景,主键必须有值 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection affected影响的行数,如果异常或者驱动不支持,返回-1 UpdateEntityMap Update IEntityMap object. Used in scenarios where struct is not convenient, the primary key must have a value ctx cannot be nil, refer to grm.Transaction method to pass in ctx. Don't build DB Connection yourself The number of rows affected by "affected", if it is abnormal or the driver does not support it, return -1

func UpdateFinder

func UpdateFinder(ctx context.Context, finder *Finder) (int, error)

UpdateFinder 更新Finder语句 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection affected影响的行数,如果异常或者驱动不支持,返回-1 UpdateFinder Update Finder statement ctx cannot be nil, refer to grm.Transaction method to pass in ctx. Don't build DB Connection yourself The number of rows affected by affected, if it is abnormal or the driver does not support it, return-1

func UpdateNotZeroValue

func UpdateNotZeroValue(ctx context.Context, entity IEntityStruct) (int, error)

UpdateNotZeroValue 更新struct不为默认零值的属性,必须是IEntityStruct类型,主键必须有值 ctx不能为nil,参照使用grm.Transaction方法传入ctx.也不要自己构建DBConnection

Types

type CustomDriverValueConvert

type CustomDriverValueConvert interface {
	//GetDriverValue 根据数据库列类型,实体类属性类型,Finder对象,返回driver.Value的实例
	//如果无法获取到structFieldType,例如Map查询,会传入nil
	//如果返回值为nil,接口扩展逻辑无效,使用原生的方式接收数据库字段值
	GetDriverValue(columnType *sql.ColumnType, structFieldType *reflect.Type, finder *Finder) (driver.Value, error)

	//ConvertDriverValue 数据库列类型,实体类属性类型,GetDriverValue返回的driver.Value的临时接收值,Finder对象
	//如果无法获取到structFieldType,例如Map查询,会传入nil
	//返回符合接收类型值的指针,指针,指针!!!!
	ConvertDriverValue(columnType *sql.ColumnType, structFieldType *reflect.Type, tempDriverValue driver.Value, finder *Finder) (interface{}, error)
}

CustomDriverValueConvert 自定义类型转化接口,用于解决 类似达梦 text --> dm.DmClob --> string类型接收的问题

type DBConfig

type DBConfig struct {
	//DSN DataSourceName Database connection string
	DSN string //
	//Database Type:mysql,postgresql,oracle,mssql,sqlite,clickhouse corresponds to Driver,A database may have multiple drivers
	Driver string
	//ShowSQL Whether to print SQL, use grm.ShowSQL record sql
	ShowSQL bool
	//MaxOpenConns Maximum number of database connections, Default 50
	MaxOpenConns int
	//MaxIdleConns The maximum number of free connections to the database default 50
	MaxIdleConns int
	//MaxLifetime: (Connection survival time in seconds)Destroy and rebuild the connection after the default 600 seconds (10 minutes)
	//Prevent the database from actively disconnecting and causing dead connections. MySQL Default wait_timeout 28800 seconds
	MaxLifetime int

	Tag string // customize tag

	//事务隔离级别的默认配置,默认为nil
	DefaultTxOptions *sql.TxOptions

	//使用现有的数据库连接,优先级高于DSN
	SQLDB *sql.DB
}

DBConfig 数据库连接池的配置 DateSourceConfig Database connection pool configuration

type DBDao

type DBDao struct {
	// contains filtered or unexported fields
}

DBDao 数据库操作基类,隔离原生操作数据库API入口,所有数据库操作必须通过DBDao进行 DBDao Database operation base class, isolate the native operation database API entry,all database operations must be performed through DB Dao

func NewDao

func NewDao(config *DBConfig) (*DBDao, error)

NewDao 创建dbDao,一个数据库要只执行一次,业务自行控制 第一个执行的数据库为 defaultDao,后续grm.xxx方法,默认使用的就是defaultDao NewDao Creates dbDao, a database must be executed only once, and the business is controlled by itself The first database to be executed is defaultDao, and the subsequent grm.xxx method is defaultDao by default

func (*DBDao) BindCtxDBConn

func (dbDao *DBDao) BindCtxDBConn(parent context.Context) (context.Context, error)

BindCtxDBConn 多库的时候,通过dbDao创建DBConnection绑定到子context,返回的context就有了DBConnection. parent 不能为空 BindCtxDBConn In the case of multiple databases, create a DB Connection through db Dao and bind it to a sub-context,and the returned context will have a DB Connection. parent is not nil

func (*DBDao) BindCtxTxOptions

func (dbDao *DBDao) BindCtxTxOptions(parent context.Context, txOptions *sql.TxOptions) (context.Context, error)

BindCtxTxOptions 绑定事务的隔离级别,参考sql.IsolationLevel,如果txOptions为nil,使用默认的事务隔离级别.parent不能为空 需要在事务开启前调用,也就是grm.Transaction方法前,不然事务开启之后再调用就无效了

func (*DBDao) CloseDB

func (dbDao *DBDao) CloseDB() error

CloseDB 关闭所有数据库连接 请谨慎调用这个方法,会关闭所有数据库连接,用于处理特殊场景,正常使用无需手动关闭数据库连接

type EntityMap

type EntityMap struct {
	PkColumnName string
	PkSequence   map[string]string
	// contains filtered or unexported fields
}

EntityMap IEntityMap的基础实现,可以直接使用或者匿名注入

func NewEntityMap

func NewEntityMap(tbName string) *EntityMap

NewEntityMap Table name cannot be empty

func (*EntityMap) FieldMap

func (entity *EntityMap) FieldMap() map[string]interface{}

FieldMap For Map type, record database fields

func (*EntityMap) GetPkSequence

func (entity *EntityMap) GetPkSequence() map[string]string

GetPkSequence 主键序列,因为需要兼容多种数据库的序列,所以使用map key是Driver,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高 如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]"" GetPkSequence Primary key sequence, because it needs to be compatible with multiple database sequences, map is used The key is the DB Type, and the value is the value of the sequence, such as Oracle's TESTSEQ.NEXTVAL. If there is a value, the priority is the highest If the value corresponding to the key is "", it means the sequence triggered by the trigger Compatible with auto-increment keywords, such as ["oracle"]""

func (*EntityMap) PK

func (entity *EntityMap) PK() string

func (*EntityMap) Set

func (entity *EntityMap) Set(key string, value interface{}) map[string]interface{}

Set database fields

func (*EntityMap) TableName

func (entity *EntityMap) TableName() string

type EntityStruct

type EntityStruct struct {
}

EntityStruct "IBaseEntity" 的基础实现,所有的实体类都匿名注入.这样就类似实现继承了,如果接口增加方法,调整这个默认实现即可 EntityStruct The basic implementation of "IBaseEntity", all entity classes are injected anonymously This is similar to implementation inheritance. If the interface adds methods, adjust the default implementation

func (*EntityStruct) GetPkSequence

func (entity *EntityStruct) GetPkSequence() map[string]string

GetPkSequence 主键序列,需要兼容多种数据库的序列,使用map,key是Driver,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高 如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]""

func (*EntityStruct) PK

func (entity *EntityStruct) PK() string

PK 获取数据库表的主键字段名称.因为要兼容Map,只能是数据库的字段名称 PK Get the primary key field name of the database table Because it is compatible with Map, it can only be the field name of the database

type Finder

type Finder struct {

	//注入检查,默认true 不允许SQL注入的 ' 单引号
	//Injection check, default true does not allow SQL injection  single quote
	InjectionCheck bool
	//CountFinder 自定义的查询总条数'Finder',使用指针默认为nil.主要是为了在"group by"等复杂情况下,为了性能,手动编写总条数语句
	//CountFinder The total number of custom queries is'Finder', and the pointer is nil by default. It is mainly used to manually write the total number of statements for performance in complex situations such as"group by"
	CountFinder *Finder
	//是否自动查询总条数,默认true.同时需要Page不为nil,才查询总条数
	//Whether to automatically query the total number of entries, the default is true. At the same time, the Page is not nil to query the total number of entries
	SelectTotalCount bool
	// contains filtered or unexported fields
}

Finder 查询数据库的载体,所有的sql语句都要通过Finder执行. Finder To query the database carrier, all SQL statements must be executed through Finder

func NewDeleteFinder

func NewDeleteFinder(tableName string) *Finder

NewDeleteFinder 根据表名初始化删除的'Finder', DELETE FROM tableName NewDeleteFinder Finder for initial deletion based on table name. DELETE FROM tableName

func NewFinder

func NewFinder() *Finder

NewFinder Initialize a Finder and generate an empty Finder

func NewSelectFinder

func NewSelectFinder(tableName string, strs ...string) *Finder

NewSelectFinder 根据表名初始化查询的Finder | Finder that initializes the query based on the table name NewSelectFinder("tableName") SELECT * FROM tableName NewSelectFinder("tableName", "id,name") SELECT id,name FROM tableName

func NewUpdateFinder

func NewUpdateFinder(tableName string) *Finder

NewUpdateFinder 根据表名初始化更新的Finder, UPDATE tableName SET NewUpdateFinder Initialize the updated Finder according to the table name, UPDATE tableName SET

func (*Finder) Append

func (finder *Finder) Append(s string, values ...interface{}) *Finder

Append 添加SQL和参数的值,第一个参数是语句,后面的参数[可选]是参数的值,顺序要正确 例如: finder.Append(" and id=? and name=? ",23123,"abc") 只拼接SQL,例如: finder.Append(" and name=123 ") Append:Add SQL and parameter values, the first parameter is the statement, and the following parameter (optional) is the value of the parameter, in the correct order E.g: finder.Append(" and id=? and name=? ",23123,"abc") Only splice SQL, E.g: finder.Append(" and name=123 ")

func (*Finder) AppendFinder

func (finder *Finder) AppendFinder(f *Finder) (*Finder, error)

AppendFinder 添加另一个Finder finder.AppendFinder(f) AppendFinder Add another Finder . finder.AppendFinder(f)

func (*Finder) GetSQL

func (finder *Finder) GetSQL() (string, error)

GetSQL 返回Finder封装的SQL语句 GetSQL Return the SQL statement encapsulated by the Finder

type IEntityMap

type IEntityMap interface {
	TableName() string

	PK() string

	//GetPkSequence 主键序列,因为需要兼容多种数据库的序列,所以使用map
	//key是Driver,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高
	//如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]""
	//GetPkSequence Primary key sequence, because it needs to be compatible with multiple database sequences, map is used
	//The key is the DB Type, and the value is the value of the sequence,
	//such as Oracle's TESTSEQ.NEXTVAL. If there is a value, the priority is the highest
	//If the value corresponding to the key is "", it means the sequence triggered by the trigger
	//Compatible with auto-increment keywords, such as ["oracle"]""
	GetPkSequence() map[string]string

	//FieldMap For Map type, record database fields.
	FieldMap() map[string]interface{}

	//Set the value of a database field.
	Set(key string, value interface{}) map[string]interface{}
}

IEntityMap 使用Map保存数据,用于不方便使用struct的场景,如果主键是自增或者序列,不要"entityMap.Set"主键的值 IEntityMap Use Map to save data for scenarios where it is not convenient to use struct If the primary key is auto-increment or sequence, do not "entity Map.Set" the value of the primary key

type IEntityStruct

type IEntityStruct interface {
	TableName() string

	PK() string

	//GetPkSequence 主键序列,因为需要兼容多种数据库的序列,所以使用map
	//key是Driver,value是序列的值,例如oracle的TESTSEQ.NEXTVAL,如果有值,优先级最高
	//如果key对应的value是 "",则代表是触发器触发的序列,兼容自增关键字,例如 ["oracle"]""
	//GetPkSequence Primary key sequence, because it needs to be compatible with multiple database sequences, map is used
	//The key is the DB Type, and the value is the value of the sequence,
	//such as Oracle's TESTSEQ.NEXTVAL. If there is a value, the priority is the highest
	//If the value corresponding to the key is "", it means the sequence triggered by the trigger
	//Compatible with auto-increment keywords, such as ["oracle"]""
	GetPkSequence() map[string]string
}

IEntityStruct The interface of the "struct" entity class, all struct entity classes must implement this interface

type Page

type Page struct {
	PageNo     int
	PageSize   int
	TotalCount int
	PageCount  int
	FirstPage  bool
	HasPrev    bool
	HasNext    bool
	LastPage   bool
}

Page Pagination object

func NewPage

func NewPage() *Page

NewPage Create Page object

type SQLBuilder

type SQLBuilder struct {
	// contains filtered or unexported fields
}

A SQLBuilder is used to efficiently build a string using Write methods. It minimizes memory copying. The zero value is ready to use. Do not copy a non-zero SQLBuilder.

func (*SQLBuilder) Cap

func (b *SQLBuilder) Cap() int

Cap returns the capacity of the builder's underlying byte slice. It is the total space allocated for the string being built and includes any bytes already written.

func (*SQLBuilder) Len

func (b *SQLBuilder) Len() int

Len returns the number of accumulated bytes; b.Len() == len(b.String()).

func (*SQLBuilder) RemoveEnd

func (b *SQLBuilder) RemoveEnd(i int)

RemoveEnd remove end of string

func (*SQLBuilder) Reset

func (b *SQLBuilder) Reset()

Reset resets the SQLBuilder to be empty.

func (*SQLBuilder) String

func (b *SQLBuilder) String() string

String returns the accumulated string.

func (*SQLBuilder) Write

func (b *SQLBuilder) Write(p []byte)

Write appends the contents of p to b's buffer. Write always returns len(p), nil.

func (*SQLBuilder) WriteByte

func (b *SQLBuilder) WriteByte(c byte)

WriteByte appends the byte c to b's buffer. The returned error is always nil.

func (*SQLBuilder) WriteInt

func (b *SQLBuilder) WriteInt(i int)

func (*SQLBuilder) WriteString

func (b *SQLBuilder) WriteString(s string)

WriteString appends the contents of s to b's buffer. It returns the length of s and a nil error.

Directories

Path Synopsis
Package decimal implements an arbitrary precision fixed-point decimal.
Package decimal implements an arbitrary precision fixed-point decimal.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL