MyBatis原理看这一篇就够了!

165 阅读10分钟

基本介绍

我们知道MybatisORM框架,即对象关系映射,其实本质是对JDBC的封装,我们首先看一下原生的JDBC操作数据库的代码示例:

public class Test {
    // 驱动器路径
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    //连接数据库地址
    private static final String URL = "jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false&characterEncoding=UTF8";
    //数据库用户名
    private static final String USER_NAME = "root";
    //数据库密码
    private static final String USER_PASSWORD = "root";
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        // 加载驱动
        Class.forName(DRIVER);
        // 建立和数据库的连接
        Connection conn = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
        // 创建SQL命令发送器
        Statement stmt = conn.createStatement();
        // 使用SQL命令发送器发送SQL命令并得到结果
        String sql = "insert into student values(1,'小黄',18,'男','海南省文章市')";
        int n = stmt.executeUpdate(sql);
        // 处理结果
        if (n > 0) {
            System.out.println("添加成功");
        } else {
            System.out.println("添加失败");
        }
        // 关闭数据库资源
        stmt.close();
        conn.close();
    }
}

总结上面的步骤,就简单的四步,如下:

  • 加载Driver驱动
  • 创建数据库连接Connection
  • 创建数据库操作对象Statement
  • 通过Statement发送SQL命令并得到结果

其实我们仔细一看就知道,上面四个步骤都是固定的,代码都是固定的,只有一个东西是随机变化的,就是SQL!!!

四大组件

Executor

每一个SqlSession对象都会被分配一个执行器对象。主要负责Connection对象的获取和Statement对象的管理方案。

Statement对象的管理方式如下:

  1. 简单方案:一个Statemnt接口对象只执行一次,执行完毕就会将Stament对象销毁,即SimpleExecutor简单执行器
  2. 可重用方案:使用一个Map集合,关键字就是一条Sql语句,对应的value就是Statemnt对象,即ReuseExecutor可重用执行器,等到SqlSession再次接收到相同的SQL命令时,就会从Map集合中找到对象的Statement进行使用。Map.put("select * from order",statement1)
  3. 批量处理管理方案:将多个Statement包含的SQL语句,交给一个Statement对象输送到数据库中,进行批处理操作,即BatchExecutor批处理执行器

我们来看一下Executor的继承结构

image.png

  • BaseExecutor是一个抽象类,它有三个子类,SimpleExecutor简单执行器(默认执行器)、ReuseExecutor可重用执行器和BatchExecutor批量处理执行器
  • CachingExecutor缓存执行器:当接收到查询任务时,该执行器先去缓存中查找是否有该铲鲟对象的结果,如果有就直接返回,否则就交给其他的执行器执行,即BaseExecutor的子类去执行。MyBatis默认情况下都会配置一个缓存执行器,来提高查询效率的,并且其中delegate变量存储另外一个执行器对象

Excecutor对象创建

执行器对象是由 Configuration对象负责创建的,Configuration 对象会根据得到 ExecutorType 创建对应的 Excecutor对象, 并把这个Excecutor对象传给 SqlSession 对象

image.png

image.png

image.png

image.png

StatementHandler

  • 负责创建Statement或者PreparedStatement或者CallableStatement对象,
  • 负责PreparedStatement或者CallableStatementSQL语句中占位符的赋值任务
  • 此外还负责数据库操作对象的行为,是执行executeUpdate更新还是执行executeQuery查询。

我们来看一下StatementHandler接口,如下:

public interface StatementHandler {

    Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;

    
    void parameterize(Statement statement) throws SQLException;

    
    void batch(Statement statement) throws SQLException;

   
    int update(Statement statement) throws SQLException;

    
    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
}

  • prepare方法:创建Statement对象或者PreparedStatement或者CallableStatement
  • parameterize方法:针对PreparedStatement或者CallableStatement关联的预编译SQL语句中占位符的赋值操作,该方法是通过ParameterHandler中的setParameter方法给参数赋值的。
  • update方法:主要负责数据库操作对象中的更新行为,即执行executeUpdate
  • query方法:主要负责数据库操作对象中的查询行为,即执行executeQuery

我们来看一下StatementHandler的继承结构

image.png

BaseStatementHandler一共有三个子类,即分别针对三个数据库操作对象

  • SimpleStatementHandler:创建Statement对象并初始化,进行简单的查询操作
  • PreparedStatementHandler:创建PreparedStatement对象并初始化,进行预编译SQL,更新数据库操作。
  • CallableStatementHandler:创建CallableStatement对象并初始化,处理存储过程。

RoutingStatementHandler:相当于SimpleStatementHandlerPreparedStatementHandler以及CallableStatementHandler的调度器。RoutingStatementHandler构造方法,将会根据Executor的类型决定创建SimpleStatementHandlerPreparedStatementHandler以及CallableStatementHandler

注意:StatementHandler对象是在SqlSession对象接收到命令时,由Configuration中的newStatementHandler方法创建的。

StatementHandler对象创建

StatementHandler对象是在SqlSession对象接收到操作命令时, 由ConfiguraionnewStatementHandler 方法负责调用的。

image.png

RoutingStatementHandler构造方法 , 将会根据Executor的类型决定创建SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler实例对象。

image.png

StatementHandler接口方法

prepare方法

image.png

prepare方法用于创建一个 (Statement or PreparedStatement or CallableStatement) 对象 , 并设置 Statement 对象的最大工作时间和一次性读取的最大数据量让后将生成的 Statement 对象返回

prepare 方法只在BaseStatementHandler被实现 在其三个子类中没有被重写 用于三个子类调用获得对应的 Statement接口对象 ,prepare 方法依靠instantiateStatement(connection)方法来返回具体Statement接口对象 ,这个方法是BaseStatementHandle中定义的抽象方法 , 由三个子类来具体实现

  • SimpleHandler中的instantiateStatement方法如下:

image.png

  • PreparedStatementHandler中的instantiateStatement方法如下:

image.png

  • CallableStatementHandler中的instantiateStatement方法如下:

image.png

parameterize方法

主要为PreparedStatementCallableStatement传参.因此只在PreparedStatementHandlerCallableStatementHandler中被重写。即设置SQL占位符的

PreparedStatementHandler中的parameterize

image.png

CallableStatementHandler中的parameterize

image.png

在这两个方法中,可以看到都是ParameterHandler对象进行参数赋值的。

query方法

输送查询查询语句,并将查询结果转换对应的实体类对象

SimpleStatementHandler 中的 query方法

image.png

PreparedStatementHandler中的query方法

image.png

CallableStatementHandler中的query方法

image.png

ParameterHandler

为了PreparedStatementCallableStatement的输送的SQL语句中占位符进行赋值操作。

image.png

  • getParameterObject方法:读取开发人员提供参数内容(占位符对应的参数值)

  • setParameters方法: 通过反射机制,负责将参数内容设置到数据库操作对象中输送的sql语句的占位符上

我们来看一下ParameterHandler的继承结构,它只有一个实现类DefaultParameterHandler

image.png

ParameterHandler对象创建

参数处理器对象是在创建StatementHandler对象同时被创建的.由Configuration对象负责创建.

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 获得 Configuration 对象
    this.configuration = mappedStatement.getConfiguration();

    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    // 获得 TypeHandlerRegistry 和 ObjectFactory 对象
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    // 如果 boundSql 非空,一般是写类操作,例如:insert、update、delete ,则先获得自增主键,然后再创建 BoundSql 对象
    if (boundSql == null) { // issue #435, get the key before calculating the statement
        // 获得自增主键
        generateKeys(parameterObject);
        // 创建 BoundSql 对象
        boundSql = mappedStatement.getBoundSql(parameterObject);
    }
    this.boundSql = boundSql;

    // 创建 ParameterHandler 对象
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    // 创建 ResultSetHandler 对象
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

ResultSetHandler

将对数据库操作的结果映射为一个Java对象

image.png

我们来看一下ResultSetHandler的继承结构,它只有一个实现类 DefaultResultSetHandler

Mybatis源码剖析

解析配置文件

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

创建SqlSession对象

image.png

image.png

image.png

image.png

image.png

image.png

image.png

MapperProxy创建dao接口代理对象

image.png

image.png

image.png

image.png

image.png

protected T newInstance(MapperProxy<T> mapperProxy) {
    //第一个参数为类加载器,第二个参数为被代理类的接口数据,第三个参数为InvocationHandler的实现类
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

image.png

MapperProxy中的Invoke方法

image.png

image.png

public Object execute(SqlSession sqlSession, Object[] args) {
    Object param;
    Object result;
    //根据dao接口方法类型执行不同的逻辑,比如查询就是SELECT
    switch(this.command.getType()) {
    case INSERT:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case UPDATE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case DELETE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case SELECT:
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        //由于findAll方法的返回值是List,所以会执行下面的方法
        } else if (this.method.returnsMany()) {
            //查看该方法
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {
            result = this.executeForCursor(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
        }
        break;
    case FLUSH:
        result = sqlSession.flushStatements();
        break;
    default:
        throw new BindingException("Unknown execution method for: " + this.command.getName());
    }

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

image.png

image.png

image.png

image.png

image.png

image.png

创建StatementHandler对象

image.png

image.png

image.png

image.png

创建ParameterHandler对象和ResultSetHandler对象

image.png

image.png

image.png

创建数据库操作对象

image.png

image.png

image.png

image.png

protected Statement instantiateStatement(Connection connection) throws SQLException {
    //创建数据库操作对象Statement对象
    return this.mappedStatement.getResultSetType() != null ? connection.createStatement(this.mappedStatement.getResultSetType().getValue(), 1007) : connection.createStatement();
}

执行数据库操作

image.png

image.png

image.png

插件

原理

Mybatis作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(ExecutorStatementHandlerParameterHandlerResultSetHandler)处提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象。

MyBatis所允许拦截的方法如下

  • 执行器Executor (update、query、commit、rollback等方法);

  • tatementHandler (prepare、parameterize、batch、updates query等方 法);

  • 参数处理器ParameterHandler (getParameterObject、setParameters方法);

  • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);

在四大组件对象创建的时候,每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler); ,获取到所有的Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返回 target 包装后的对象

插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说

public ParameterHandler newParameterHandler(MappedStatement mappedStatement,Object object, BoundSql sql, InterceptorChain interceptorChain){

    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);

    parameterHandler = (ParameterHandler)interceptorChain.pluginAll(parameterHandler);

    return parameterHandler;

}


/**
 * @author Clinton Begin
 */
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}
  • interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调用拦截器链中的拦截器依次的对目标进行拦截或增强。

  • interceptor.plugin(target)中的target就可以理解为mybatis中的四大组件对象。返回的target是被重重代理后的对象。

自定义插件

数据权限插件例子如下:

/**
 * @Auther: huangshuai
 * @Date: 2024/2/24 23:29
 * @Description:给sql后面添加where查询条件
 * @Version:
 */

/**
 * 开发自己的插件
 * OneInterceptor相当于代理实现类
 * typ表示对哪个神器进行拦截
 * method:神器的具体方法
 * args:方法的参数类型
 */
@Intercepts(value = @Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
))
public class OneInterceptor implements Interceptor {


    private final String key = "factory_code";
    private final List<String> value = Arrays.asList("111", "222");

    /**
     * 该方法的作用:【任务】为当前目标对象生成代理对象
     * <p>
     * 监听对象(代理对象)=Proxy.newProxyInstance(类加载器,interfaces,代理实现里)
     * 上面的代码可以用Plugin工具类来完成,因为该工具类中的wrap方法封装了上一行代码
     * <p>
     * target就是四大神器的实例对象
     * 返回值就是四大神器的监听对象即代理对象
     */
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 相当于InvocationHandler中的Invoke方法
     * invoke(Object proxy,Method method,Object[] args);
     * <p>
     * 该方法中的参数Invocation invocation,封装的是被拦截的神器对象,还封装了被拦截神器的行为,即神器的方法
     * 例如封装了StatementHandler对象,封装了该对象中的prepare方法
     * <p>
     * 该方法的返回值就是神器的被拦截的方法的返回值
     */
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            metaObject.setValue("delegate.boundSql.sql", rewriteSql(metaObject));
        }
        return invocation.proceed();

    }

    private String rewriteSql(MetaObject metaObject) throws Exception {
        String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
        CCJSqlParserManager sqlParserManager = new CCJSqlParserManager();
        Select select = (Select) sqlParserManager.parse(new StringReader(originalSql));
        SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect) {
            this.setWhere((PlainSelect) selectBody, key, value);
        } else {
            SetOperationList operationList = (SetOperationList) selectBody;
            List<SelectBody> selectBodyList = operationList.getSelects();
            selectBodyList.forEach(s -> {
                try {
                    this.setWhere((PlainSelect) s, key, value);
                } catch (Exception e) {

                }

            });
        }
        return select.toString();
    }

    private void setWhere(PlainSelect plainSelect, String key, List<String> value) throws Exception {
        Table fromItem = (Table) plainSelect.getFromItem();
        Alias alias = fromItem.getAlias();
        String mainTableName = alias == null ? fromItem.getName() : alias.getName();
        ExpressionList expressionList = new ExpressionList(value.stream().map(StringValue::new).collect(Collectors.toList()));
        InExpression inExpression = new InExpression(new Column(mainTableName + "." + key), expressionList);
        if (plainSelect.getWhere() == null) {
            plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression(inExpression.toString()));
        } else {
            plainSelect.setWhere(new AndExpression(plainSelect.getWhere(), CCJSqlParserUtil.parseExpression(inExpression.toString())));
        }


    }

    /**
     * 该方法的任务:为拦截器对象的属性进行赋值的
     *
     * <plugins>
     * <plugin interceptor="com.kaikeba.interceptor.OneInterceptor">
     * <!-- 该属性的值,可以在拦截器中的setProperties方法中进行读取 -->
     * <property name="driver" value="com.mysql.jdbc.Driver"/>
     * </plugin>
     * </plugins>
     */
    public void setProperties(Properties properties) {
        String property = properties.getProperty("driver");
        System.out.println("获得属性值是:" + property);
    }

}