# 6.1 GORM

# 6.1.1 第三方库

# 6.1.2 配置参数

名称 类型 描述
dsn string 连接串
logLevel string 日志级别,可选值:silent, error, warn, info
maxIdleConns int 最大空闲连接数,默认为50
maxOpenConns int 最大打开连接数,默认为50
slowThreshold string 慢查询阈值,支持单位:纳秒(ns)、微秒(us 或 µs)、毫秒(ms)、秒(s)、分(m)、小时(h)、天(d)。默认2s
connMaxLifetime string 连接最大生命周期,支持单位:纳秒(ns)、微秒(us 或 µs)、毫秒(ms)、秒(s)、分(m)、小时(h)、天(d)。默认1h

# 6.1.3 示例代码

以下完整示例详见:third-gorm-example (opens new window)

  1. 组件封装

文件位置:third-gorm-example/gorm/mysql.go (opens new window)

package gorm

import (
	"fmt"
	"strings"
	"time"

	_ "third-gorm-example/gorm/serializer"

	"github.com/dobyte/due/v2/core/pool"
	"github.com/dobyte/due/v2/etc"
	"github.com/dobyte/due/v2/log"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	loggers "gorm.io/gorm/logger"
)

var factory = pool.NewFactory(func(name string) (*gorm.DB, error) {
	return NewInstance(fmt.Sprintf("etc.mysql.%s", name))
})

type Config struct {
	DSN             string        `json:"dsn"`
	LogLevel        string        `json:"logLevel"`
	MaxIdleConns    int           `json:"maxIdleConns"`
	MaxOpenConns    int           `json:"maxOpenConns"`
	SlowThreshold   time.Duration `json:"slowThreshold"`
	ConnMaxLifetime time.Duration `json:"connMaxLifetime"`
}

// Instance 获取实例
func Instance(name ...string) *gorm.DB {
	var (
		err error
		ins *gorm.DB
	)

	if len(name) == 0 {
		ins, err = factory.Get("default")
	} else {
		ins, err = factory.Get(name[0])
	}

	if err != nil {
		log.Fatalf("create mysql instance failed: %v", err)
	}

	return ins
}

// NewInstance 新建实例
func NewInstance[T string | Config | *Config](config T) (*gorm.DB, error) {
	var (
		conf *Config
		v    any = config
	)

	switch c := v.(type) {
	case string:
		conf = &Config{
			DSN:             etc.Get(fmt.Sprintf("%s.dsn", c)).String(),
			LogLevel:        etc.Get(fmt.Sprintf("%s.logLevel", c)).String(),
			MaxIdleConns:    etc.Get(fmt.Sprintf("%s.maxIdleConns", c)).Int(),
			MaxOpenConns:    etc.Get(fmt.Sprintf("%s.maxOpenConns", c)).Int(),
			SlowThreshold:   etc.Get(fmt.Sprintf("%s.slowThreshold", c), "2s").Duration(),
			ConnMaxLifetime: etc.Get(fmt.Sprintf("%s.connMaxLifetime", c), "1h").Duration(),
		}
	case Config:
		conf = &c
	case *Config:
		conf = c
	}

	var logLevel loggers.LogLevel
	switch strings.ToLower(conf.LogLevel) {
	case "silent":
		logLevel = loggers.Silent
	case "error":
		logLevel = loggers.Error
	case "warn":
		logLevel = loggers.Warn
	case "info":
		logLevel = loggers.Info
	default:
		logLevel = loggers.Warn
	}

	db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: conf.DSN,
	}), &gorm.Config{Logger: &logger{
		logLevel:                  logLevel,
		ignoreRecordNotFoundError: true,
		slowThreshold:             conf.SlowThreshold,
	}})
	if err != nil {
		return nil, err
	}

	database, err := db.DB()
	if err != nil {
		return nil, err
	}

	if conf.MaxIdleConns > 0 {
		database.SetMaxIdleConns(conf.MaxIdleConns)
	}

	if conf.MaxOpenConns > 0 {
		database.SetMaxOpenConns(conf.MaxOpenConns)
	}

	if conf.ConnMaxLifetime > 0 {
		database.SetConnMaxLifetime(conf.ConnMaxLifetime)
	}

	return db, nil
}
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

文件位置:third-gorm-example/gorm/logger.go (opens new window)

package gorm

import (
	"context"
	"fmt"
	"time"

	"github.com/dobyte/due/v2/errors"
	"github.com/dobyte/due/v2/log"
	loggers "gorm.io/gorm/logger"
)

const traceStr = "%s\n[%.3fms] [rows:%v] %s"

type logger struct {
	logLevel                  loggers.LogLevel
	ignoreRecordNotFoundError bool
	slowThreshold             time.Duration
}

var _ loggers.Interface = &logger{}

func (l *logger) LogMode(level loggers.LogLevel) loggers.Interface {
	newLogger := *l
	newLogger.logLevel = level
	return &newLogger
}

func (l *logger) Info(ctx context.Context, msg string, data ...any) {
	if l.logLevel >= loggers.Info {
		log.Printf(log.LevelInfo, msg, data)
	}
}

func (l *logger) Warn(ctx context.Context, msg string, data ...any) {
	if l.logLevel >= loggers.Warn {
		log.Printf(log.LevelWarn, msg, data)
	}
}

func (l *logger) Error(ctx context.Context, msg string, data ...any) {
	if l.logLevel >= loggers.Error {
		log.Printf(log.LevelError, msg, data)
	}
}

// Trace print sql message
func (l *logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if l.logLevel <= loggers.Silent {
		return
	}

	switch elapsed := time.Since(begin); {
	case err != nil && l.logLevel >= loggers.Error && (!errors.Is(err, loggers.ErrRecordNotFound) || !l.ignoreRecordNotFoundError):
		sql, rows := fc()
		if rows == -1 {
			log.Printf(log.LevelError, traceStr, err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			log.Printf(log.LevelError, traceStr, err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case elapsed > l.slowThreshold && l.slowThreshold != 0 && l.logLevel >= loggers.Error:
		sql, rows := fc()
		slowLog := fmt.Sprintf("SLOW SQL >= %v", l.slowThreshold)
		if rows == -1 {
			log.Printf(log.LevelWarn, traceStr, slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			log.Printf(log.LevelWarn, traceStr, slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case l.logLevel == loggers.Info:
		sql, rows := fc()
		if rows == -1 {
			log.Printf(log.LevelInfo, traceStr, "", float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			log.Printf(log.LevelInfo, traceStr, "", float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	}
}
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

文件位置:third-gorm-example/gorm/serializer/json.go (opens new window)

package serializer

import (
	"context"
	"reflect"

	"github.com/dobyte/due/v2/encoding/json"
	"gorm.io/gorm/schema"
)

func init() {
	schema.RegisterSerializer("json", JSONSerializer{})
}

type JSONSerializer struct {
}

// Scan implements serializer interface
func (JSONSerializer) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue any) (err error) {
	fieldValue := reflect.New(field.FieldType)

	if dbValue != nil {
		var bytes []byte
		switch v := dbValue.(type) {
		case []byte:
			bytes = v
		case string:
			bytes = []byte(v)
		default:
			bytes, err = json.Marshal(v)
			if err != nil {
				return err
			}
		}

		if len(bytes) > 0 {
			err = json.Unmarshal(bytes, fieldValue.Interface())
		}
	}

	field.ReflectValueOf(ctx, dst).Set(fieldValue.Elem())
	return
}

// Value implements serializer interface
func (JSONSerializer) Value(ctx context.Context, field *schema.Field, dst reflect.Value, fieldValue any) (any, error) {
	result, err := json.Marshal(fieldValue)
	if err != nil {
		return "", err
	}

	if string(result) == "null" {
		switch field.FieldType.Kind() {
		case reflect.Array, reflect.Slice:
			return "[]", nil
		default:
			return "{}", nil
		}
	}

	return string(result), nil
}
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
  1. 配置示例

文件位置:third-gorm-example/etc/etc.toml (opens new window)

[mysql.default]
	# dsn连接信息
	dsn = "root:123456@tcp(127.0.0.1:3306)/due_default?charset=utf8mb4&parseTime=True&loc=Local"
	# 日志级别; silent | error | warn | info
	logLevel = "info"
	# 最大空闲连接数,默认为50
	maxIdleConns = 50
	# 最大打开连接数,默认为50
	maxOpenConns = 50
	# 慢查询阈值,支持单位:纳秒(ns)、微秒(us | µs)、毫秒(ms)、秒(s)、分(m)、小时(h)、天(d)。默认2s
	slowThreshold = "2s"
	# 连接最大生命周期,支持单位:纳秒(ns)、微秒(us | µs)、毫秒(ms)、秒(s)、分(m)、小时(h)、天(d)。默认1h
	connMaxLifetime = "1h"
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 组件调用

文件位置:third-gorm-example/main.go (opens new window)

package main

import (
	gormcomp "third-gorm-example/gorm"

	"github.com/dobyte/due/v2/log"
	"gorm.io/gorm"
)

func main() {
	db := gormcomp.Instance("default")

	if err := db.Connection(func(tx *gorm.DB) error {
		return nil
	}); err != nil {
		log.Errorf("db connection error: %v", err)
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18