MyBatis-框架使用和分析

 一、基礎知識

MyBatis 是一款優秀的持久層框架,它支援自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 程式碼以及設置參數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或註解來配置和映射原始類型、介面為資料庫中的記錄。
本文測試和源碼分析參考版本: Mybatis-version:3.4.1

1.1 名詞術語

名稱
說明
SqlSession
作為MyBatis工作的主要頂層API,表示和資料庫交互的會話,完成必要資料庫增刪改查功能 。
Executor
MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢快取的維護。
StatementHandler
封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數、將Statement結果集轉換成List集合。
ParameterHandler
封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數、將Statement結果集轉換成List集合。
ResultSetHandler
負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合
TypeHandler
負責java數據類型和jdbc數據類型之間的映射和轉換
MappedStatement
MappedStatement維護了一條<select|update|delete|insert>節點的封裝
SqlSource
負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql對象中,並返回
BoundSql
表示動態生成的SQL語句以及相應的參數資訊
Configuration
MyBatis所有的配置資訊都維持在Configuration對象之中
MetaObject
MetaObject是MyBatis提供的工具類,它可以有效的獲取或修改一些重要對象的屬性。

1.2 流程原理分析

MyBatis執行流程節點說明

二、插件使用

與其稱為Mybatis插件,不如叫Mybatis攔截器,更加符合其功能定位,實際上它就是一個攔截器,應用代理模式,在方法級別上進行攔截。Mybatis3 插件採用責任鏈模式,通過動態代理組織多個攔截器(插件),通過這些攔截器可以改變Mybatis的默認行為(諸如SQL重寫之類的)。
責任鏈模式:責任鏈模式在面向對象程式設計里是一種軟體設計模式,它包含了一些命令對象和一系列的處理對象。每一個處理對象決定它能處理哪些命令對象,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法。

2.1 插件原理

// 插件聲明格式
@Intercepts({
    @Signature(type =StatementHandler.class,
        method="prepare" , 
        args={Connection.class})})
public class MyPlugin implements Interceptor{
    ......
} 

支援攔截的方法

  • 執行器Executor(update、query、commit、rollback等方法);
  • 參數處理器ParameterHandler(getParameterObject、setParameters方法);
  • 結果集處理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
  • SQL語法構建器StatementHandler(prepare、parameterize、batch、update、query等方法);
  • @Signature(type = StatementHandler.class, method = “query”, args = {Statement.class, ResultHandler.class}),編寫邏輯,基於StatementHandler介面,查看裡面對應方法,配置的參數,args為StatementHandler.query()方法的入參。參考程式碼:
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
在實際的攔截器中,可通過參數獲取對象如:
Statement statement = (Statement)invocation.getArgs()[0];
ResultHandler resultHandler = (ResultHandler)invocation.getArgs()[1];

流程說明

Mybatis的攔截器實現機制,使用的是JDK的InvocationHandler,當調用ParameterHandler,ResultSetHandler,StatementHandler,Executor的對象的時候,實際上使用的是Plugin這個代理類的對象,這個類實現了InvocationHandler介面。在調用上述被代理類的方法的時候,就會執行Plugin的invoke方法。
Plugin在invoke方法中根據@Intercepts的配置資訊(方法名,參數等)動態判斷是否需要攔截該方法,再然後使用需要攔截的方法Method封裝成Invocation,並調用Interceptor的proceed方法達到了攔截目標方法的結果。截器代理類對象->攔截器->目標方法,如:
Executor->Plugin->Interceptor->Invocation
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke

2.2 應用場景

場景
描述
分頁
mybatis的分頁默認是基於記憶體分頁的(查出所有,再截取),數據量大的情況下效率較低,不過使用mybatis插件可以改變該行為,只需要攔截StatementHandler類的prepare方法,改變要執行的SQL語句為分頁語句即可。
公共欄位統一賦值
一般業務系統都會有創建者,修改者等基礎欄位欄位,對於基礎欄位的賦值,可以在DAO層統一攔截處理,可以用mybatis插件攔截Executor類的update方法,對相關參數進行統一賦值即可。
性能監控
對於SQL語句執行的性能監控,可以通過攔截Executor類的update, query等方法,用日誌記錄每個方法執行的時間。
其他
其實mybatis擴展性還是很強的,基於插件機制,基本上可以控制SQL執行的各個階段,如執行階段,參數處理階段,語法構建階段,結果集處理階段,具體可以根據項目業務來實現對應業務邏輯。

三、框架使用

3.1 基於Java API模式

MyBatis 3.0版本,框架支援不使用配置xml文件,配置可走Java API的模式,便於項目管理和維護。

基礎配置資訊

設置資料庫的連接基礎資訊,放入到application.properties文件中。
配置項目的pom依賴文件,具體參考項目中的配置資訊。
基礎測試對象類:

**
 * @author wl
 * @description 單據生命流程日誌
 * @date 2019/10/9 21:06
 */
@Data
public class PayLifeLog implements Serializable {

    /**
     * 日誌主鍵編碼
     */
    private Integer logId;
    /**
     * 單據包編碼
     */
    private String packageId;
    /**
     * 單據編碼
     */
    private String billCode;
    /**
     * 節點執行結果
     */
    private Byte lifeStatus;
    /**
     * 節點
     */
    private Byte lifeState;
    /**
     * 備註
     */
    private String markMsg;
    /**
     * 更新時間
     */
    private LocalDateTime updatetime;
   }

View Code

構建DataBaseConfiguration配置類

package com.trace.base.tool.configuration; 
 
import com.zaxxer.hikari.HikariConfig; 
import com.zaxxer.hikari.HikariDataSource; 
import org.mybatis.spring.annotation.MapperScan; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Configuration; 
 
/** 
 * 資料庫層配置類 
 * 
 * @author wl 
 * @date 2021-4-27 
 */ 
@Configuration 
@MapperScan(basePackages = {"com.trace.base.tool.mapper.**"}, sqlSessionFactoryRef = "sqlSessionFactory") 
public class DataBaseConfiguration { 
    @Value("${hikaricp.dataSource.jdbc.driverClassName}") 
    private String driverClassName; 
    @Value("${hikaricp.dataSource.url}") 
    private String jdbcUrl; 
    @Value("${hikaricp.dataSource.username}") 
    private String username; 
    @Value("${hikaricp.dataSource.password}") 
    private String password; 
    @Value("${hikaricp.dataSource.connectionTestQuery}") 
    private String connectionTestQuery; 
    @Value("${hikaricp.dataSource.connectionTimeout}") 
    private long connectionTimeout; 
    @Value("${hikaricp.dataSource.idleTimeout}") 
    private long idleTimeout; 
    @Value("${hikaricp.dataSource.maxLifetime}") 
    private long maxLifetime; 
    @Value("${hikaricp.dataSource.maximumPoolSize}") 
    private int maximumPoolSize; 
    @Value("${hikaricp.dataSource.minimumIdle}") 
    private int minimumIdle; 
 
    /** 
     * 注入一個hikaricp dataSource 
     */ 
    @Bean(value = "dataSource", destroyMethod = "close") 
    public HikariDataSource hikariDataSource() { 
        HikariConfig hikariConfig = new HikariConfig(); 
        hikariConfig.setDriverClassName(driverClassName); 
        hikariConfig.setJdbcUrl(jdbcUrl); 
        hikariConfig.setUsername(username); 
        hikariConfig.setPassword(password); 
        hikariConfig.setConnectionTestQuery(connectionTestQuery); 
        hikariConfig.setConnectionTimeout(connectionTimeout); 
        hikariConfig.setIdleTimeout(idleTimeout); 
        hikariConfig.setMaxLifetime(maxLifetime); 
        hikariConfig.setMaximumPoolSize(maximumPoolSize); 
        hikariConfig.setMinimumIdle(minimumIdle); 
        return new HikariDataSource(hikariConfig); 
    } 
} 
資料庫配置類功能:
  • 讀取application.properties配置文件的資訊,並生成一個hikariDataSource對象。
  • 通過註解@MapperScan限定資料庫應用的包,支援項目中多數據源應用。

構建Mapper介面

package com.trace.base.tool.mapper;

import com.trace.base.tool.domain.base.PayLifeLog;
import com.trace.base.tool.sqlprovider.LifeLogSqlProvider;
import com.trace.base.tool.web.Page;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author wl
 * @description 支付請求日誌-數據介面
 * @date 2019/10/10 18:42
 */
@Mapper
@Repository
public interface LifeLogMapper {
    /**
     * 獲取批次支付失敗日誌
     *
     * @param billCode 單號
     * @return 結果
     */
    @SelectProvider(type = LifeLogSqlProvider.class, method = "getPayLifeLogByIdSql")
    PayLifeLog getPayLifeLogById(@Param("billCode") String billCode);
}
設置查詢參數:
  • 參考mybatis 3.4版本,帶上@param註解,保證參數傳遞正常。
  • @SelectProvider註解,指定基於Java API模式,提供具體sql資訊的配置類資訊。

構建SqlProvider配置類

package com.trace.base.tool.sqlprovider;

import com.trace.base.tool.domain.base.PayLifeLog;
import com.trace.base.tool.util.sql.SQL;
import com.trace.base.tool.web.Page;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;

/**
 * @author wl
 * @description 請求日誌sql生成器
 * @date 2019/10/8 18:43
 */
public class LifeLogSqlProvider {

    public String getPayLifeLogByIdSql(@Param("billCode") String billCode) {
        String sql = " select log_id as logId, package_id as packageId,bill_code as billCode,life_state as lifeState,life_status as lifeStatus,mark_msg as markMsg,updatetime  " +
                "from caiwu_pay_life_log where " +
                "bill_code = #{billCode}" +
                "limit 0,1";
        return sql;
    }
}
  • 編寫一個基本類,類似xml文件功能,生成對應執行的sql。
  • sql可參考網上或者Mybatis自身的一sql生成工具類 SQL(),擴展開發,方便生成sql語句。

構建RestController業務控制器

package com.trace.base.tool.controller;
import com.trace.base.tool.domain.base.PayLifeLog;
import com.trace.base.tool.mapper.LifeLogMapper;
import com.trace.base.tool.web.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * mybatis測試demo
 *
 * @author wl
 * @date 2020-12-01
 */
@RestController
@RequestMapping("mybatis")
@Validated
public class MyBatisController {

    @Autowired
    private LifeLogMapper lifeLogMapper;

    /**
     * 獲取當前日誌資訊
     *
     * @return 返回存儲數據
     */
    @GetMapping("/log")
    public PayLifeLog getTraceService() {
        return lifeLogMapper.getPayLifeLogById("5");
    }
}
注入Mapper介面,測試獲取數據。(正式項目不應該直接在Controller裡面調用Mapper,需設置一個Service層,流程為:Controller->Service->Mapper)。
啟動微服務,利用Postman測試效果如下圖:

3.2 編寫慢sql監控日誌插件

分析了sql插件的原理,再做一個自定義插件做測試。需求:
  • 基於環境配置變數,控制該插件是否需要啟用(如測試環境啟用,線上關閉)。
  • 當sql運行時長超過1秒時,記錄運行的sql資訊,寫入到慢sql日誌中。

基礎配置資訊

設置資料庫的連接基礎資訊,放入到application.properties文件中。
設置慢sql監控配置資訊:sql.slow.enable=true
配置項目的pom依賴文件,具體參考項目中的配置資訊。

構建DataBaseConfiguration配置類

package com.trace.base.tool.configuration;

import com.trace.base.tool.mybatis.study.StudySqlSessionFactoryBean;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

/**
 * 資料庫層配置類
 *
 * @author wl
 * @date 2021-4-27
 */
@Configuration
@MapperScan(basePackages = {"com.trace.base.tool.mapper.**"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataBaseConfiguration {
    @Value("${hikaricp.dataSource.jdbc.driverClassName}")
    private String driverClassName;
    @Value("${hikaricp.dataSource.url}")
    private String jdbcUrl;
    @Value("${hikaricp.dataSource.username}")
    private String username;
    @Value("${hikaricp.dataSource.password}")
    private String password;
    @Value("${hikaricp.dataSource.connectionTestQuery}")
    private String connectionTestQuery;
    @Value("${hikaricp.dataSource.connectionTimeout}")
    private long connectionTimeout;
    @Value("${hikaricp.dataSource.idleTimeout}")
    private long idleTimeout;
    @Value("${hikaricp.dataSource.maxLifetime}")
    private long maxLifetime;
    @Value("${hikaricp.dataSource.maximumPoolSize}")
    private int maximumPoolSize;
    @Value("${hikaricp.dataSource.minimumIdle}")
    private int minimumIdle;

    /**
     * 注入一個hikaricp dataSource
     */
    @Bean(value = "dataSource", destroyMethod = "close")
    public HikariDataSource hikariDataSource() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setDriverClassName(driverClassName);
        hikariConfig.setJdbcUrl(jdbcUrl);
        hikariConfig.setUsername(username);
        hikariConfig.setPassword(password);
        hikariConfig.setConnectionTestQuery(connectionTestQuery);
        hikariConfig.setConnectionTimeout(connectionTimeout);
        hikariConfig.setIdleTimeout(idleTimeout);
        hikariConfig.setMaxLifetime(maxLifetime);
        hikariConfig.setMaximumPoolSize(maximumPoolSize);
        hikariConfig.setMinimumIdle(minimumIdle);
        return new HikariDataSource(hikariConfig);
    }

    /**
     * 注入一個sqlSessionFactory
     */
    @Bean(value = "sqlSessionFactory")
    public StudySqlSessionFactoryBean sqlSessionFactory(HikariDataSource dataSource) {
        StudySqlSessionFactoryBean sessionFactoryBean = new StudySqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        return sessionFactoryBean;
    }

    /**
     * 主動注入一個transactionManger,適用多資料庫事務管理器環境
     */
    @Bean(value = "transactionManager")
    public DataSourceTransactionManager dataSourceTransactionManager(HikariDataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
資料庫配置類功能:
  • 讀取application.properties配置文件的資訊,並生成一個hikariDataSource對象。
  • 自定義一個StudySqlSessionFactoryBean繼承SqlSessionFactoryBean,便於擴展默認的SqlSessionFactoryBean功能,並基於MyBatis Java API功能,做插件或其他配置。

慢sql監控插件編寫

package com.trace.base.tool.mybatis.study.plugin;

import com.mysql.jdbc.PreparedStatement;
import com.trace.base.tool.logging.BaseLog;
import com.trace.base.tool.logging.Channel;
import com.trace.base.tool.logging.LevelEnum;
import com.trace.base.tool.mybatis.monitor.SlowSqlEnum;
import com.trace.base.tool.mybatis.monitor.SlowSqlLog;
import com.trace.base.tool.util.LocalDateTimeUtil;
import com.zaxxer.hikari.pool.ProxyStatement;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.jdbc.PreparedStatementLogger;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.postgresql.jdbc.PgStatement;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.Properties;

/**
 * 監控慢SQL插件
 *
 * @author wl
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
})
public class SlowSqlMonitorPlugin implements Interceptor {
    public static final String SLOW_SQL_ENABLE = "sql.slow.enable";
    private static boolean POSTGRESQL_DRIVER_AVAILABLE;
    private static boolean MYSQL_DRIVER_AVAILABLE;
    private static boolean HIKARICP_AVAILABLE;
    private static Field DELEGATE_FIELD;

    static {
        try {
            Class.forName("org.postgresql.jdbc.PgPreparedStatement");
            POSTGRESQL_DRIVER_AVAILABLE = true;
        } catch (ClassNotFoundException e) {
            // ignore
            POSTGRESQL_DRIVER_AVAILABLE = false;
        }
        try {
            Class.forName("com.mysql.jdbc.PreparedStatement");
            MYSQL_DRIVER_AVAILABLE = true;
        } catch (ClassNotFoundException e) {
            // ignore
            MYSQL_DRIVER_AVAILABLE = false;
        }
        try {
            Class.forName("com.zaxxer.hikari.pool.HikariProxyPreparedStatement");
            DELEGATE_FIELD = ProxyStatement.class.getDeclaredField("delegate");
            DELEGATE_FIELD.setAccessible(true);
            HIKARICP_AVAILABLE = true;
        } catch (ClassNotFoundException | NoSuchFieldException e) {
            // ignore
            HIKARICP_AVAILABLE = false;
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            Object obj = invocation.proceed();
            return obj;
        } finally {
            long end = System.currentTimeMillis();
            long used = end - start;
            // >= 1s
            final long max = 1000L;
            if (used >= max) {
                try {
                    Object target = invocation.getTarget();
                    String sql = "unknown";
                    if (target instanceof StatementHandler) {
                        sql = ((StatementHandler) target).getBoundSql().getSql();
                    }
                    // 外部提前做一次猜測是否為預處理語句,只用instanceof PreparedStatement有可能沒有?,這種情況不需要執行下面邏輯
                    boolean mightPreparedSql = sql.contains("?");
                    Object statementArg = invocation.getArgs()[0];
                    // 可能是預處理語句才處理
                    if (mightPreparedSql) {
                        // 這裡還要區分是否為debug模式,debug模式下,生成的connection和statement都是被mybatis logger類代理
                        if (Proxy.isProxyClass(statementArg.getClass())) {
                            // 獲取到真實被代理的statement
                            statementArg = ((PreparedStatementLogger) Proxy.getInvocationHandler(statementArg)).getPreparedStatement();
                        }
                        // 被HikariProxyPreparedStatement代理,通過反射才能獲取到真實的PreparedStatement
                        if (HIKARICP_AVAILABLE && statementArg instanceof ProxyStatement) {
                            java.sql.PreparedStatement preparedStatement = (java.sql.PreparedStatement) DELEGATE_FIELD.get(statementArg);
                            // postgresql,前提是SQL為預處理語句,避免非預處理語句也執行了toString()造成拿到記憶體地址
                            if (POSTGRESQL_DRIVER_AVAILABLE && preparedStatement instanceof PgStatement) {
                                // 因為PgPreparedStatement是保護類,只能使用PgStatement轉換,實際是執行子類的toString()
                                sql = preparedStatement.toString();
                            }
                            // mysql
                            else if (MYSQL_DRIVER_AVAILABLE && preparedStatement instanceof PreparedStatement) {
                                sql = ((PreparedStatement) preparedStatement).asSql();
                            }
                        }

                    }
                    // 記錄日誌資訊
                    SlowSqlLog slowSqlLog = new SlowSqlLog();
                    slowSqlLog.setTraceId("idnum-0001");
                    slowSqlLog.setType(SlowSqlEnum.DML);
                    slowSqlLog.setMessage("執行DML[" + sql + "]超時1秒");
                    slowSqlLog.setStart(LocalDateTimeUtil.formatMilliPlus8(start));
                    slowSqlLog.setEnd(LocalDateTimeUtil.formatMilliPlus8(end));
                    slowSqlLog.setUsed(used);
                    BaseLog<SlowSqlLog> baseLog = new BaseLog<>();
                    baseLog.setContext(slowSqlLog);
                    baseLog.setLevel(LevelEnum.WARNING.getLevel());
                    baseLog.setLevelName(LevelEnum.WARNING.getLevelName());
                    baseLog.setChannel(Channel.SYSTEM);
                    baseLog.setMessage("slowsql log");
                    baseLog.setDatetime(LocalDateTimeUtil.getMicroSecondFormattedNow());
                    String sqlLog=slowSqlLog.toString();
                    // todo 記錄日誌資訊
                } catch (Throwable ex) {
                    // ignore
                }
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

View Code

自定義插件說明:
  • 參考插件的使用說明文檔,指定@Intercepts,設置產生攔截的觸發條件。
  • 重新實現intercept方法,監聽invocation.proceed()方法執行前後的時間,記錄sql運行的相關日誌資訊。
  • 插件中獲取的原生sql的方法,在不同的驅動,資料庫版本下,方法不一樣,僅供參考。

配置插件

編寫完成自定義的插件資訊後,需將插件配置到MyBatis中,告訴它有這個插件,才可以運行插件功能。插件配置可通過xml設置,或者Java API配置,基於Java配置參考如下:

package com.trace.base.tool.mybatis.study;


import com.trace.base.tool.mybatis.monitor.MonitorSpringManagedTransactionFactory;
import com.trace.base.tool.mybatis.study.plugin.PagePlugin;
import com.trace.base.tool.mybatis.study.plugin.SlowSqlMonitorPlugin;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;


/**
 * 自定義的sqlSessionFactoryBean
 *
 * @author wl
 * @date 2021-3-9
 */
public class StudySqlSessionFactoryBean extends SqlSessionFactoryBean implements EnvironmentAware {
    private Interceptor[] plugins;
    public static Configuration CONFIGURATION;
    private boolean slowSqlEnabled = false;

    public StudySqlSessionFactoryBean() {
        this(null);
    }

    public StudySqlSessionFactoryBean(Configuration configuration) {
        super();
        if (configuration == null) {
            configuration = new Configuration();
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setAutoMappingBehavior(AutoMappingBehavior.FULL);
        }
        CONFIGURATION = configuration;
        setConfiguration(configuration);
    }

    @Override
    public void setPlugins(Interceptor[] plugins) {
        this.plugins = plugins;
    }

    /**
     * 真實執行設置插件,setPlugins只用於記錄客戶端自定義的plugin,便於後續拷貝
     */
    private void actualSetPlugins() {
        if (slowSqlEnabled) {
            // 使用自定義監控功能的事務管理器工廠類
            setTransactionFactory(new MonitorSpringManagedTransactionFactory());
            this.plugins = ArrayUtils.add(plugins == null ? new Interceptor[0] : plugins, new SlowSqlMonitorPlugin());
        }
        super.setPlugins(plugins);
    }

    @Override
    public void setEnvironment(Environment environment) {
        slowSqlEnabled = environment.getProperty(SlowSqlMonitorPlugin.SLOW_SQL_ENABLE, boolean.class, true);
        actualSetPlugins();
    }
}

View Code

配置插件關鍵點:
  • setConfiguration只是設置Mybatis的全局配置資訊,如設置統一的下劃線轉駝峰功能。
  • 重新實現setEnvironment方法,可以獲取配置資訊,用於環境變數標識,在指定的環境下,設置插件是否運行。
  • 重新實現setPlugins方法,添加自定義的插件。
再次啟動3.1中的測試用例,調試程式碼,進入插件運行函數,可查看到自定義的插件已經生效,效果如下:

3.3 Mybatis應用的一些思考

  1. 到底是xml模式設置sql好,還是Java API模式更好?
  2. 若基於Java API模式,MyBatis提供的動態sql工具類SQL()如何,有無局限性?
  3. 若擴展SQL()或者自實現,有哪些方向和注意點?
  4. 插件開發便於項目做監控管理,一般需要考慮哪些自定義的插件?
  5. 是否存在一些開源優秀的插件(自定義插件可以作為參考的方向)?
  6. Mapper配置一些關聯查詢如何處理,如一個Mapper查詢集成其他mapper中的查詢功能?
  7. 多個插件先後順序如何定義?

四、Mybatis擴展

Mybatis框架操作資料庫已經很優秀了,有沒有其他的ORM框架了,歡迎了解MybatisPlus,Spring Data Jpa 。優缺點項目開發者自己確定,也是一個不錯的方向(Mybatis Plus個人覺得裡面的部分成熟的插件可以參考源程式碼,引入到項目中)。參考文檔: