MyBatis拦截器理解与初步使用

MyBatis拦截器理解与初步使用

文章目录

    • MyBatis拦截器理解与初步使用
      • Mybatis核心流程图
      • 插件基础(plugins)
      • 业务背景
      • 简单实现
      • 设计模式
        • 适配器模式
        • 代理模式
        • 工厂模式
        • 装饰器设计模式
        • 建造者设计模式
        • 策略模式
        • 模板模式
        • 单例模式
        • 组合模式
        • 思考

Mybatis核心流程图

MyBatis拦截器理解与初步使用_第1张图片

(1)读取MyBatis的配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。加载配置方式(后加载的配置会覆盖之前的配置)。

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

(2)加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

  • 注解形式的查询语句会动态生成。

(3)构造会话工厂。通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory(单例或静态单例)。

(4)创建会话对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法(工厂)。

(5)Executor执行器。MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护(动态代理)。

(6)MappedStatement对象。在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。

(7)输入参数映射。输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程。

(8)输出结果映射。输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程。

插件基础(plugins)

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

业务背景

在很多业务场景下我们需要对查询sql做一定的处理,但是又不能过度入侵原有业务。比如:分页操作,数据权限过滤操作,统一异常处理,SQL执行时间性能监控等等,这里我们就可以用到Mybatis的拦截器Interceptor。

简单实现

通过mybatis插件,捕获所有sql执行异常,统一做异常处理(连接数据库操作不在范围内,connect被框架实现了)。

封装的sdk地址。框架在怎么扩展都好说。

import com.sky.common.exception.domain.SqlExceptionLog;
import com.sky.common.exception.exception.UniteSqlException;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Value;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Mybatis拦截器. 拦截SQL执行时的异常,写入DB.
 */
@Slf4j
@Intercepts({
  		//插入和删除底层都是通过update实现的
        @Signature(type = Executor.class, method = "update", args = {
                MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {
                MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class})})
public class SqlExceptionInterceptor implements Interceptor {

    @Value("${dubbo.application.name}")
    private String application;

    /**
     * 线程池.
     */
    private static ExecutorService executor = Executors.newFixedThreadPool(5);

    /**
     * 拦截方法.
     */
    public Object intercept(Invocation invocation) throws Throwable {

        try {
            return invocation.proceed();
        } catch (Exception e) {
            // 日志对象
            final SqlExceptionLog record = new SqlExceptionLog();
            try {
                // 取得各种值
                MappedStatement statement = (MappedStatement) invocation
                        .getArgs()[0];
                Object parameter = invocation.getArgs()[1];
                BoundSql boundSql = statement.getBoundSql(parameter);

                // 防止死循环
                if (statement.getId().toUpperCase()
                        .contains("sqlExceptionLogMapper".toUpperCase())) {
                    throw e;
                }
                record.setExceptionMessage(e.getCause().toString());
                record.setExceptionStack(getExceptionStackTrace(e));
                record.setCreateTime(new Date());
                record.setSystemName(application);
                record.setSqlId(statement.getId());
                record.setSqlParameter(parameter.toString());
                record.setSqlSource(boundSql.getSql());
                record.setSqlType(statement.getSqlCommandType().toString());

                executor.execute(new Runnable() {
                    @Override
                    public void run() {

                    }
                });

                log.warn("SQL异常处理", application, "【异常信息:{},异常点:{},时间:{}】", record.getExceptionMessage(), record.getSqlId(), record.getCreateTime());
            } catch (Exception ex) {
                log.error("SQL异常处理", application, ex, "【拦截异常信息处理失败,失败原因:{}】", ex.getCause().toString());
                // ex.printStackTrace();
            }
            // 抛出异常
            throw new UniteSqlException(record.toString());
        }
    }

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

    /**
     * 配置文件读取属性.
     */
    public void setProperties(Properties properties) {
    }

    /**
     * 获取异常的堆栈信息.
     *
     * @param e
     * @return
     */
    private String getExceptionStackTrace(Exception e) {
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        e.printStackTrace(new java.io.PrintWriter(buf, true));
        String expMessage = buf.toString();
        try {
            buf.close();
        } catch (IOException ex) {
        }
        return expMessage;
    }
}

一个sdk封装并没有什么太大的价值。更多的是扩展思考吧。

设计模式

mybatis中用的设计模式,可能不全,毕竟看的还不是很通透,随时补充吧。

适配器模式

用途:Mybatis中日志模块。

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。当没有找到这些实现时,将会禁用日志功能。

你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。

<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  settings>
configuration>

代理模式

日志打印,sql執行。Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载(如果查询只有马上遍历集合,会马上触发加载)的效果。

工厂模式

mybatis中数据源,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory。

装饰器设计模式

mybatis中的缓存模块,例如Cache包中的cache.decorators子包中等各个装饰者的实现。

IO中输入流和输出流的设计就是完美的体现。

建造者设计模式

Mybatis的初始化,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder。

策略模式

创建SqlSession。

模板模式

Executor查询操作流程,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler。

单例模式

例如ErrorContext和LogFactory。加载配置文件SqlSessionFactory,基本是单例或者静态单例。

组合模式

例如SqlNode和各个子类ChooseSqlNode等。

思考

mybatis的源码很适合小白阅读,内容不是很复杂,而且用法很经典,包括一些处理逻辑,设计模式的运用。恰到好处。渐渐真香。慢慢学习。

你可能感兴趣的:(数据库)