嘘~ 正在从服务器偷取页面 . . .

Mybtis(四)工作原理


Mybtis(四)工作原理

一、Mybatis涉及技术

mybatis运行主要两部分:

(1)读取配置文件换乘到Configuration对象,用以创建SqlSessionFactory

(2)SqlSession执行过程。

SqlSession执行过程涉及技术:

反射技术、动态代理技术。

什么是代理模式?

代理模式就是在原有的服务上多加一个占位,通过占位去控制服务的访问。

为什么要使用代理模式?

通过代理一方面可以控制如何访问真正的服务对象,提供额外服务。另一方面有机会通过重写一些类来满足特定的需要。

一般动态代理分两种:

(A)JDK反射机制提供的代理。必须提供接口。

(B)CGLIB代理。不需要提供接口。 CGlib底层是动态的在内存中生成了目标对象的子类的字节码,并生成相应的对象 。

1、反射简单的示例

package com.ssm.web.demo.test.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class SayService {

    public void sayHello(String someOne){
        System.out.println(" hello ," +someOne +" !");
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
             //通过反射创建SayService对象
            Object service = Class.forName(SayService.class.getName()).newInstance();
             //获取服务的方法
            Method method = service.getClass().getMethod("sayHello", String.class);
             //反射调用服务的方法    
            method.invoke(service, "zhangxiaocai");
    }
}

运行输出:

 hello ,zhangxiaocai !

反射的好出是配置性提高,springIOC容器也是使用反射机制实现。

2、JDK动态代理

支持包:java.lang.reflect.*

实现JDK动态代理步骤:

(1)编写服务类和接口,是真正的服务提供者,在JDK代理这接口是必须的。

(2)编写代理类,提供绑定和代理方法。

服务接口类:

package com.ssm.web.demo.test.jdktest;

public interface SayServiceI {

    public void sayHello(String someOne);
}

服务实现类:

package com.ssm.web.demo.test.jdktest;

public class SayServiceImpl implements SayServiceI {

    @Override
    public void sayHello(String someOne) {
        try {
            Thread.sleep(1000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(" hello ," + someOne + " !");
    }
}

服务代理类:

package com.ssm.web.demo.test.jdktest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class SayServiceProxy implements InvocationHandler {

    //需要持有真实的目标服务
    private Object target;

    public SayServiceProxy(){

    }
    //构造法绑定委托对象
    public SayServiceProxy(Object object){
        this.target = object;
    }

    //绑定委托对象并返回代理类
    public Object bind(Object object) {
        this.target = object;
        //取得代理对象
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        return  proxy;
    }


    /**
     * 通过代理对象调用方法首先会进入此方法
     * @param proxy -----代理对象
     * @param method ------调用方法
     * @param args ----方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        //System.out.println("----proxy = " + proxy.toString());
        //System.out.println("----proxy = " + proxy.hashCode());
        System.out.println("----proxy = " + proxy.getClass().getName());
        System.out.println("----method = " + method);
        System.out.println("---反射方法执行前调用----");

        result = method.invoke(this.target, args);

        System.out.println("---反射方法执行前调用----");
        return result;
    }
}

调用测试:

package com.ssm.web.demo.test.jdktest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class SayServiceTest {

    public static void main(String[] args) {
        SayServiceProxy proxyHandle = new SayServiceProxy();
        //调用bind绑定的方式
        SayServiceI service = (SayServiceI) proxyHandle.bind(new SayServiceImpl());
        service.sayHello("zhangxiaocai.cn");

        SayServiceI mysay = new SayServiceImpl();
        //调用构造方法
        InvocationHandler handler = new SayServiceProxy(mysay);
        SayServiceI proxyHello = (SayServiceI) Proxy.newProxyInstance(mysay.getClass().getClassLoader(), mysay.getClass().getInterfaces(), handler);
        proxyHello.sayHello("zhangxiaocai.cn");
    }
}

执行结果如下,两种写法的调用结果一致:

 ----proxy = com.sun.proxy.$Proxy0
----method = public abstract void com.ssm.web.demo.test.jdktest.SayServiceI.sayHello(java.lang.String)
---反射方法执行前调用----
 hello ,zhangxiaocai.cn !
---反射方法执行前调用----
----proxy = com.sun.proxy.$Proxy0
----method = public abstract void com.ssm.web.demo.test.jdktest.SayServiceI.sayHello(java.lang.String)
---反射方法执行前调用----
 hello ,zhangxiaocai.cn !
---反射方法执行前调用----

执行过程中如果出现死循环导致栈溢出(SOF),注意下列写法会导致死循环。

result = method.invoke(proxy, args);//第一个参数写出proxy
System.out.println("----proxy = " + proxy.toString());//调用了代理对象的方法
System.out.println("----proxy = " + proxy.hashcode());//调用了代理对象的方法

代理对象是没有自己的方法的,它的所有方法都是基于被代理对象,而调用代理对象方法的时候,都会经过拦截器方法。因此,如果在拦截器中再调用代理对象的方法(如toString,hashcode,equals等),就会再次进入拦截器,这样就形成了死循环。

还可以

在jvm启动时加上-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true参数,保存生成的动态代理类字节码$Proxy0.class

然后再利用反编译工具查看代理类$Proxy0.class源码

关于 newProxyInstancef方法:

Proxy.newProxyInstance(mysay.getClass().getClassLoader(), mysay.getClass().getInterfaces(), handler);

第一个参数mysay.getClass().getClassLoader(),是类加载器。

第二个参数mysay.getClass().getInterfaces(),是接口,代理对象挂在哪个接口下面。

第三个参数InvocationHandler实例,写在代理类中时直接使用this,表示当前代理类;写在非代理类中则是InvocationHandler子类指向InvocationHandler自己的引用。例子中使用了两种不同的写法。

3、CGLIB动态代理

因JDK动态代理必须提供接口才可以使用,为了解决这个难题,CGLIB出现了。

CGLIB是开源框架,也是比较流行的动态代理。

CGLIB的代理类需要实现org.springframework.cglib.proxy.MethodInterceptor接口。

上例子中的服务接口类和服务实现类不变,添加CGLIB代理类:

package com.ssm.web.demo.test.jdktest;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class SayServiceCglib implements MethodInterceptor {

    //需要持有真实的目标服务
    private Object target;

    // 创建代理对象
    public Object getInstance(Object object) {
        this.target = object;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        //设置回调方法
        enhancer.setCallback(this);
        //创建代理对象并返回
        return enhancer.create();
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        System.out.println("----proxy = " + proxy.getClass().getName());
        System.out.println("----method = " + method.toString());
        System.out.println("---反射方法执行前调用----");

        result = method.invoke(this.target, args);

        System.out.println("---反射方法执行前调用----");
        return result;
    }
}

测试运行类

package com.ssm.web.demo.test.jdktest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import org.springframework.cglib.proxy.MethodInterceptor;

public class SayServiceCglibTest {

    public static void main(String[] args) {

        SayServiceI mysay = new SayServiceImpl();
        MethodInterceptor handler = new SayServiceCglib();
        SayServiceI proxyHello =(SayServiceI) ((SayServiceCglib) handler).getInstance(mysay);
        proxyHello.sayHello("zhangxiaocai");
    }
}

执行结果

----proxy = com.ssm.web.demo.test.jdktest.SayServiceImpl$$EnhancerByCGLIB$$71af915d
----method = public void com.ssm.web.demo.test.jdktest.SayServiceImpl.sayHello(java.lang.String)
---反射方法执行前调用----
 hello ,zhangxiaocai.cn !
---反射方法执行前调用----

Mybatis中在延迟加载时会使用CGLIB动态代理。

二、构建SqlSessionFactory过程

SqlSessionFactory 是Mybatis的核心类之一,它最重要的功能就是提供Mybatis核心接口SqlSession。

1、构建步骤

Mybatis使用构造模式去创建SqlSessionFactory,大致过程分两步:

(1)通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置XML文件读出配置参数,并将读得的数据存入org.apache.ibatis.session.Configuration类中。(几乎所有配置都在这个Configuration类中)

(2)使用Configuration对象创建SqlSessionFactory 。SqlSessionFactory是接口不能直接使用,一般使用它默认的实现类org.apache.ibatis.session.defaults.DefaultSqlSessionFactory

sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这种创建方式就是一种Buidler模式。对于复杂的对象,直接使用构造方法构建比较困难的,这会导致大量的逻辑放在构造方法中,过程及其复杂。这种Builder模式值得我们学习。

构建SqlSessionFactory 过程中,Configuration最重要。

2、Configuration的主要作用:

(A)读入配置文件,包括基础配置XML和映射器XML文件。

(B)初始化基础配置,如别名、映射器、对象工厂、类型转换器等

(C)提供单例,为后续创建SessionFactory服务提供配置参数。

(D)执行重要的对象方法,初始化配置信息。

Configuration源码有八百多行,就不贴了。

具体相关配置可以参考《Mybatis配置大全》

3、映射器组成

映射器主要由三个部分组成:

(1)MappedStatement - 保存映射器的一个节点(select | inset | delete | update)。包括配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、languageDriver等重要配置内容。

(2)SqlSource - 提供BoundSql 对象的地方,是MappedStatement 的一个属性。本质上一个接口,主要作用是根据参数和其他规则组装SQL。

(3)BoundSql - 建立SQL和参数的地方。常用属性有3个:SQL、parameterObject、parameterMapping。

​ (A)parameterObject 表示参数本身。传递基础类型对象时,Mybatis会把参数变成基础类型对应的包装类型对传递,如传int类型,参数会变成Integer类型。

​ (B)如果传递的是POJO或Map,那么parameterObject 就传入的POJO或Map。

​ (C)传递多个参数时,不使用@Param注解,Mybatis会把parameterObject变成Map<String,Object>对象,Map里的键值关系按照参数顺序存放:

{"1":value1,"2",value2,"3":value3......,"param1":value1,"param2":value2,"param3":value3......}

可以使用时可以通过#{param1}#{1}取第一个参数。

举个例子如:

//假设传入id的值为 10010, name的值为 zhangxiaocai
List<T>  selectList(long id, String name);

则parameterObject变成Map<String,Object>对象后内容为:

{"1":10010,"2","zhangxiaocai","param1":10010,"param2":"zhangxiaocai"}

​ (D)传递多个参数时,使用@Param注解,Mybatis也会把parameterObject变成Map<String,Object>对象,Map里的键值关系按照参数顺序存放:

{"key1":value1,"key2",value2,"3":value3......,"param1":value1,"param2":value2,"param3":value3......}

举个例子:

//假设传入id的值为 10010, name的值为 zhangxiaocai
List<T>  selectList(@Param("id") long id, @Param("name")  String name);

那么对于的parameterObject变成的Map<String,Object>对象为:

{"id":10010,"name","zhangxiaocai","param1":10010,"param2":"zhangxiaocai"}

​ (E)parameterMappings是由元素为ParameterMapping对象组成的List集合。ParameterMapping对象描述的是参数,参数包括属性、名称、表达式、JavaType、jdbcType、typeHandler等信息。通过ParameterMapping对象实现参数和SQL的结合,方便PreparedStatement能通过ParameterMapping找到parameterObject对象的属性并设置参数。
​ (F)SQL就是映射器的一条SQL。

三、SqlSession运行过程

有了SqlSessionFactory就可以直接拿到SqlSession。

sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();

SqlSession可以进行查询、写入、更新、删除等方法,新版Mybatis使用Mapper映射器进行操作。

1、Mapper映射器动态代理

在Mybatis源码org.apache.ibatis.binding.MapperProxy<T>类中可以看出Mapper是通过动态代理来实现的:

package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

但是动态代理的特点是:
(1)实现InvocationHandler的接口,重写invoke方法。
(2)使用绑定或构造方法建立与被代理对象的练习。
(3)Proxy.newProxyInstance(…)取得代理对象。
按以上三点来看,代理类里没有看到Proxy.newProxyInstance的调用,因为Mybatis使用MapperProxyFactory来实现代理类管理的,在这里找到了Proxy.newProxyInstance的调用。

package org.apache.ibatis.binding;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.ibatis.session.SqlSession;

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

一旦Mapper接口调用SQL方法,那么就会运行到invoke方法中,invoke首先判断它是否是一个类,Mapper是接口不是类,所以会走到下面,生成MapperMethod对象,通过cachedMapperMethod方法对其进行初始化,然后执行execute方法,吧SQLSession和当前运行的参数传递进行。
MapperMethod的execute的方法,代码如下:

package org.apache.ibatis.binding;
//MapperMethod 类内容较多,只贴了execute的方法。
public class MapperMethod {

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.update(command.getName(), param));
            break;
          }
          case DELETE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.delete(command.getName(), param));
            break;
          }
          case SELECT:
            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 if (method.returnsCursor()) {
              result = executeForCursor(sqlSession, args);
            } else {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = sqlSession.selectOne(command.getName(), param);
            }
            break;
          case FLUSH:
            result = sqlSession.flushStatements();
            break;
          default:
            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;
      }
      //查询List的操作
      private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        Object param = method.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
          RowBounds rowBounds = method.extractRowBounds(args);
          result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
        } else {
          result = sqlSession.<E>selectList(command.getName(), param);
        }
        // issue #510 Collections & arrays support
        if (!method.getReturnType().isAssignableFrom(result.getClass())) {
          if (method.getReturnType().isArray()) {
            return convertToArray(result);
          } else {
            return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
          }
        }
        return result;
      }
}

MapperMethod是采用命令模式运行,根据上下文跳转的。
execute里都是常见的IUDS操作,根据不同的SQL类型进行处理。
比如SELECT里有个 result = executeForMany(sqlSession, args); 就是一个典型的查询List结果的调用,从源代码里可以看到最后都是通过调用sqlSession去执行的对象的SQL。

Mapper接口能够执行SQL,就是因为映射器的XML文件命名空间对应的是这个接口的全路径,根据全路径和方法进行绑定,通过动态代理技术让这个接口运行起来。然后采用命令模式,根据SQL类型跳转不同方法,但最终还是使用SqlSession接口的方法使用执行对应SQL返回结果。

2、 SqlSession的四大对象

Mapper执行的过程是通过ExcutorStatementHandlerParameterHandlerResultHandler来完成数据库操作和结果返回。
(1)Excutor 代表执行器,负责调度StatementHandlerParameterHandlerResultHandler等来执行对应SQL。
(2)StatementHandler作用是使用数据库的Statement(PreparedStatement)执行操作,是四大对象的核心,起到承上启下的作用。
(3)ParameterHandler用于SQL参数的处理。
(4)ResultHandler是进行最后数据集(ResultSet)的封装返回处理。

1、Excutor执行器

执行器Excutor是真正执行Java和数据交互的东西,主要有三种执行器。可以在配置setting元素的属性defaultExcutorType来进行配置。
(A)SIMPLE - 简单执行器,不配做就是默认执行器。
(B)REUSE - 是一种执行器重用预处理语句。
(C)BATCH - 执行器重用语句和批量更新,是针对批量专用的执行器。

三者都提供了查询和更新的方法,事务方法。

Configuration中可以看到创建执行器的过程:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }

    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

其中interceptorChain.pluginAll是Mybatis插件,用来构建动态代理对象,在调度真正的Executor方法执行执行配置插件代码可以修改。

如SimpleExecutor代码:

package org.apache.ibatis.executor;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.List;

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>queryCursor(stmt);
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    return Collections.emptyList();
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
}

从代码可以看到常见的doUpdate和doQuery操作。以doQuery为例,其过程大致如下:
根据Configuration来构建StatementHandler,然后使用prepareStatement方法对象SQL编译并对参数初始化,它的实现过程调用StatementHandler的prepare()进行预编译和基础设置,然后通过根据StatementHandler的parameterize()来设置参数并执行,ResultHandler再组装查询结果返回给调用者来完成查询。

2、数据库会话器

数据库会话器StatementHandler主要负责与数据库会话操作。
若要指定使用,可以在SQL上进行配置:

<!-- statementType (可选配置,默认配置为PREPARED)
    STATEMENT,PREPARED 或 CALLABLE 的一个。
    分别对应使用 Statement,PreparedStatement 或 CallableStatement -->
<insert id="save"  statementType="STATEMENT"> 
</insert>

Configuration中可以看到创建数据库会话器的过程:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

可以看到创建statementHandler的动作是由RoutingStatementHandler来完成,它实现了statementHandler接口,和Executor类似用代理对象进行封装。

RoutingStatementHandler的源代码中:

package org.apache.ibatis.executor.statement;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
    //some code ...
  }

可以看出RoutingStatementHandler也不是真正服务对象,它是通过适配模式找到对应的StatementHandler来执行。Mybatis中StatementHandler和Executor一样也有三种:
(A)SimpleStatementHandler - 对应JDBC中Statement接口,执行普通SQL
(B)PreparedStatementHandler - 对应JDBC中的PreparedStatement,预编译SQL的接口;
(c)CallableStatementHandler - 对应JDBC中CallableStatement,用于执行存储过程相关的接口;

RoutingStatementHandler中定义了一个delegate,它是一个StatementHandler接口对象,构造方法根据配置来适配对应的StatementHandler对象。

RoutingStatementHandler的作用是给实现类对象的使用提供一个统一简易的使用适配器,这是对象的适配模式,可以让我们使用现有的类和方法对外提供服务,也可以根据实际需求对外屏蔽方法,加入新方法。

如PreparedSatementHandler执行Mybatis执行查询,有三个主要的方法:
prepare、parameterize、query

如下是BaseStatementHandler的prepare方法:

package org.apache.ibatis.executor.statement;
public abstract class BaseStatementHandler implements StatementHandler {
      @Override
      public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
          statement = instantiateStatement(connection);
          setStatementTimeout(statement, transactionTimeout);
          setFetchSize(statement);
          return statement;
        } catch (SQLException e) {
          closeStatement(statement);
          throw e;
        } catch (Exception e) {
          closeStatement(statement);
          throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
        }
      }
}

instantiateStatement()方法是对SQL进行预编译。
下面是PreparedSatementHandler里的instantiateStatement()和parameterize()方法:

 @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }

    @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

    @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

prepare()方法首先进行一些基础配置,如超时、获取最大行数等设置。然后Executor会调用parameterize()方法设置参数。从代码可以看到设置参数是由ParameterHandler来完成的。

SatementHandler执行查询的方法里,由于执行前参数和SQL都被prepare()方法预编译,参数在parameterize()方法完成了设置,只要执行SQL返回结果即可。执行之后就是ResultHandler对结果的封装和返回。

SQL执行过程总结
Executor会先调用StatementHandler的prepare()方法预编译SQL语句,并设置基本运行参数。
然后使用parameterize()方法启用ParameterHandler设置成桉树,完成预编译,跟着就是执行SQL操作(query/update)
最后,如果有结果集就使用ResultHandler封装结果返回给调用者。

3、参数处理器

参数处理器ParameterHandler主要作用就是完成对预编译参数的设置。
ParameterHandler源码:

package org.apache.ibatis.executor.parameter;

import java.sql.PreparedStatement;
import java.sql.SQLException;

public interface ParameterHandler {
    //返回参数对象
    Object getParameterObject();
    //设置预编译SQL语句的参数
    void setParameters(PreparedStatement ps)throws SQLException;
}

ParameterHandler接口中,getParameterObject()方法的作用是返回参数对象,setParameters()方法的作用是设置预编译SQL语句的参数。
Mybatis为ParameterHandler提供了一个实现类DefaultParameterHandler,setParameters()方法源码:

package org.apache.ibatis.scripting.defaults;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

public class DefaultParameterHandler implements ParameterHandler {

  private final TypeHandlerRegistry typeHandlerRegistry;

  private final MappedStatement mappedStatement;
  private final Object parameterObject;
  private BoundSql boundSql;
  private Configuration configuration;

  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }

  @Override
  public Object getParameterObject() {
    return parameterObject;
  }

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
}

从代码中可以看到从parameterObject对象获取参数,然后使用类型转换器typeHandler进行参数处理。如果自定义了类型转换器,就会根据签名注册typeHandler对参数进行处理。类型转换器也是在Mybatis初始化的时候注册在Configuration里面。

4、结果处理器

ResultHandler是封装结果集的。
ResultHandler的源码:

package org.apache.ibatis.executor.resultset;

import org.apache.ibatis.cursor.Cursor;

import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

public interface ResultSetHandler {
    //包装结果集
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;

    <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
    //处理存储过程输出参数
    void handleOutputParameters(CallableStatement cs) throws SQLException;
}

其中handleOutputParameters()方法是处理存储过程输出参数。handleResultSets()方法是包装结果集的。
Mybatis提供了一个实现类DefaultResultSetHandler类,默认情况下都是通过这个类进行处理。它的实现涉及JavaSSIST或CGLIB作为延迟加载的,然后通过typeHandler和ObjectFacorty进行组装结果再返回的。

public class DefaultResultSetHandler implements ResultSetHandler { 
     @Override
      public List<Object> handleResultSets(Statement stmt) throws SQLException {
        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

        final List<Object> multipleResults = new ArrayList<Object>();

        int resultSetCount = 0;
        ResultSetWrapper rsw = getFirstResultSet(stmt);

        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        validateResultMapsCount(rsw, resultMapCount);
        while (rsw != null && resultMapCount > resultSetCount) {
          ResultMap resultMap = resultMaps.get(resultSetCount);
          handleResultSet(rsw, resultMap, multipleResults, null);
          rsw = getNextResultSet(stmt);
          cleanUpAfterHandlingResultSet();
          resultSetCount++;
        }

        String[] resultSets = mappedStatement.getResultSets();
        if (resultSets != null) {
          while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
              String nestedResultMapId = parentMapping.getNestedResultMapId();
              ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
              handleResultSet(rsw, resultMap, null, parentMapping);
            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
          }
        }

        return collapseSingleResultList(multipleResults);
      }
}

3、SqlSession运行总结:

SqlSession内部允许图

SqlSession是通过Executor调度StatementHandler来运行,StatementHandler要结果三步:
(A)prepared预编译SQL。
(B)parameterize设置参数
(C)query或update执行SQL
parameterize是调用ParameterHandler的方法去设置,参数是根据类型处理器typeHandler处理的。query或update方法通过ResultHandler进行处理结果的封装。update返回整数,query返回的结果集则是通过typeHandler处理结果类型,然后使用ObjectFactory提供的规则组装对象,返回给调用者。


相关文章:

文章名称
《Mybatis(一)主要组件》
《Mybatis(二)配置》
《Mybatis(三)动态SQL》
《Mybtis(四)工作原理》
《Mybtis(五)Mapper映射器》
《Mybtis(六)Mapper级联》


文章作者: Small-Rose /张小菜
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Small-Rose /张小菜 !
评论
  目录