美文网首页
MyBatis解析

MyBatis解析

作者: Y了个J | 来源:发表于2021-08-14 19:08 被阅读0次
SqlSessionFactoryBuilder

从命名上可以看出,这个是一个 Builder 模式的,用于创建 SqlSessionFactory 的类。SqlSessionFactoryBuilder 根据配置来构造 SqlSessionFactory。其中配置方式有两种:

  • 1.XML 文件方式,XML 文件方式是作为常用的一种方式:
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

mybatis-config.xml 就是我们的配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
        </environment>
    </environments>

    <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    </mappers>

    <settings>
    </settings>

    <typeAliases>
    </typeAliases>

    <typeHandlers>
    </typeHandlers>

    <plugins>
    </plugins>
</configuration>
  • 2.第二种配置方通过 Java 代码来配置:
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

Java Config 相比较 XML 文件的方式而言,会有一些限制。比如修改了配置文件需要重新编译,注解方式没有 XML 配置项多等。所以,业界大多数情况下是选择 XML 文件的方式。但到底选择哪种方式,这个要取决与自己团队的需要。比如,项目的 SQL 语句不复杂,也不需要一些高级的 SQL 特性,那么 Java Config 则会更加简洁一点;反之,则可以选择 XML 文件的方式。

SqlSessionFactoryBuilder#build

创建配置文件解析器XMLConfigBuilder

// 创建配置文件解析器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 调用 parser.parse() 方法解析配置文件,生成 Configuration 对象,Mybatis全局配置对象。
Configuration configuration = parser.parse();
XMLConfigBuilder

解析mybatis-config.xml里的配置为Configuration对象,Mybatis的全局配置对象。

XMLConfigBuilder#parseConfiguration解析mapper下的xml
XMLMapperBuilder#bindMapperForNamespace,根据xml里的 namespace 反射出 mapper接口 的 class,如果有mapper接口,则把该mapper接口的class添加到Configuration的mapperRegistry里。

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);//根据xml里的 namespace 反射出 mapper接口 的 class
      } catch (ClassNotFoundException e) {
        //如果没有这个类,则直接忽略,这是因为namespace属性值只需要唯一即可,并不一定对应一个XXXMapper接口
        //没有XXXMapper接口的时候,我们可以直接使用SqlSession来进行增删改查
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }
MapperRegistry#addMapper
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // 这主要是处理不使用xml映射文件的情况
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

如果该接口已经注册,则抛出已经绑定的异常。
为该接口注册MapperProxyFactory,但这里只是注册其创建MapperProxy的工厂,并不是创建MapperProxy。
如果Mapper对应的xml资源未加载,触发xml的绑定操作,将xml中的sql语句与Mapper建立关系。
addMapper方法,只是为**Mapper创建对应对应的MapperProxyFactory。

MapperRegistry#getMapper
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

根据Mapper接口与SqlSession创建MapperProxy对象。
根据接口类获取MapperProxyFactory。
调用MapperProxyFactory的newInstance创建MapperProxy对象。

SqlSessionFactory

SqlSessionFactory 顾名思义,是用于生产 SqlSession 的工厂。 通过如下的方式来获取 SqlSession 实例:

SqlSession session = sqlSessionFactory.openSession();
SqlSession

SqlSession 包含了执行 SQL 的所有的方法。以下是示例:

SqlSession session = sqlSessionFactory.openSession();
try {
  Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

当然,下面的方式可以做到类型安全:

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
} finally {
  session.close();
}
MapperProxy

MapperProxy是MapperProxyFactory使用SqlSession创建出来的。所以MapperProxy中包含SqlSession。

截屏2021-08-14 下午6.40.56.png

可以看到MapperProxy调用invoke方法,进而调用MapperMethod的execute(),这些MapperMethod就是和你要执行的命令相关,比如执行select语句,则会通过SqlSession的select()方法,最终调用到Executor的query方法。Executor会再协调另外三个核心组件。

  • MapperProxyFactory用来创建MapperProxy,这个factory其实主要就是完成了InvokeHandler的bindTarget的功能。而MapperProxy只需要完成invoke方法的功能。
  • MapperProxy包含SqlSession
  • SqlSesion包含四大组件Executor,StatementHandler,ParameterHandler,ResultHandler。还包含Configuration
  • Configuration可以创建四大组件,同时Configuration还包含InterceptorChain,通过调用interceptorChain的pluginAll()方法,完成针对四大组件的插件的动态代理链的创建。

MapperProxy:

  • 因为Mapper接口不能直接被实例化,Mybatis利用JDK动态代理,创建MapperProxy间接实例化Mapper对象。
  • MapperProxy还可以缓存MapperMethod对象

MapperMethod:

  • 负责解析Mapper接口的方法,并封装成MapperMethod对象
  • 将Sql命令的执行路由到恰当的SqlSesison方法上
public class MapperMethod {

  // 保存了Sql命令的类型和键id
  private final SqlCommand command;
  // 保存了Mapper接口方法的解析信息
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, method);
  }

  // 根据解析结果,路由到恰当的SqlSession方法上
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
  // ...

插件的构建:
谈原理首先要知道StatementHandler,ParameterHandler,Result Handler都是代理,他们是Configuration创建,在创建过程中会调用interceptorChain.pluginAll()方法,为四大组件组装插件(再底层是通过Plugin.wrap(target,XX, new Plugin( interceptor))来来创建的)。

插件链是何时构建的:
在执行SqlSession的query或者update方法时,SqlSession会通过Configuration创建Executor代理,在创建过程中就调用interceptor的pluginAll方法组装插件。然后executor在调用doQuery()方法的时候,也会调用Configuration的newStatementHandler方法创建StatemenHandler(和上面描述的一样,这个handler就是个代理,也是通过interceptorChain的pluginAll方法构建插件)

插件如何执行:
以statementhandler的prepare方法的插件为例,正如前面所说,statementhandler是一个proxy,执行他的prepare方法,将调用invokeHandler的invoke方法,而invokeHandler就是Plugin.wrap(target, xxx, new Plugin(interceptor))中的第三个参数,所以很自然invokeHanlder的invoke的方法最终就会调用interceptor对象的intercept方法。

Mybatis插件原理

Mybatis的插件配置在configuration内部,初始化时,会读取这些插件,保存于Configuration对象的InterceptorChain中。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
        <plugin interceptor="com.mybatis3.interceptor.MyBatisInterceptor">
            <property name="value" value="100" />
        </plugin>
    </plugins>
</configuration>
public class Configuration {
     protected final InterceptorChain interceptorChain = new InterceptorChain();
}

org.apache.ibatis.plugin.InterceptorChain.java源码。

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);
  }
}

上面的for循环代表了只要是插件,都会以责任链的方式逐一执行,所谓插件,其实就类似于拦截器。

插件的编写
插件必须实现org.apache.ibatis.plugin.Interceptor接口。

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);
}

-intercept()方法:执行拦截内容的地方,拦截目标对象的目标方法的执行
-plugin()方法:决定是否触发intercept()方法。 作用:包装目标对象,包装就是为目标对象创建一个代理对象
-setProperties()方法:给自定义的拦截器传递xml配置的属性参数。将插件注册时的property属性设置进来

下面自定义一个拦截器:

package com.atguigu.mybatis.dao;

import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

/*
 * 完成插件签名:
 *     告诉Mybatis当前插件用来拦截哪个对象的哪个方法
 * */
@Intercepts({
   @Signature(type=StatementHandler.class,method ="parameterize",args = java.sql.Statement.class )
})
public class MyFirstPlugin implements Interceptor
{
    /*
     * 拦截目标对象的目标方法的执行
     * */
    @Override
    public Object intercept(Invocation invocation) throws Throwable
    {
        System.out.println("MyFirstPlugin的intercept方法执行,拦截方法:"+invocation.getMethod());

        //动态改变一下sql的参数,查3号员工,这里偷梁换柱一下查询4号员工
        Object target = invocation.getTarget();
        System.out.println("当前拦截的对象:"+target);
        //拿到:StatementHandler==>ParameterHandler==>parameterObject
        //拿到target(拦截对象)的元数据
        MetaObject metaObject = SystemMetaObject.forObject(target);
        Object value = metaObject.getValue("parameterHandler.parameterObject");
        System.out.println("sql语句用的参数是:"+value);
        metaObject.setValue("parameterHandler.parameterObject", 4);

        //执行目标方法
        Object proceed = invocation.proceed();

        //返回执行后的返回值
        return proceed;
    }


    /*
     * 插件:
     * 作用:包装目标对象,包装就是为目标对象创建一个代理对象
     * */
    @Override
    public Object plugin(Object target)
    {
        System.out.println("MyFirstPlugin的plugin方法执行,包装的对象(目标对象):"+target);

        //我们可以借助Plugin的wrap方法来使用当前的拦截器包装我们的目标对象
        Object wrap = Plugin.wrap(target, this);

        //返回的就是为当前target创建的动态代理
        return wrap;
    }

    /*
     * setProperties:
     *     将插件注册时的property属性设置进来
     *
     * */
    @Override
    public void setProperties(Properties properties)
    {
        System.out.println("插件配置的信息:"+properties);
    }
}

为什么要写Annotation注解?注解都是什么含义?
Mybatis规定插件必须编写Annotation注解,是必须,而不是可选。@Intercepts注解:装载一个@Signature列表,一个@Signature其实就是一个需要拦截的方法封装。那么,一个拦截器要拦截多个方法,自然就是一个@Signature列表。

type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }
解释:要拦截Executor接口内的query()方法,参数类型为args列表。

Plugin.wrap(target, this)是干什么的?
使用JDK的动态代理,给target对象创建一个delegate代理对象,以此来实现方法拦截和增强功能,它会回调intercept()方法。

Mybatis可以拦截哪些接口对象?
Mybatis只能拦截ParameterHandler、ResultSetHandler、StatementHandler、Executor共4个接口对象内的方法。

重新审视interceptorChain.pluginAll()方法:该方法在创建上述4个接口对象时调用,其含义为给这些接口对象注册拦截器功能,注意是注册,而不是执行拦截。

拦截器执行时机:plugin()方法注册拦截器后,那么,在执行上述4个接口对象内的具体方法时,就会自动触发拦截器的执行,也就是插件的执行。

Invocation

public class Invocation {
   private Object target;
   private Method method;
   private Object[] args;
}

可以通过invocation来获取拦截的目标方法,以及执行目标方法。

分页插件原理
由于Mybatis采用的是逻辑分页,而非物理分页,那么,市场上就出现了可以实现物理分页的Mybatis的分页插件。 要实现物理分页,就需要对String sql进行拦截并增强,Mybatis通过BoundSql对象存储String sql,而BoundSql则由StatementHandler对象获取。

public interface StatementHandler {
     <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
 
     BoundSql getBoundSql();
}
 public class BoundSql {
    public String getSql() {
     return sql;
   }
}

因此,就需要编写一个针对StatementHandler的query方法拦截器,然后获取到sql,对sql进行重写增强。

相关文章

网友评论

      本文标题:MyBatis解析

      本文链接:https://www.haomeiwen.com/subject/clklbltx.html