Introduction

Custom callback handling is the most common implementation in interface development, where it involves replacing and modifying some methods in the interface to inject custom logic into the default implementation of the driver. By referring to the interface relationship diagram (ORM - Interface), we understand that all SQL statement executions will pass through the DoQuery, DoExec, or DoFilter interfaces. Depending on the requirements, you can implement and override the relevant interface methods in the custom driver to achieve the desired functionality.

A common use case is to perform log recording or unified security checks on SQL at the ORM lower layer.

Example Usage

Let’s look at an example of custom callback handling. Suppose we need to log all executed SQL statements into a monitor table for SQL auditing purposes. The easiest way to achieve this is by creating a custom Driver and then overriding the underlying interface methods of ORM. To simplify the example, the following code demonstrates a custom MySQL driver, which inherits from the mysql module’s Driver under drivers.

  1. package driver
  2. import (
  3. "context"
  4. "github.com/gogf/gf/contrib/drivers/mysql/v2"
  5. "github.com/gogf/gf/v2/database/gdb"
  6. "github.com/gogf/gf/v2/os/gtime"
  7. )
  8. // MyDriver is a custom database driver, which is used for testing only.
  9. // For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver
  10. // gdb.Driver and overwrites its functions DoQuery and DoExec.
  11. // So if there's any sql execution, it goes through MyDriver.DoQuery/MyDriver.DoExec firstly
  12. // and then gdb.Driver.DoQuery/gdb.Driver.DoExec.
  13. // You can call it sql "HOOK" or "HiJack" as your will.
  14. type MyDriver struct {
  15. *mysql.Driver
  16. }
  17. var (
  18. // customDriverName is my driver name, which is used for registering.
  19. customDriverName = "MyDriver"
  20. )
  21. func init() {
  22. // It here registers my custom driver in package initialization function "init".
  23. // You can later use this type in the database configuration.
  24. if err := gdb.Register(customDriverName, &MyDriver{}); err != nil {
  25. panic(err)
  26. }
  27. }
  28. // New creates and returns a database object for mysql.
  29. // It implements the interface of gdb.Driver for extra database driver installation.
  30. func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) {
  31. return &MyDriver{
  32. &mysql.Driver{
  33. Core: core,
  34. },
  35. }, nil
  36. }
  37. // DoCommit commits current sql and arguments to underlying sql driver.
  38. func (d *MyDriver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) {
  39. tsMilliStart := gtime.TimestampMilli()
  40. out, err = d.Core.DoCommit(ctx, in)
  41. tsMilliFinished := gtime.TimestampMilli()
  42. _, _ = in.Link.ExecContext(ctx,
  43. "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)",
  44. gdb.FormatSqlWithArgs(in.Sql, in.Args),
  45. tsMilliFinished-tsMilliStart,
  46. gtime.Now(),
  47. err,
  48. )
  49. return
  50. }

We see that a custom driver is registered with a unique name in the package initialization function init using gdb.Register("MyDriver", &MyDriver{}). We can also override the existing framework mysql driver with our own driver using gdb.Register("mysql", &MyDriver{}).

ORM Interface - Callback - 图1tip

The driver name mysql is the default name for the framework’s DriverMysql.

Since we are using a new driver name MyDriver, it is necessary to specify this driver name in the type field in the gdb configuration. Below is an example configuration:

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

Notes

In the implementation of interface methods, use the Link input object parameter to operate on the database. Using the g.DB method to get a database object for operations may lead to deadlock issues.