中间件

什么是中间件

在juice中,中间件是一个接口,接口的描述如下:

// QueryHandler defines the handler of the query.
type QueryHandler func(ctx context.Context, query string, args ...any) (sql.Rows, error)

// ExecHandler defines the handler of the exec.
type ExecHandler func(ctx context.Context, query string, args ...any) (sql.Result, error)

// Middleware defines the interface for intercepting and processing SQL statement executions.
// It implements the interceptor pattern, allowing cross-cutting concerns like logging,
// timeout management, and connection switching to be handled transparently.
type Middleware interface {
    // QueryContext intercepts and processes SELECT query executions.
    // It receives the statement, configuration, and the next handler in the chain.
    // Must return a QueryHandler that processes the actual query execution.
    QueryContext(stmt Statement, configuration Configuration, next QueryHandler) QueryHandler

    // ExecContext intercepts and processes INSERT/UPDATE/DELETE executions.
    // It receives the statement, configuration, and the next handler in the chain.
    // Must return an ExecHandler that processes the actual execution.
    ExecContext(stmt Statement, configuration Configuration, next ExecHandler) ExecHandler
}

中间件的作用是在执行SQL语句之前,对SQL语句进行一些处理,比如SQL语句的日志记录,SQL语句的缓存等等。

当执行的是查询操作的时候,QueryContext 将会被执行。否则执行 ExecContext

juice中内置了一些中间件,比如:

  • juice/middleware/DebugMiddleware:用于打印SQL语句的中间件。

// logger is a default logger for debug.
var logger = log.New(log.Writer(), "[juice] ", log.Flags())

// DebugMiddleware is a middleware that logs SQL statements with their execution time and parameters.
// It provides debugging capabilities by printing formatted SQL queries along with execution metrics.
// The middleware can be enabled/disabled through statement attributes or global configuration settings.
type DebugMiddleware struct{}

// QueryContext implements Middleware.
// QueryContext logs SQL SELECT statements with their execution time and parameters.
// The logging includes statement ID, SQL query, arguments, and execution duration.
// Logging is controlled by the debug mode setting from statement attributes or global configuration.
func (m *DebugMiddleware) QueryContext(stmt Statement, configuration Configuration, next QueryHandler) QueryHandler {
    if !m.isDeBugMode(stmt, configuration) {
            return next
    }
    // wrapper QueryHandler
    return func(ctx context.Context, query string, args ...any) (sql.Rows, error) {
            start := time.Now()
            rows, err := next(ctx, query, args...)
            spent := time.Since(start)
            logger.Printf("\x1b[33m[%s]\x1b[0m args: \u001B[34m%v\u001B[0m time: \u001B[31m%v\u001B[0m \x1b[32m%s\x1b[0m",
                    stmt.Name(), query, args, spent, query)
            return rows, err
    }
}

// ExecContext implements Middleware.
// ExecContext logs SQL INSERT/UPDATE/DELETE statements with their execution time and parameters.
// The logging includes statement ID, SQL query, arguments, and execution duration.
// Logging is controlled by the debug mode setting from statement attributes or global configuration.
func (m *DebugMiddleware) ExecContext(stmt Statement, configuration Configuration, next ExecHandler) ExecHandler {
    if !m.isDeBugMode(stmt, configuration) {
            return next
    }
    // wrapper ExecContext
    return func(ctx context.Context, query string, args ...any) (sql.Result, error) {
            start := time.Now()
            rows, err := next(ctx, query, args...)
            spent := time.Since(start)
            logger.Printf("\x1b[33m[%s]\x1b[0m args: \u001B[34m%v\u001B[0m time: \u001B[31m%v\u001B[0m \x1b[32m%s\x1b[0m",
                    stmt.Name(), query, args, spent, query)
            return rows, err
    }
}

// isDeBugMode determines whether debug logging should be enabled for the given statement.
// It checks debug settings in the following priority order:
// 1. Statement-level "debug" attribute (if set to "false", disables debug)
// 2. Global configuration "debug" setting (if set to "false", disables debug)
// 3. Default is true (debug mode enabled) if neither is explicitly set to false
//
// Returns true if debug mode should be enabled, false otherwise.
func (m *DebugMiddleware) isDeBugMode(stmt Statement, configuration Configuration) bool {
    // try to get the debug mode from the Statement
    debug := stmt.Attribute("debug")
    // if the debug mode is not set, try to get the debug mode from the configuration
    if debug == "false" {
            return false
    }
    if configuration.Settings().Get("debug") == "false" {
            return false
    }
    return true
}

当你启用了这个中间件,juice会将每次执行的sql语句和参数写入到log包的默认的writer里面(默认是console),并且记录耗时。

当不想使用这个中间件的时候,可以在setting里面将debug设置为false, 这样就会全局关闭这个中间件。

<settings>
    <setting name="debug" value="false"/>
</settings>

当你想局部禁用这个功能的时候,可以在对应的action上面配置,如:

<insert id="xxx" debug="false">
</insert>
  • juice/middleware/TimeoutMiddleware:用于控制sql执行超时。

// TimeoutMiddleware is a middleware that manages query execution timeouts.
// It sets context timeouts for SQL statements to prevent long-running queries from hanging.
// The timeout value is obtained from the statement's "timeout" attribute and is specified in milliseconds.
type TimeoutMiddleware struct{}

// QueryContext implements Middleware.
// QueryContext sets a context timeout for SELECT queries to prevent long-running operations.
// The timeout value is obtained from the statement's "timeout" attribute.
// If timeout is <= 0, no timeout is applied and the original handler is returned unchanged.
func (t TimeoutMiddleware) QueryContext(stmt Statement, _ Configuration, next QueryHandler) QueryHandler {
    timeout := t.getTimeout(stmt)
    if timeout <= 0 {
            return next
    }
    return func(ctx context.Context, query string, args ...any) (sql.Rows, error) {
            ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Millisecond)
            defer cancel()
            return next(ctx, query, args...)
    }
}

// ExecContext implements Middleware.
// ExecContext sets a context timeout for INSERT/UPDATE/DELETE operations to prevent long-running operations.
// The timeout value is obtained from the statement's "timeout" attribute.
// If timeout is <= 0, no timeout is applied and the original handler is returned unchanged.
func (t TimeoutMiddleware) ExecContext(stmt Statement, _ Configuration, next ExecHandler) ExecHandler {
    timeout := t.getTimeout(stmt)
    if timeout <= 0 {
            return next
    }
    return func(ctx context.Context, query string, args ...any) (sql.Result, error) {
            ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Millisecond)
            defer cancel()
            return next(ctx, query, args...)
    }
}

// getTimeout retrieves the timeout value from the statement's "timeout" attribute.
// Returns the timeout value in milliseconds, or 0 if not set or invalid.
func (t TimeoutMiddleware) getTimeout(stmt Statement) (timeout int64) {
    timeoutAttr := stmt.Attribute("timeout")
    if timeoutAttr == "" {
            return
    }
    timeout, _ = strconv.ParseInt(timeoutAttr, 10, 64)
    return
}

在对应action标签的属性上面加上timeout属性,即可启用这个功能,timeout的单位为毫秒,如:

<insert id="xxx" timeout="1000">
</insert>

Attention

注意:TimeoutMiddleware是在go语言级别实现的超时,而不是数据库级别。

自定义中间件

自定义中间只需要实现 Middleware 接口, 然后注册入对应的engine即可,如:

func main() {
    var mymiddleware juice.Middleware = yourMiddlewareImpl{}

    cfg, err := juice.NewXMLConfiguration("config.xml")
    if err != nil {
        panic(err)
    }

    engine, err := juice.Default(cfg)
    if err != nil {
        panic(err)
    }

    engine.Use(mymiddleware)
}