Go sqlx フレームワークはアンダースコア命名法を使用して構造体フィールドを処理します

SQLXについて

sqlx は、Golang データベース操作ライブラリであり、ネイティブ ライブラリ API のスーパーセットであるネイティブ ライブラリdatabase/sqlに基づく。

デフォルトでは、sqlx は構造の解析とスキャンを実行するときに対応するデータベース フィールドを取得します。優先順位は次のとおりです。

  1. タグ内に db タグがある場合は、タグに従ってデータベースの対応するフィールド名を取得します。ソース コードについてはsqlx.go#43を参照してください。
  2. タグに db タグがない場合は、すべて小文字のフィールド名がデータベースの対応するフィールド名として使用されます。ソース コードについては、 sqlx.go#27を参照してください。

TiDB SQL 開発仕様および基本原則 - オブジェクト命名仕様によると、「名前付けには意味のある英単語を使用し、単語の途中でアンダースコアで区切ることをお勧めします」とあり、2 番目の「すべて小文字」を「」に変更する必要があります。アンダースコア命名方法」。

SQLX データベース フィールド生成関数を変更するには 2 つの手順が必要です。

  1. アンダースコア命名法 NameMapper 関数を作成します。
  2. デフォルトの NameMapper 関数を変更します。

アンダースコア命名メソッド NameMapper 関数を作成する

ソース コードによると、デフォルトで sqlx はフィールド処理にstrings.ToLowerネイティブ。この関数には名前と型文字列という名前のパラメータしかありません。

次に、ソースgormコードtoDBName関数を抽出できます。詳細については、gorm-schema/naming.go:117 を参照してください。

以下は著者が提案したアンダースコア命名フィールド生成関数ですtoDBName

import "strings"

var commonInitialismsReplacer = newCommonInitialismsReplacer()

func newCommonInitialismsReplacer() *strings.Replacer {
    
    
	// https://github.com/golang/lint/blob/master/lint.go#L770
	commonInitialisms := []string{
    
    "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SSH", "TLS", "TTL", "UID", "UI", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XSRF", "XSS"}

	commonInitialismsForReplacer := make([]string, 0, len(commonInitialisms))
	for _, initialism := range commonInitialisms {
    
    
		commonInitialismsForReplacer = append(commonInitialismsForReplacer, initialism, strings.Title(strings.ToLower(initialism)))
	}
	replacer := strings.NewReplacer(commonInitialismsForReplacer...)
	return replacer
}

func toDBName(name string) string {
    
    
	if name == "" {
    
    
		return ""
	}

	var (
		value                          = commonInitialismsReplacer.Replace(name)
		buf                            strings.Builder
		lastCase, nextCase, nextNumber bool // upper case == true
		curCase                        = value[0] <= 'Z' && value[0] >= 'A'
	)

	for i, v := range value[:len(value)-1] {
    
    
		nextCase = value[i+1] <= 'Z' && value[i+1] >= 'A'
		nextNumber = value[i+1] >= '0' && value[i+1] <= '9'

		if curCase {
    
    
			if lastCase && (nextCase || nextNumber) {
    
    
				buf.WriteRune(v + 32)
			} else {
    
    
				if i > 0 && value[i-1] != '_' && value[i+1] != '_' {
    
    
					buf.WriteByte('_')
				}
				buf.WriteRune(v + 32)
			}
		} else {
    
    
			buf.WriteRune(v)
		}

		lastCase = curCase
		curCase = nextCase
	}

	if curCase {
    
    
		if !lastCase && len(value) > 1 {
    
    
			buf.WriteByte('_')
		}
		buf.WriteByte(value[len(value)-1] + 32)
	} else {
    
    
		buf.WriteByte(value[len(value)-1])
	}
	ret := buf.String()
	return ret
}

デフォルトの NameMapper 関数を変更する

SQLX ソース コードによると、NameMapperこれが SQLX のグローバル変数であることがため、プログラムの初期化プロセス中に変数を変更するだけで済みます。

func init() {
    
    
	sqlx.NameMapper = toDBName
}

結果を示す

デモでは、すべて小文字を使用する場合と、アンダースコア命名関数を置き換える場合の違いを示します。

テスト環境では golang v1.17、sqlx v1.3.4、TiDB v1.6.1 を使用していることに注意してください。

  1. まず、デモンストレーション用のデータ テーブル testTable を作成する必要があります。
CREATE TABLE `test_table` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(256) NOT NULL,
  `sex` varchar(256) NOT NULL,
  `counter` bigint(20) NOT NULL,
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `miss_counter` bigint(20) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `test_table_name_uindex` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=30001
  1. 次に、Go プログラムで testTable という構造体を定義します。ここでMissCounterフィールドはNameMapper の影響を受けます。
type testTable struct {
	ID          uint64
	Name        string // unique index
	Sex         string
	Counter     uint64 // 命中计数字段
	MissCounter uint64 // 非命中计数字段
	CreateAt    time.Time
	UpdateAt    time.Time
}
  1. 次に、さまざまなデータベース フィールド生成関数の影響を示すインサーター コードを作成する必要があります。
func main(){
    
    
  tx, err := db.BeginTxx(ctx, &sql.TxOptions{
    
    })
	if err != nil {
    
    
		log.Error(err)
		return
	}
	
	const insertSQL = "INSERT INTO test_table (name, sex, counter, miss_counter) VALUES (:name, :sex, :counter, :miss_counter);"
	stmt, errPrepare := tx.PrepareNamedContext(ctx, insertSQL)
	if errPrepare != nil {
    
    
		log.Errorf("failed to prepare, err: %v", errPrepare)
		_ = tx.Rollback()
		return
	}
	for _, row := range []*testTable{
    
    
		{
    
    Name: "小李", Sex: "男", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
		{
    
    Name: "小白", Sex: "女", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
		{
    
    Name: "小黑", Sex: "男", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
		{
    
    Name: "小天", Sex: "女", Counter: uint64(rand.Int63()), MissCounter: uint64(rand.Int63())},
	} {
    
    
		res, errExec := stmt.ExecContext(ctx, row)
		if errExec != nil {
    
    
			// for duplicate err
			if me, isMySQLErr := errExec.(*mysql.MySQLError); isMySQLErr && me.Number == 1062 /*duplicate is 1062*/ {
    
    
				log.Infof("skip duplicate err, err: %v, row: %+v", me, row)
				continue
			}
			log.Errorf("failed to stmt exec, err: %v", errExec)
			_ = tx.Rollback()
			return
		}
		insertID, _ := res.LastInsertId()
		log.Infof("success to stmt exec, row: %+v, id: %d", row, insertID)
	}
	if errCommit := tx.Commit(); errCommit != nil {
    
    
		log.Error(errCommit)
		return
	}
	log.Infof("finish")
}
  1. すべて小文字の NameMapper を使用する場合、プログラムの実行でエラーが報告されますcould not find name xxx
2021-10-31T16:29:15.029+0800    ERROR   transaction/main.go:76  failed to stmt exec, err: could not find name miss_counter in &main.testTable{
    
    ID:0x0, Name:"小李, Sex:"男", Counter:0x4354432fe0f37484, MissCounter:0x45afa115903b7669, CreateAt:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), UpdateAt:time.Date(1, tim.January, 1, 0, 0, 0, 0, time.UTC)}
  1. アンダースコア表記を使用する NameMapper の場合、プログラムはスムーズに実行されます。
2021-10-31T16:30:27.841+0800    INFO    transaction/main.go:81  success to stmt exec, row: &{
    
    ID:0 Name:小李 Sex:男 Counter:6239523519032355576 MissCounter:180608923895024 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 23
2021-10-31T16:30:27.842+0800    INFO    transaction/main.go:81  success to stmt exec, row: &{
    
    ID:0 Name:小白 Sex:女 Counter:2433768853952726858 MissCounter:7832533413053117 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 24
2021-10-31T16:30:27.844+0800    INFO    transaction/main.go:81  success to stmt exec, row: &{
    
    ID:0 Name:小黑 Sex:男 Counter:8462818185278118914 MissCounter:1149211500697067 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 25
2021-10-31T16:30:27.845+0800    INFO    transaction/main.go:81  success to stmt exec, row: &{
    
    ID:0 Name:小天 Sex:女 Counter:23243666993166910 MissCounter:9005766596808865 CreateAt:0001-01-01 00:00:00 +0000 UTC UpdateAt:0001-01-01 00:00:00 +0000 UTC}, id: 26
2021-10-31T16:30:27.848+0800    INFO    transaction/main.go:87  finish

この記事が役に立った場合は、著者に「いいね」を押してください。ありがとうございます。

おすすめ

転載: blog.csdn.net/yes169yes123/article/details/121066019