-
Notifications
You must be signed in to change notification settings - Fork 6
/
DBDao.go
1929 lines (1742 loc) · 79.9 KB
/
DBDao.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package zorm 使用原生的sql语句,没有对sql语法做限制.语句使用Finder作为载体
// 占位符统一使用?,zorm会根据数据库类型,语句执行前会自动替换占位符,postgresql 把?替换成$1,$2...;mssql替换成@P1,@p2...;orace替换成:1,:2...
// zorm使用 ctx context.Context 参数实现事务传播,ctx从web层传递进来即可,例如gin的c.Request.Context()
// zorm的事务操作需要显示使用zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {})开启
// "package zorm" Use native SQL statements, no restrictions on SQL syntax. Statements use Finder as a carrier
// Use placeholders uniformly "?" "zorm" 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...,
// "zorm" 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()",
// "zorm" Transaction operations need to be displayed using "zorm.transaction" (ctx, func(ctx context.context) (interface{}, error) {})
package zorm
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"time"
)
// FuncReadWriteStrategy 数据库的读写分离的策略,用于外部重写实现自定义的逻辑,也可以使用ctx标识,处理多库的场景,rwType=0 read,rwType=1 write
// 不能归属到DBDao里,BindContextDBConnection已经是指定数据库的连接了,和这个函数会冲突.就作为读写分离的处理方式
// 即便是放到DBDao里,因为是多库,BindContextDBConnection函数调用少不了,业务包装一个方法,指定一下读写获取一个DBDao效果是一样的,唯一就是需要根据业务指定一下读写,其实更灵活了
// FuncReadWriteStrategy Single database read and write separation strategy,used for external replication to implement custom logic, rwType=0 read, rwType=1 write.
// "BindContextDBConnection" is already a connection to the specified database and will conflict with this function. As a single database read and write separation of processing
var FuncReadWriteStrategy = func(ctx context.Context, rwType int) (*DBDao, error) {
if defaultDao == nil {
return nil, errors.New("->FuncReadWriteStrategy-->defaultDao为nil,请检查数据库初始化配置是否正确,主要是DSN,DriverName和Dialect")
}
return defaultDao, nil
}
// wrapContextStringKey 包装context的key,不直接使用string类型,避免外部直接注入使用
type wrapContextStringKey string
// contextDBConnectionValueKey context WithValue的key,不能是基础类型,例如字符串,包装一下
// The key of context WithValue cannot be a basic type, such as a string, wrap it
const contextDBConnectionValueKey = wrapContextStringKey("contextDBConnectionValueKey")
// contextTxOptionsKey 事务选项设置TxOptions的key,设置事务的隔离级别
const contextTxOptionsKey = wrapContextStringKey("contextTxOptionsKey")
// stringBuilderGrowLen 默认长度
const stringBuilderGrowLen = 100
// DataSourceConfig 数据库连接池的配置
// DateSourceConfig Database connection pool configuration
type DataSourceConfig struct {
// DSN dataSourceName 连接字符串
// DSN DataSourceName Database connection string
DSN string
// DriverName 数据库驱动名称:mysql,postgres,oracle(go-ora),sqlserver,sqlite3,go_ibm_db,clickhouse,dm,kingbase,aci,taosSql|taosRestful 和Dialect对应
// DriverName:mysql,dm,postgres,opi8,sqlserver,sqlite3,go_ibm_db,clickhouse,kingbase,aci,taosSql|taosRestful corresponds to Dialect
DriverName string
// Dialect 数据库方言:mysql,postgresql,oracle,mssql,sqlite,db2,clickhouse,dm,kingbase,shentong,tdengine 和 DriverName 对应
// Dialect:mysql,postgresql,oracle,mssql,sqlite,db2,clickhouse,dm,kingbase,shentong,tdengine corresponds to DriverName
Dialect string
// Deprecated
// DBType 即将废弃,请使用Dialect属性
// DBType is about to be deprecated, please use the Dialect property
// DBType string
// SlowSQLMillis 慢sql的时间阈值,单位毫秒.小于0是禁用SQL语句输出;等于0是只输出SQL语句,不计算执行时间;大于0是计算SQL执行时间,并且>=SlowSQLMillis值
SlowSQLMillis int
// MaxOpenConns 数据库最大连接数,默认50
// MaxOpenConns Maximum number of database connections, Default 50
MaxOpenConns int
// MaxIdleConns 数据库最大空闲连接数,默认50
// MaxIdleConns The maximum number of free connections to the database default 50
MaxIdleConns int
// ConnMaxLifetimeSecond 连接存活秒时间. 默认600(10分钟)后连接被销毁重建.避免数据库主动断开连接,造成死连接.MySQL默认wait_timeout 28800秒(8小时)
// ConnMaxLifetimeSecond (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
ConnMaxLifetimeSecond int
// DefaultTxOptions 事务隔离级别的默认配置,默认为nil
DefaultTxOptions *sql.TxOptions
// DisableTransaction 禁用事务,默认false,如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务.为了处理某些数据库不支持事务,比如TDengine
// 禁用事务应该有驱动伪造事务API,不应该由orm实现
DisableTransaction bool
// MockSQLDB 用于mock测试的入口,如果MockSQLDB不为nil,则不使用DSN,直接使用MockSQLDB
// db, mock, err := sqlmock.New()
// MockSQLDB *sql.DB
// FuncGlobalTransaction seata/hptx全局分布式事务的适配函数,返回IGlobalTransaction接口的实现
// 业务必须调用zorm.BindContextEnableGlobalTransaction(ctx)开启全局分布事务
// seata-go 的ctx是统一的绑定的是struct,也不是XID字符串. hptx是分离的,所以返回了两个ctx,兼容两个库
FuncGlobalTransaction func(ctx context.Context) (IGlobalTransaction, context.Context, context.Context, error)
// DisableAutoGlobalTransaction 属性已废弃,请勿使用,相关注释仅作记录备忘
// DisableAutoGlobalTransaction 禁用自动全局分布式事务,默认false,虽然设置了FuncGlobalTransaction,但是并不想全部业务自动开启全局事务
// DisableAutoGlobalTransaction = false; ctx,_=zorm.BindContextEnableGlobalTransaction(ctx,false) 默认使用全局事务,ctx绑定为false才不开启
// DisableAutoGlobalTransaction = true; ctx,_=zorm.BindContextEnableGlobalTransaction(ctx,true) 默认禁用全局事务,ctx绑定为true才开启
// DisableAutoGlobalTransaction bool
// SQLDB 使用现有的数据库连接,优先级高于DSN
SQLDB *sql.DB
// TDengineInsertsColumnName TDengine批量insert语句中是否有列名.默认false没有列名,插入值和数据库列顺序保持一致,减少语句长度
TDengineInsertsColumnName bool
}
// DBDao 数据库操作基类,隔离原生操作数据库API入口,所有数据库操作必须通过DBDao进行
// DBDao Database operation base class, isolate the native operation database API entry,all database operations must be performed through DB Dao
type DBDao struct {
config *DataSourceConfig
dataSource *dataSource
}
var defaultDao *DBDao = nil
// NewDBDao 创建dbDao,一个数据库要只执行一次,业务自行控制
// 第一个执行的数据库为 defaultDao,后续zorm.xxx方法,默认使用的就是defaultDao
// NewDBDao 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 zorm.xxx method is defaultDao by default
func NewDBDao(config *DataSourceConfig) (*DBDao, error) {
dataSource, err := newDataSource(config)
if err != nil {
err = fmt.Errorf("->NewDBDao创建dataSource失败:%w", err)
FuncLogError(nil, err)
return nil, err
}
dbdao, err := FuncReadWriteStrategy(nil, 1)
if dbdao == nil {
defaultDao = &DBDao{config, dataSource}
return defaultDao, nil
}
if err != nil {
return dbdao, err
}
return &DBDao{config, dataSource}, nil
}
// newDBConnection 获取一个dbConnection
// 如果参数dbConnection为nil,使用默认的datasource进行获取dbConnection
// 如果是多库,Dao手动调用newDBConnection(),获得dbConnection,WithValue绑定到子context
// newDBConnection Get a db Connection
// If the parameter db Connection is nil, use the default datasource to get db Connection.
// If it is multi-database, Dao manually calls new DB Connection() to obtain db Connection, and With Value is bound to the sub-context
func (dbDao *DBDao) newDBConnection() (*dataBaseConnection, error) {
if dbDao == nil || dbDao.dataSource == nil {
return nil, errors.New("->newDBConnection-->请不要自己创建dbDao,请使用NewDBDao方法进行创建")
}
dbConnection := new(dataBaseConnection)
dbConnection.db = dbDao.dataSource.DB
dbConnection.config = dbDao.config
return dbConnection, nil
}
// BindContextDBConnection 多库的时候,通过dbDao创建DBConnection绑定到子context,返回的context就有了DBConnection. parent 不能为空
// BindContextDBConnection 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 *DBDao) BindContextDBConnection(parent context.Context) (context.Context, error) {
if parent == nil {
return nil, errors.New("->BindContextDBConnection-->context的parent不能为nil")
}
dbConnection, errDBConnection := dbDao.newDBConnection()
if errDBConnection != nil {
return parent, errDBConnection
}
ctx := context.WithValue(parent, contextDBConnectionValueKey, dbConnection)
return ctx, nil
}
// BindContextTxOptions 绑定事务的隔离级别,参考sql.IsolationLevel,如果txOptions为nil,使用默认的事务隔离级别.parent不能为空
// 需要在事务开启前调用,也就是zorm.Transaction方法前,不然事务开启之后再调用就无效了
func (dbDao *DBDao) BindContextTxOptions(parent context.Context, txOptions *sql.TxOptions) (context.Context, error) {
if parent == nil {
return nil, errors.New("->BindContextTxOptions-->context的parent不能为nil")
}
ctx := context.WithValue(parent, contextTxOptionsKey, txOptions)
return ctx, nil
}
// CloseDB 关闭所有数据库连接
// 请谨慎调用这个方法,会关闭所有数据库连接,用于处理特殊场景,正常使用无需手动关闭数据库连接
func (dbDao *DBDao) CloseDB() error {
if dbDao == nil || dbDao.dataSource == nil {
return errors.New("->CloseDB-->请不要自己创建dbDao,请使用NewDBDao方法进行创建")
}
return dbDao.dataSource.Close()
}
/*
Transaction 的示例代码
//匿名函数return的error如果不为nil,事务就会回滚
zorm.Transaction(ctx context.Context,func(ctx context.Context) (interface{}, error) {
//业务代码
//return的error如果不为nil,事务就会回滚
return nil, nil
})
*/
// 事务方法,隔离dbConnection相关的API.必须通过这个方法进行事务处理,统一事务方式.如果设置了DisableTransaction=true,Transaction方法失效,不再要求有事务
// 如果入参ctx中没有dbConnection,使用defaultDao开启事务并最后提交
// 如果入参ctx有dbConnection且没有事务,调用dbConnection.begin()开启事务并最后提交
// 如果入参ctx有dbConnection且有事务,只使用不提交,有开启方提交事务
// 但是如果遇到错误或者异常,虽然不是事务的开启方,也会回滚事务,让事务尽早回滚
// 在多库的场景,手动获取dbConnection,然后绑定到一个新的context,传入进来
// 不要去掉匿名函数的context参数,因为如果Transaction的context中没有dbConnection,会新建一个context并放入dbConnection,此时的context指针已经变化,不能直接使用Transaction的context参数
// bug(springrain)如果有大神修改了匿名函数内的参数名,例如改为ctx2,这样业务代码实际使用的是Transaction的context参数,如果为没有dbConnection,会抛异常,如果有dbConnection,实际就是一个对象.影响有限.也可以把匿名函数抽到外部
// 如果zorm.DataSourceConfig.DefaultTxOptions配置不满足需求,可以在zorm.Transaction事务方法前设置事务的隔离级别,例如 ctx, _ := dbDao.BindContextTxOptions(ctx, &sql.TxOptions{Isolation: sql.LevelDefault}),如果txOptions为nil,使用zorm.DataSourceConfig.DefaultTxOptions
// return的error如果不为nil,事务就会回滚
// 如果使用了分布式事务,需要设置分布式事务函数zorm.DataSourceConfig.FuncGlobalTransaction,实现IGlobalTransaction接口
// 如果是分布式事务开启方,需要在本地事务前开启分布事务,开启之后获取XID,设值到ctx的XID和TX_XID.XID是seata/hptx MySQL驱动需要,TX_XID是gtxContext.NewRootContext需要
// 分布式事务需要传递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 Transaction(ctx context.Context, doTransaction func(ctx context.Context) (interface{}, error)) (interface{}, error) {
return transaction(ctx, doTransaction)
}
var transaction = func(ctx context.Context, doTransaction func(ctx context.Context) (interface{}, error)) (info interface{}, err error) {
// 是否是dbConnection的开启方,如果是开启方,才可以提交事务
// Whether it is the opener of db Connection, if it is the opener, the transaction can be submitted
localTxOpen := false
// 是否是分布式事务的开启方.如果ctx中没有xid,认为是开启方
globalTxOpen := false
// 如果dbConnection不存在,则会用默认的datasource开启事务
// If db Connection does not exist, the default datasource will be used to start the transaction
var dbConnection *dataBaseConnection
ctx, dbConnection, err = checkDBConnection(ctx, dbConnection, false, 1)
if err != nil {
FuncLogError(ctx, err)
return nil, err
}
// 适配全局事务的函数
funcGlobalTx := dbConnection.config.FuncGlobalTransaction
// 实现IGlobalTransaction接口的事务对象
var globalTransaction IGlobalTransaction
// 分布式事务的 rootContext,和业务的ctx区别开来,如果业务ctx使用WithValue,就会出现差异
var globalRootContext context.Context
// 分布式事务的异常
var errGlobal error
// 如果没有事务,并且事务没有被禁用,开启事务
// 开启本地事务前,需要拿到分布式事务对象
if dbConnection.tx == nil && (!getContextBoolValue(ctx, contextDisableTransactionValueKey, dbConnection.config.DisableTransaction)) {
// if dbConnection.tx == nil {
// 是否使用分布式事务
enableGlobalTransaction := funcGlobalTx != nil
if enableGlobalTransaction { // 判断ctx里是否有绑定 enableGlobalTransaction
/*
ctxGTXval := ctx.Value(contextEnableGlobalTransactionValueKey)
if ctxGTXval != nil { //如果有值
enableGlobalTransaction = ctxGTXval.(bool)
} else { //如果ctx没有值,就取值DisableAutoGlobalTransaction
//enableGlobalTransaction = !dbConnection.config.DisableAutoGlobalTransaction
enableGlobalTransaction = false
}
*/
enableGlobalTransaction = getContextBoolValue(ctx, contextEnableGlobalTransactionValueKey, false)
}
// 需要开启分布式事务,初始化分布式事务对象,判断是否是分布式事务入口
if enableGlobalTransaction {
// 获取分布式事务的XID
ctxXIDval := ctx.Value("XID")
if ctxXIDval != nil { // 如果本地ctx中有XID
globalXID, _ := ctxXIDval.(string)
// 不知道为什么需要两个Key,还需要请教seata/hptx团队
// seata/hptx mysql驱动需要 XID,gtxContext.NewRootContext 需要 TX_XID
ctx = context.WithValue(ctx, "TX_XID", globalXID)
} else { // 如果本地ctx中没有XID,也就是没有传递过来XID,认为是分布式事务的开启方.ctx中没有XID和TX_XID的值
globalTxOpen = true
}
// 获取分布式事务实现对象,用于控制事务提交和回滚.分支事务需要ctx中TX_XID有值,将分支事务关联到主事务
globalTransaction, ctx, globalRootContext, errGlobal = funcGlobalTx(ctx)
if errGlobal != nil {
errGlobal = fmt.Errorf("->Transaction-->global:Transaction FuncGlobalTransaction获取IGlobalTransaction接口实现失败:%w ", errGlobal)
FuncLogError(ctx, errGlobal)
return nil, errGlobal
}
if globalTransaction == nil || globalRootContext == nil {
errGlobal = errors.New("->Transaction-->global:Transaction FuncGlobalTransaction获取IGlobalTransaction接口的实现为nil ")
FuncLogError(ctx, errGlobal)
return nil, errGlobal
}
}
if globalTxOpen { // 如果是分布事务开启方,启动分布式事务
errGlobal = globalTransaction.BeginGTX(ctx, globalRootContext)
if errGlobal != nil {
errGlobal = fmt.Errorf("->Transaction-->global:Transaction 分布式事务开启失败:%w ", errGlobal)
FuncLogError(ctx, errGlobal)
return nil, errGlobal
}
// 分布式事务开启成功,获取XID,设置到ctx的XID和TX_XID
// seata/hptx mysql驱动需要 XID,gtxContext.NewRootContext 需要 TX_XID
globalXID, errGlobal := globalTransaction.GetGTXID(ctx, globalRootContext)
if errGlobal != nil {
FuncLogError(ctx, errGlobal)
return nil, errGlobal
}
if globalXID == "" {
errGlobal = errors.New("->Transaction-->global:globalTransaction.Begin无异常开启后,获取的XID为空")
FuncLogError(ctx, errGlobal)
return nil, errGlobal
}
ctx = context.WithValue(ctx, "XID", globalXID)
ctx = context.WithValue(ctx, "TX_XID", globalXID)
}
// 开启本地事务/分支事务
errBeginTx := dbConnection.beginTx(ctx)
if errBeginTx != nil {
errBeginTx = fmt.Errorf("->Transaction 事务开启失败:%w ", errBeginTx)
FuncLogError(ctx, errBeginTx)
return nil, errBeginTx
}
// 本方法开启的事务,由本方法提交
// The transaction opened by this method is submitted by this method
localTxOpen = true
}
defer func() {
if r := recover(); r != nil {
//err = fmt.Errorf("->事务开启失败:%w ", err)
//记录异常日志
//if _, ok := r.(runtime.Error); ok {
// panic(r)
//}
var errOk bool
err, errOk = r.(error)
if errOk {
err = fmt.Errorf("->Transaction-->recover异常:%w", err)
FuncLogPanic(ctx, err)
} else {
err = fmt.Errorf("->Transaction-->recover异常:%v", r)
FuncLogPanic(ctx, err)
}
//if !txOpen { //如果不是开启方,也应该回滚事务,虽然可能造成日志不准确,但是回滚要尽早
// return
//}
//如果禁用了事务
if getContextBoolValue(ctx, contextDisableTransactionValueKey, dbConnection.config.DisableTransaction) {
return
}
rberr := dbConnection.rollback()
if rberr != nil {
rberr = fmt.Errorf("->Transaction-->recover内事务回滚失败:%w", rberr)
FuncLogError(ctx, rberr)
}
// 任意一个分支事务回滚,分布式事务就整体回滚
if globalTransaction != nil {
errGlobal = globalTransaction.RollbackGTX(ctx, globalRootContext)
if errGlobal != nil {
errGlobal = fmt.Errorf("->Transaction-->global:recover内globalTransaction事务回滚失败:%w", errGlobal)
FuncLogError(ctx, errGlobal)
}
}
}
}()
// 执行业务的事务函数
info, err = doTransaction(ctx)
if err != nil {
err = fmt.Errorf("->Transaction-->doTransaction业务执行错误:%w", err)
FuncLogError(ctx, err)
// 如果禁用了事务
if getContextBoolValue(ctx, contextDisableTransactionValueKey, dbConnection.config.DisableTransaction) {
return info, err
}
// 不是开启方回滚事务,有可能造成日志记录不准确,但是回滚最重要了,尽早回滚
// It is not the start party to roll back the transaction, which may cause inaccurate log records,but rollback is the most important, roll back as soon as possible
errRollback := dbConnection.rollback()
if errRollback != nil {
errRollback = fmt.Errorf("->Transaction-->rollback事务回滚失败:%w", errRollback)
FuncLogError(ctx, errRollback)
}
// 任意一个分支事务回滚,分布式事务就整体回滚
if globalTransaction != nil {
errGlobal = globalTransaction.RollbackGTX(ctx, globalRootContext)
if errGlobal != nil {
errGlobal = fmt.Errorf("->Transaction-->global:Transaction-->rollback globalTransaction事务回滚失败:%w", errGlobal)
FuncLogError(ctx, errGlobal)
}
}
return info, err
}
// 如果是事务开启方,提交事务
// If it is the transaction opener, commit the transaction
if localTxOpen {
errCommit := dbConnection.commit()
// 本地事务提交成功,如果是全局事务的开启方,提交分布式事务
if errCommit == nil && globalTxOpen {
errGlobal = globalTransaction.CommitGTX(ctx, globalRootContext)
if errGlobal != nil {
errGlobal = fmt.Errorf("->Transaction-->global:Transaction-->commit globalTransaction 事务提交失败:%w", errGlobal)
FuncLogError(ctx, errGlobal)
}
}
if errCommit != nil {
errCommit = fmt.Errorf("->Transaction-->commit事务提交失败:%w", errCommit)
FuncLogError(ctx, errCommit)
// 任意一个分支事务回滚,分布式事务就整体回滚
if globalTransaction != nil {
errGlobal = globalTransaction.RollbackGTX(ctx, globalRootContext)
if errGlobal != nil {
errGlobal = fmt.Errorf("->Transaction-->global:Transaction-->commit失败,然后回滚globalTransaction事务也失败:%w", errGlobal)
FuncLogError(ctx, errGlobal)
}
}
return info, errCommit
}
}
return info, err
}
var errQueryRow = errors.New("->QueryRow查询出多条数据")
// QueryRow 不要偷懒调用Query返回第一条,问题1.需要构建一个slice,问题2.调用方传递的对象其他值会被抛弃或者覆盖.
// 根据Finder和封装为指定的entity类型,entity必须是*struct类型或者基础类型的指针.把查询的数据赋值给entity,所以要求指针类型
// context必须传入,不能为空
// QueryRow Don't be lazy to call Query to return the first one
// Question 1. A selice 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 QueryRow(ctx context.Context, finder *Finder, entity interface{}) (bool, error) {
return queryRow(ctx, finder, entity)
}
var queryRow = func(ctx context.Context, finder *Finder, entity interface{}) (has bool, err error) {
typeOf, errCheck := checkEntityKind(entity)
if errCheck != nil {
errCheck = fmt.Errorf("->QueryRow-->checkEntityKind类型检查错误:%w", errCheck)
FuncLogError(ctx, errCheck)
return has, errCheck
}
// 从contxt中获取数据库连接,可能为nil
// Get database connection from contxt, may be nil
dbConnection, errFromContxt := getDBConnectionFromContext(ctx)
if errFromContxt != nil {
FuncLogError(ctx, errFromContxt)
return has, errFromContxt
}
// 自己构建的dbConnection
// dbConnection built by yourself
if dbConnection != nil && dbConnection.db == nil {
FuncLogError(ctx, errDBConnection)
return has, errDBConnection
}
config, errConfig := getConfigFromConnection(ctx, dbConnection, 0)
if errConfig != nil {
FuncLogError(ctx, errConfig)
return has, errConfig
}
// 获取到sql语句
// Get the sql statement
sqlstr, errSQL := wrapQuerySQL(ctx, config, finder, nil)
if errSQL != nil {
errSQL = fmt.Errorf("->QueryRow-->wrapQuerySQL获取查询SQL语句错误:%w", errSQL)
FuncLogError(ctx, errSQL)
return has, errSQL
}
// 检查dbConnection.有可能会创建dbConnection或者开启事务,所以要尽可能的接近执行时检查
// Check db Connection. It is possible to create a db Connection or start a transaction, so check it as close as possible to the execution
var errDbConnection error
ctx, dbConnection, errDbConnection = checkDBConnection(ctx, dbConnection, false, 0)
if errDbConnection != nil {
FuncLogError(ctx, errDbConnection)
return has, errDbConnection
}
// 根据语句和参数查询
// Query based on statements and parameters
rows, errQueryContext := dbConnection.queryContext(ctx, &sqlstr, &finder.values)
if errQueryContext != nil {
errQueryContext = fmt.Errorf("->QueryRow-->queryContext查询数据库错误:%w", errQueryContext)
FuncLogError(ctx, errQueryContext)
return has, errQueryContext
}
// 先判断error 再关闭
defer func() {
// 先判断error 再关闭
rows.Close()
// 捕获panic,赋值给err,避免程序崩溃
if r := recover(); r != nil {
has = false
var errOk bool
err, errOk = r.(error)
if errOk {
err = fmt.Errorf("->QueryRow-->recover异常:%w", err)
FuncLogPanic(ctx, err)
} else {
err = fmt.Errorf("->QueryRow-->recover异常:%v", r)
FuncLogPanic(ctx, err)
}
}
}()
// typeOf := reflect.TypeOf(entity).Elem()
// 数据库字段类型
columnTypes, errColumnTypes := rows.ColumnTypes()
if errColumnTypes != nil {
errColumnTypes = fmt.Errorf("->QueryRow-->rows.ColumnTypes数据库类型错误:%w", errColumnTypes)
FuncLogError(ctx, errColumnTypes)
return has, errColumnTypes
}
// 查询的字段长度
ctLen := len(columnTypes)
// 是否只有一列,而且可以直接赋值
oneColumnScanner := false
if ctLen < 1 { // 没有返回列
errColumn0 := errors.New("->QueryRow-->ctLen<1,没有返回列")
FuncLogError(ctx, errColumn0)
return has, errColumn0
} else if ctLen == 1 { // 如果只查询一个字段
// 是否是可以直接扫描的类型
_, oneColumnScanner = entity.(sql.Scanner)
if !oneColumnScanner {
pkgPath := (*typeOf).PkgPath()
if pkgPath == "" || pkgPath == "time" { // 系统内置变量和time包
oneColumnScanner = true
}
}
}
var dbColumnFieldMap *map[string]reflect.StructField
var exportFieldMap *map[string]reflect.StructField
if !oneColumnScanner { // 如果不是一个直接可以映射的字段,默认为是sturct
// 获取到类型的字段缓存
// Get the type field cache
dbColumnFieldMap, exportFieldMap, err = getDBColumnExportFieldMap(typeOf)
if err != nil {
err = fmt.Errorf("->QueryRow-->getDBColumnFieldMap获取字段缓存错误:%w", err)
return has, err
}
}
// 反射获取 []driver.Value的值,用于处理nil值和自定义类型
driverValue := reflect.Indirect(reflect.ValueOf(rows))
driverValue = driverValue.FieldByName("lastcols")
// 循环遍历结果集
// Loop through the result set
for i := 0; rows.Next(); i++ {
has = true
if i > 0 {
FuncLogError(ctx, errQueryRow)
return has, errQueryRow
}
if oneColumnScanner {
err = sqlRowsValues(ctx, config, nil, typeOf, rows, &driverValue, columnTypes, entity, dbColumnFieldMap, exportFieldMap)
} else {
pv := reflect.ValueOf(entity)
err = sqlRowsValues(ctx, config, &pv, typeOf, rows, &driverValue, columnTypes, nil, dbColumnFieldMap, exportFieldMap)
}
// pv = pv.Elem()
// scan赋值.是一个指针数组,已经根据struct的属性类型初始化了,sql驱动能感知到参数类型,所以可以直接赋值给struct的指针.这样struct的属性就有值了
// scan assignment. It is an array of pointers that has been initialized according to the attribute type of the struct,The sql driver can perceive the parameter type,so it can be directly assigned to the pointer of the struct. In this way, the attributes of the struct have values
// scanerr := rows.Scan(values...)
if err != nil {
err = fmt.Errorf("->Query-->sqlRowsValues错误:%w", err)
FuncLogError(ctx, err)
return has, err
}
}
return has, err
}
var errQuerySlice = errors.New("->Query数组必须是*[]struct类型或者*[]*struct或者基础类型数组的指针")
// 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
var Query = func(ctx context.Context, finder *Finder, rowsSlicePtr interface{}, page *Page) error {
return query(ctx, finder, rowsSlicePtr, page)
}
var query = func(ctx context.Context, finder *Finder, rowsSlicePtr interface{}, page *Page) (err error) {
if rowsSlicePtr == nil { // 如果为nil
FuncLogError(ctx, errQuerySlice)
return errQuerySlice
}
pvPtr := reflect.ValueOf(rowsSlicePtr)
if pvPtr.Kind() != reflect.Ptr { // 如果不是指针
FuncLogError(ctx, errQuerySlice)
return errQuerySlice
}
sliceValue := reflect.Indirect(pvPtr)
// 如果不是数组
// If it is not an array.
if sliceValue.Kind() != reflect.Slice {
FuncLogError(ctx, errQuerySlice)
return errQuerySlice
}
// 获取数组内的元素类型
// Get the element type in the array
sliceElementType := sliceValue.Type().Elem()
// slice数组里是否是指针,实际参数类似 *[]*struct,兼容这种类型
sliceElementTypePtr := false
// 如果数组里还是指针类型
if sliceElementType.Kind() == reflect.Ptr {
sliceElementTypePtr = true
sliceElementType = sliceElementType.Elem()
}
//如果不是struct
//if !(sliceElementType.Kind() == reflect.Struct || allowBaseTypeMap[sliceElementType.Kind()]) {
// return errors.New("->Query数组必须是*[]struct类型或者*[]*struct或者基础类型数组的指针")
//}
//从contxt中获取数据库连接,可能为nil
//Get database connection from contxt, may be nil
dbConnection, errFromContxt := getDBConnectionFromContext(ctx)
if errFromContxt != nil {
FuncLogError(ctx, errFromContxt)
return errFromContxt
}
// 自己构建的dbConnection
// dbConnection built by yourself
if dbConnection != nil && dbConnection.db == nil {
FuncLogError(ctx, errDBConnection)
return errDBConnection
}
config, errConfig := getConfigFromConnection(ctx, dbConnection, 0)
if errConfig != nil {
FuncLogError(ctx, errConfig)
return errConfig
}
sqlstr, errSQL := wrapQuerySQL(ctx, config, finder, page)
if errSQL != nil {
errSQL = fmt.Errorf("->Query-->wrapQuerySQL获取查询SQL语句错误:%w", errSQL)
FuncLogError(ctx, errSQL)
return errSQL
}
// 检查dbConnection.有可能会创建dbConnection或者开启事务,所以要尽可能的接近执行时检查
// Check db Connection. It is possible to create a db Connection or start a transaction, so check it as close as possible to the execution
var errDbConnection error
ctx, dbConnection, errDbConnection = checkDBConnection(ctx, dbConnection, false, 0)
if errDbConnection != nil {
FuncLogError(ctx, errDbConnection)
return errDbConnection
}
// 根据语句和参数查询
// Query based on statements and parameters
rows, errQueryContext := dbConnection.queryContext(ctx, &sqlstr, &finder.values)
if errQueryContext != nil {
errQueryContext = fmt.Errorf("->Query-->queryContext查询rows错误:%w", errQueryContext)
FuncLogError(ctx, errQueryContext)
return errQueryContext
}
// 先判断error 再关闭
defer func() {
// 先判断error 再关闭
rows.Close()
// 捕获panic,赋值给err,避免程序崩溃
if r := recover(); r != nil {
var errOk bool
err, errOk = r.(error)
if errOk {
err = fmt.Errorf("->Query-->recover异常:%w", err)
FuncLogPanic(ctx, err)
} else {
err = fmt.Errorf("->Query-->recover异常:%v", r)
FuncLogPanic(ctx, err)
}
}
}()
//_, ok := reflect.New(sliceElementType).Interface().(sql.Scanner)
// 数据库返回的字段类型
columnTypes, errColumnTypes := rows.ColumnTypes()
if errColumnTypes != nil {
errColumnTypes = fmt.Errorf("->Query-->rows.ColumnTypes数据库类型错误:%w", errColumnTypes)
FuncLogError(ctx, errColumnTypes)
return errColumnTypes
}
// 查询的字段长度
ctLen := len(columnTypes)
// 是否只有一列,而且可以直接赋值
oneColumnScanner := false
if ctLen < 1 { // 没有返回列
errColumn0 := errors.New("->Query-->ctLen<1,没有返回列")
FuncLogError(ctx, errColumn0)
return errColumn0
} else if ctLen == 1 { // 如果只查询一个字段
// 是否是可以直接扫描的类型
_, oneColumnScanner = reflect.New(sliceElementType).Interface().(sql.Scanner)
if !oneColumnScanner {
pkgPath := sliceElementType.PkgPath()
if pkgPath == "" || pkgPath == "time" { // 系统内置变量和time包
oneColumnScanner = true
}
}
}
var dbColumnFieldMap *map[string]reflect.StructField
var exportFieldMap *map[string]reflect.StructField
if !oneColumnScanner { // 如果不是一个直接可以映射的字段,默认为是sturct
// 获取到类型的字段缓存
// Get the type field cache
dbColumnFieldMap, exportFieldMap, err = getDBColumnExportFieldMap(&sliceElementType)
if err != nil {
err = fmt.Errorf("->Query-->getDBColumnFieldMap获取字段缓存错误:%w", err)
return err
}
}
// 反射获取 []driver.Value的值,用于处理nil值和自定义类型
driverValue := reflect.Indirect(reflect.ValueOf(rows))
driverValue = driverValue.FieldByName("lastcols")
// TODO 在这里确定字段直接接收或者struct反射,sqlRowsValues 就不再额外处理了,直接映射数据,提升性能
// 循环遍历结果集
// Loop through the result set
for rows.Next() {
pv := reflect.New(sliceElementType)
if oneColumnScanner {
err = sqlRowsValues(ctx, config, nil, &sliceElementType, rows, &driverValue, columnTypes, pv.Interface(), dbColumnFieldMap, exportFieldMap)
} else {
err = sqlRowsValues(ctx, config, &pv, &sliceElementType, rows, &driverValue, columnTypes, nil, dbColumnFieldMap, exportFieldMap)
}
// err = sqlRowsValues(ctx, dialect, &pv, rows, &driverValue, columnTypes, oneColumnScanner, structType, &dbColumnFieldMap, &exportFieldMap)
pv = pv.Elem()
// scan赋值.是一个指针数组,已经根据struct的属性类型初始化了,sql驱动能感知到参数类型,所以可以直接赋值给struct的指针.这样struct的属性就有值了
// scan assignment. It is an array of pointers that has been initialized according to the attribute type of the struct,The sql driver can perceive the parameter type,so it can be directly assigned to the pointer of the struct. In this way, the attributes of the struct have values
// scanerr := rows.Scan(values...)
if err != nil {
err = fmt.Errorf("->Query-->sqlRowsValues错误:%w", err)
FuncLogError(ctx, err)
return err
}
// values[i] = f.Addr().Interface()
// 通过反射给slice添加元素
// Add elements to slice through reflection
if sliceElementTypePtr { // 如果数组里是指针地址,*[]*struct
sliceValue.Set(reflect.Append(sliceValue, pv.Addr()))
} else {
sliceValue.Set(reflect.Append(sliceValue, pv))
}
}
// 查询总条数
// Query total number
if finder.SelectTotalCount && page != nil {
count, errCount := selectCount(ctx, finder)
if errCount != nil {
errCount = fmt.Errorf("->Query-->selectCount查询总条数错误:%w", errCount)
FuncLogError(ctx, errCount)
return errCount
}
page.setTotalCount(count)
}
return nil
}
var (
errQueryRowMapFinder = errors.New("->QueryRowMap-->finder参数不能为nil")
errQueryRowMapMany = errors.New("->QueryRowMap查询出多条数据")
)
// QueryRowMap 根据Finder查询,封装Map
// context必须传入,不能为空
// QueryRowMap encapsulates Map according to Finder query
// context must be passed in and cannot be empty
func QueryRowMap(ctx context.Context, finder *Finder) (map[string]interface{}, error) {
return queryRowMap(ctx, finder)
}
var queryRowMap = func(ctx context.Context, finder *Finder) (map[string]interface{}, error) {
if finder == nil {
FuncLogError(ctx, errQueryRowMapFinder)
return nil, errQueryRowMapFinder
}
resultMapList, errList := QueryMap(ctx, finder, nil)
if errList != nil {
errList = fmt.Errorf("->QueryRowMap-->QueryMap查询错误:%w", errList)
FuncLogError(ctx, errList)
return nil, errList
}
if resultMapList == nil {
return nil, nil
}
if len(resultMapList) > 1 {
FuncLogError(ctx, errQueryRowMapMany)
return resultMapList[0], errQueryRowMapMany
} else if len(resultMapList) == 0 { // 数据库不存在值
return nil, nil
}
return resultMapList[0], nil
}
var errQueryMapFinder = errors.New("->QueryMap-->finder参数不能为nil")
// QueryMap 根据Finder查询,封装Map数组
// 根据数据库字段的类型,完成从[]byte到Go类型的映射,理论上其他查询方法都可以调用此方法,但是需要处理sql.Nullxxx等驱动支持的类型
// context必须传入,不能为空
// QueryMap According to Finder query, encapsulate Map array
// According to the type of database field, the mapping from []byte to Go 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 QueryMap(ctx context.Context, finder *Finder, page *Page) ([]map[string]interface{}, error) {
return queryMap(ctx, finder, page)
}
var queryMap = func(ctx context.Context, finder *Finder, page *Page) (resultMapList []map[string]interface{}, err error) {
if finder == nil {
FuncLogError(ctx, errQueryMapFinder)
return nil, errQueryMapFinder
}
// 从contxt中获取数据库连接,可能为nil
// Get database connection from contxt, may be nil
dbConnection, errFromContxt := getDBConnectionFromContext(ctx)
if errFromContxt != nil {
FuncLogError(ctx, errFromContxt)
return nil, errFromContxt
}
// 自己构建的dbConnection
// dbConnection built by yourself
if dbConnection != nil && dbConnection.db == nil {
FuncLogError(ctx, errDBConnection)
return nil, errDBConnection
}
config, errConfig := getConfigFromConnection(ctx, dbConnection, 0)
if errConfig != nil {
FuncLogError(ctx, errConfig)
return nil, errConfig
}
sqlstr, errSQL := wrapQuerySQL(ctx, config, finder, page)
if errSQL != nil {
errSQL = fmt.Errorf("->QueryMap -->wrapQuerySQL查询SQL语句错误:%w", errSQL)
FuncLogError(ctx, errSQL)
return nil, errSQL
}
// 检查dbConnection.有可能会创建dbConnection或者开启事务,所以要尽可能的接近执行时检查
// Check db Connection. It is possible to create a db Connection or start a transaction, so check it as close as possible to the execution
var errDbConnection error
ctx, dbConnection, errDbConnection = checkDBConnection(ctx, dbConnection, false, 0)
if errDbConnection != nil {
return nil, errDbConnection
}
// 根据语句和参数查询
// Query based on statements and parameters
rows, errQueryContext := dbConnection.queryContext(ctx, &sqlstr, &finder.values)
if errQueryContext != nil {
errQueryContext = fmt.Errorf("->QueryMap-->queryContext查询rows错误:%w", errQueryContext)
FuncLogError(ctx, errQueryContext)
return nil, errQueryContext
}
// 先判断error 再关闭
defer func() {
// 先判断error 再关闭
rows.Close()
// 捕获panic,赋值给err,避免程序崩溃
if r := recover(); r != nil {
var errOk bool
err, errOk = r.(error)
if errOk {
err = fmt.Errorf("->QueryMap-->recover异常:%w", err)
FuncLogPanic(ctx, err)
} else {
err = fmt.Errorf("->QueryMap-->recover异常:%v", r)
FuncLogPanic(ctx, err)
}
}
}()
// 数据库返回的列类型
// The types returned by column Type.scan Type are all []byte, use column Type.database Type to judge one by one
columnTypes, errColumnTypes := rows.ColumnTypes()
if errColumnTypes != nil {
errColumnTypes = fmt.Errorf("->QueryMap-->rows.ColumnTypes数据库返回列名错误:%w", errColumnTypes)
FuncLogError(ctx, errColumnTypes)
return nil, errColumnTypes
}
// 反射获取 []driver.Value的值
driverValue := reflect.Indirect(reflect.ValueOf(rows))
driverValue = driverValue.FieldByName("lastcols")
resultMapList = make([]map[string]interface{}, 0)
columnTypeLen := len(columnTypes)
// 循环遍历结果集
// Loop through the result set
for rows.Next() {
// 接收数据库返回的数据,需要使用指针接收
// To receive the data returned by the database, you need to use the pointer to receive
values := make([]interface{}, columnTypeLen)
// 使用指针类型接收字段值,需要使用interface{}包装一下
// To use the pointer type to receive the field value, you need to use interface() to wrap it
result := make(map[string]interface{})
// 记录需要类型转换的字段信息
var fieldTempDriverValueMap map[int]*driverValueInfo
if iscdvm {
fieldTempDriverValueMap = make(map[int]*driverValueInfo)
}
// 给数据赋值初始化变量
// Initialize variables by assigning values to data
for i, columnType := range columnTypes {
dv := driverValue.Index(i)
if dv.IsValid() && dv.InterfaceData()[0] == 0 { // 该字段的数据库值是null,不再处理,使用默认值
values[i] = new(interface{})
continue
}
// 类型转换的接口实现
var customDriverValueConver ICustomDriverValueConver
// 是否需要类型转换
var converOK bool = false
// 类型转换的临时值
var tempDriverValue driver.Value
// 根据接收的类型,获取到类型转换的接口实现,优先匹配指定的数据库类型
databaseTypeName := strings.ToUpper(columnType.DatabaseTypeName())
// 判断是否有自定义扩展,避免无意义的反射
if iscdvm {
customDriverValueConver, converOK = customDriverValueMap[config.Dialect+"."+databaseTypeName]
if !converOK {
customDriverValueConver, converOK = customDriverValueMap[databaseTypeName]
}
}
var errGetDriverValue error
// 如果需要类型转换
if converOK {
// 获取需要转的临时值
tempDriverValue, errGetDriverValue = customDriverValueConver.GetDriverValue(ctx, columnType, nil)
if errGetDriverValue != nil {
errGetDriverValue = fmt.Errorf("->QueryMap-->customDriverValueConver.GetDriverValue错误:%w", errGetDriverValue)
FuncLogError(ctx, errGetDriverValue)
return nil, errGetDriverValue
}
// 返回值为nil,不做任何处理,使用原始逻辑
if tempDriverValue == nil {
values[i] = new(interface{})
} else { // 如果需要类型转换
values[i] = tempDriverValue
dvinfo := driverValueInfo{}
dvinfo.customDriverValueConver = customDriverValueConver