自定义回调处理是最常见的接口开发实现,开发中只需要对接口中的部分方法进行替换与修改,在驱动默认实现逻辑中注入自定义逻辑。所有的SQL语句执行必定会通过DoQuery或者DoExec或者 DoFilter接口,根据需求在自定义的驱动中实现并覆盖相关接口方法实现所需功能即可。

    我们来看一个自定义回调处理的示例,现需要将所有执行的SQL语句记录到monitor表中,以方便于进行SQL审计。为简化示例编写,以下代码实现了一个自定义的MySQL驱动,该驱动继承于driversmysql模块内已经实现的DriverMysql

    自2.1.0版本之后,ORM驱动模块独立,需要开发者手动引入至项目内。

    以mysql为例,需引入 github.com/gogf/gf/contrib/drivers/mysql/v2 至go.mod,并导入到项目中使用。

    其它驱动详细请至 https://github.com/gogf/gf/tree/master/contrib/drivers 查看。

    1. package driver
    2. import (
    3. "context"
    4. "database/sql"
    5. "github.com/gogf/gf/contrib/drivers/mysql/v2"
    6. "github.com/gogf/gf/v2/database/gdb"
    7. "github.com/gogf/gf/v2/os/gtime"
    8. "github.com/gogf/gf/v2/text/gstr"
    9. "github.com/gogf/gf/v2/util/gconv"
    10. "strconv"
    11. )
    12. // MyDriver is a custom database driver, which is used for testing only.
    13. // For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
    14. // gdb.DriverMysql and overwrites its functions DoQuery and DoExec.
    15. // So if there's any sql execution, it goes through MyDriver.DoQuery/MyDriver.DoExec firstly
    16. // and then gdb.DriverMysql.DoQuery/gdb.DriverMysql.DoExec.
    17. // You can call it sql "HOOK" or "HiJack" as your will.
    18. type MyDriver struct {
    19. *mysql.DriverMysql
    20. }
    21. var (
    22. // customDriverName is my driver name, which is used for registering.
    23. customDriverName = "MyDriver"
    24. )
    25. func init() {
    26. // It here registers my custom driver in package initialization function "init".
    27. // You can later use this type in the database configuration.
    28. if err := gdb.Register(customDriverName, &MyDriver{}); err != nil {
    29. panic(err)
    30. }
    31. }
    32. // New creates and returns a database object for mysql.
    33. // It implements the interface of gdb.Driver for extra database driver installation.
    34. func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
    35. return &MyDriver{
    36. &mysql.DriverMysql{
    37. Core: core,
    38. },
    39. }, nil
    40. }
    41. // DoQuery commits the sql string and its arguments to underlying driver
    42. // through given link object and returns the execution result.
    43. func (d *MyDriver) DoQuery(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (rows gdb.Result, err error) {
    44. tsMilli := gtime.TimestampMilli()
    45. rows, err = d.DriverMysql.DoQuery(ctx, link, sql, args...)
    46. _, _ = link.ExecContext(ctx,
    47. "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
    48. gdb.FormatSqlWithArgs(sql, args),
    49. gtime.TimestampMilli()-tsMilli,
    50. gtime.Now(),
    51. err,
    52. )
    53. return
    54. }
    55. // DoExec commits the query string and its arguments to underlying driver
    56. // through given link object and returns the execution result.
    57. func (d *MyDriver) DoExec(ctx context.Context, link gdb.Link, sql string, args ...interface{}) (result sql.Result, err error) {
    58. tsMilli := gtime.TimestampMilli()
    59. result, err = d.DriverMysql.DoExec(ctx, link, sql, args...)
    60. _, _ = link.ExecContext(ctx,
    61. "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
    62. gdb.FormatSqlWithArgs(sql, args),
    63. gtime.TimestampMilli()-tsMilli,
    64. gtime.Now(),
    65. err,
    66. )
    67. return
    68. }
    69. // DoFilter is a hook function, which filters the sql and its arguments before it's committed to underlying driver.
    70. // The parameter `link` specifies the current database connection operation object. You can modify the sql
    71. // string `sql` and its arguments `args` as you wish before they're committed to driver.
    72. func (d *MyDriver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) {
    73. //Filter sql Step1
    74. sql_new := gstr.Replace(sql, "\n", "")
    75. //Filter sql Step2
    76. sql_new = gstr.Replace(sql_new, "\t", "")
    77. //... Filter what you want ...
    78. //Filter args Step1
    79. for _, v := range args {
    80. switch v.(type) {
    81. case gdb.Map:
    82. //Do it what you wan
    83. case string:
    84. //Do it what you wan
    85. }
    86. }
    87. return sql_new, args, nil
    88. }
    89. // ConvertDataForRecord is a very important function, which does converting for any data that
    90. // will be inserted into table/collection as a record.
    91. //
    92. // The parameter `value` should be type of *map/map/*struct/struct.
    93. // It supports embedded struct definition for struct.
    94. func (d *MyDriver) ConvertDataForRecord(ctx context.Context, data interface{}) (map[string]interface{}, error) {
    95. //this hook is convert data to map[string]interface{}
    96. result := make(map[string]interface{}, 0)
    97. //like this
    98. switch data.(type) {
    99. case gdb.Map:
    100. result = gconv.Map(data)
    101. case gdb.List:
    102. for k, v := range data.(gdb.List) {
    103. result[strconv.Itoa(k)] = gconv.Map(v)
    104. }
    105. //case other type,do it what you want
    106. }
    107. return result, nil
    108. }

    我们看到,这里在包初始化方法init中使用了gdb.Register("MyDriver", &MyDriver{})来注册了了一个自定义名称的驱动。我们也可以通过gdb.Register("mysql", &MyDriver{})来覆盖已有的框架mysql驱动为自己的驱动。

    驱动名称mysql为框架默认的DriverMysql驱动的名称。

    由于这里我们使用了一个新的驱动名称MyDriver,因此在gdb配置中的type数据库类型时,需要填写该驱动名称。以下是一个使用配置的示例:

    1. database:
    2. default:
    3. - link: "MyDriver:root:12345678@tcp(127.0.0.1:3306)/user"