前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis核心流程源码分析(下)​

MyBatis核心流程源码分析(下)​

原创
作者头像
全干程序员demo
发布2024-01-19 14:52:31
1300
发布2024-01-19 14:52:31
举报
文章被收录于专栏:mybatis源码分析mybatis源码分析

MyBatis核心流程源码分析(下)

Mybatis核心对象如何与SqlSession建立联系

上面说了mybatis的核心对象,我们使用mybatis操作数据库使用到的仅仅只是SqlSession对象,下面我们看看SqlSession如何使用核心对象帮助我们完成这一系列操作.

我们重新回到mybatis核心代码部分

代码语言:java
复制
    /**
     * mybatis基础开发流程
     * @throws IOException
     */
    @Test
    public void test1() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
        List<User> users = userDAO.queryAllUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }

    /**
     * sqlsession的第二种用法
     * @throws IOException
     */
    @Test
    public void test2() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> users = sqlSession.selectList("com.example.dao.UserDAO.queryAllUsers");
        for (User user : users) {
            System.out.println(user);
        }
    }

在这里我们说过第一种方法是第二种方法的封装,那么我们先从第二种方法入手,看看SqlSession如何执行的增删改查方法.由于以下操作会追朔源码所以我先进行文字描述然后会贴上相关源码方便理解.

重新复制一个sqlsession的第二中用法

代码语言:java
复制
   @Test
    public void test3() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("");
    }

我们通过进入sqlSession.insert()源码进行一步步分析,sqlsession是如何调用我们的mybatis核心对象的.

首先看到SqlSession的insert()方法,由于SqlSession是一个接口所以我们需要继续向下查看它的实现

代码语言:java
复制
  /**
   * Execute an insert statement.
   * @param statement Unique identifier matching the statement to execute.
   * @return int The number of rows affected by the insert.
   */
  int insert(String statement);

这里我们直接点击SqlSession默认的实现类DefaultSqlSession看到对应的insert()方法实现,会发现insert()方法是一个重载方法,而下面的重载方法是调用的是update()方法

代码语言:java
复制
  @Override
  public int insert(String statement) {
    return insert(statement, null);
  }

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

然后我们进入update()方法可以看到在SqlSession中关于insert()方法的实现(下面注释已经写的很清楚,我们也可以明白SqlSession如何与executor建立联系进行调用)

代码语言:java
复制
  //这里的statement参数之前有说过是namespace.id
  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      //这里是通过namespace.id获取到mapper文件中那条sql语句的标签例如<insert>...</insert>
      MappedStatement ms = configuration.getMappedStatement(statement);
      //通过executor成员变量调用executor.update()方法来进行实现
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

下面我们点击executor.update()方法来查看它的具体实现,因为executor是一个接口,所以我们来看看他的实现,之前说过executor有三个实现类都继承了BaseExcutor,这里我们直接选择BaseExcutor查看update()方法的实现.

代码语言:java
复制
  int update(MappedStatement ms, Object parameter) throws SQLException;
代码语言:java
复制
  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    //具体执行的是这个方法
    return doUpdate(ms, parameter);
  }

可以发现在BaseExcutor中具体执行的方法是BaseExcutor.doUpdate(),进入doUpdate()方法可以发现doUpdate是一个抽象方法会由子类进行实现那么就还是回到了我们之前说的3个常用的Executor实现类分别为

我们选择默认的SimpleExecutor中查看doUpdate()方法的具体实现,可以发现这里使用了StatementHandler执行update().这里体现了我们之前说过的StatementHandler帮助Executor去对数据库进行具体操作.

代码语言:java
复制
  @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);
    }
  }

接下来我们进入handler.update()方法看看具体实现,由于StatementHandler是一个接口我们还是需要去进入底层的实现类

代码语言:java
复制
  int update(Statement statement)
      throws SQLException;

这里还是选择SimpleStatementHandler来查看update()方法的具体实现,可以发现这里SimpleStatementHandler里面对update进行我们最传统JDBC操作也体现出StatementHandler是JDBC的封装,至此我们已SimpleStatementHandler经分析完SqlSession的第二种用法是如何与我们myabtis核心对象进行调用的也就是建立联系.

代码语言:java
复制
  @Override
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }

之前说过SqlSession的用法第一种是第二种的封装,我们现在已经知道第二种用法的具体执行流程,那么如何知道第一种的呢?

代码语言:java
复制
  UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

如果看过mybatis相关面试题那么就知道myabtis这一步操作是使用了动态代理,因为UserDAO是一个接口,我们无法调用接口里面的方法.所以需要动态代理一个实现类来替我们实现UserDAO中的方法.

通过在IDEA中DeBug可以发现这里是使用了代理对象进行相关调用的.

mybatis代理核心大致流程

动态代理是一个动态字节码技术,在JVM运行过程中创建class,在jvm运行结束消失.

代码语言:txt
复制
动态代理
	a.为原始对象(目标对象) 增加额外功能
	b.远程代理 1.网络代理  2.输出传输|(RPC)Dubbo
	c.接口实现类 我们看不到实实在在的.class文件,但是运行时却能体现出来效果(无中生有)
	
	核心代码
	Proxy.newProxyIntance(ClassLoader,Interface,InvocationHandler)

为了下面方便测试,我们先将UserDAO中的save()方法注释(对应的我们mapper文件中的save也注释掉),防止我们测试动态代理时还需要做类型的判断因为我们知道动态代理类执行的是SqlSession.insert(),这里我们就只测试SqlSession.select().

代码语言:java
复制
package com.example.dao;

import com.example.entity.User;

import java.util.List;


public interface UserDAO {

//先注释掉save方法
//    void save(User user);

    List<User> queryAllUsers();
}
代码语言:xml
复制
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.UserDAO">

<!--    <insert id="save" parameterType="User">-->
<!--        insert into t_user (name) values (#{name})-->
<!--    </insert>-->

    <select id="queryAllUsers" resultType="User" useCache="true" >
        select id,name from t_user
    </select>
</mapper>

开始编写代理测试类

代码语言:java
复制
  /**
     * 用于测试代理
     * @throws IOException
     */
    @Test
    public void test4() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
 		Class[] classes = {UserDAO.class};
        //这一块的Proxy.newProxyInstance(),第一个参数是类加载器,第二是目标代理类数组,第三个InvocationHandler是一个接口我们去创建一个它的实现类来实现相关方法
        UserDAO userDAO =  Proxy.newProxyInstance(MybatisTest.class.getClassLoader(),classes, InvocationHandler);
        List<User> users = userDAO.queryAllUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }

创建MyMapperProxy实现InvocationHandler,然后分析我们该如何实现invoke()方法

代码语言:java
复制
package com.example.proxy;

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

public class MyMapperProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}

代理类最终执行的方法是sqlSession.selectList()方法,我们需要将SqlSession作为成员变量在创建代理对象时作为参数传入.而sqlSession.selectList()方法的参数需要一个namespace.id(例如:com.example.dao.UserDAO.queryAllUsers),我们可以通过method.getName()来获取id(也就是最终执行的方法名),通过获取目标对象的全限定类名来获取前面部分.所以我们将目标类也作为参数传入构造方法中通过daoClass.getName()通过字符串拼接获得最终的namespace.id(也就是最终的方法)

代码语言:java
复制
package com.example.proxy;

import org.apache.ibatis.session.SqlSession;

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

public class MyMapperProxy implements InvocationHandler {

    private SqlSession sqlSession;

    private Class daoClass;

    //将需要的sqlSession和daoClass作为成员变量,后面方便去进行操作和获取内容
    public MyMapperProxy(SqlSession sqlSession, Class daoClass) {
        this.sqlSession = sqlSession;
        this.daoClass = daoClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return sqlSession.selectList(daoClass.getName()+"."+method.getName());
    }
}

这样我们的代理类就已经完成了,回到测试类将之前的Proxy.newProxyInstance()的第三个参数替换为我们的代理类对象,就可以看到打印结果和之前一样,也打印出了具体执行的查询方法.

代码语言:java
复制
  /**
     * 用于测试代理
     * @throws IOException
     */
    @Test
    public void test4() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Class[] classes = {UserDAO.class};
        UserDAO userDAO = (UserDAO) Proxy.newProxyInstance(MybatisTest.class.getClassLoader(),classes,new MyMapperProxy(sqlSession,UserDAO.class));
        List<User> users = userDAO.queryAllUsers();
        for (User user : users) {
            System.out.println(user);
        }
    }

但是myabtis对于invoke()方法的实现远比我们设计的复杂多的多,因为我们这个实现类中只有一个selectList(),而没有涉及到其他类型的查询,但是大致流程跟以上是一样的.

mybatis代理源码分析

代码语言:txt
复制
俩个核心对象帮我们去创建了DAO接口的实现类
MapperProxyFactory对应的就是我们 Proxy.newProxyInstance();

MapperProxy 对应invoke()方法去具体执行我们的增删改查操作

下面我们去进入MapperProxyFactory源码中分析

代码语言:java
复制
/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
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;

/**
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {

  //目标类接口.class
  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);
  }
  //这里通过创建invoke()方法的实现也就是MapperProxy,通过newInstance()的重载将mapperProxy传入上面的重载方法作为参数来进行代理,而mapperProxy的创建也跟我们一样需要SqlSession来进行增删改查操作,需要mapperInterface来过去接口方法的namespace.id.
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
```txt
代码语言:txt
复制
	可以发现这块跟我们设计的代理流程基本上是一致的,因为我们这里肯定是考虑用更简单的流程能够清晰的看出这块的代理自然不会去设计复杂的流程,下面再看看MapperProxy中源码和我们设计的又有什么区别
代码语言:java
复制
/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

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

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  //可以发现MapperProxy跟我们设计的MyMapperProxy基本一直,实现了InvocationHandler,提供了SqlSession,Class成员变量,然后实现了invoke()方法
  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 {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, 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;
  }

  @UsesJava7
  private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
      throws Throwable {
    final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
        .getDeclaredConstructor(Class.class, int.class);
    if (!constructor.isAccessible()) {
      constructor.setAccessible(true);
    }
    final Class<?> declaringClass = method.getDeclaringClass();
    return constructor
        .newInstance(declaringClass,
            MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
        .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
  }

  /**
   * Backport of java.lang.reflect.Method#isDefault()
   */
  private boolean isDefaultMethod(Method method) {
    return (method.getModifiers()
        & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
        && method.getDeclaringClass().isInterface();
  }
}

这块我们主要来分析下源码中的invoke()方法

然后我们看下这一步final MapperMethod mapperMethod = cachedMapperMethod(method);具体是在做什么操作,直接进入cachedMapperMethod()方法的源码来查看下

代码语言:java
复制
  private MapperMethod cachedMapperMethod(Method method) {
    //从methodCache中获取是否存在这个方法,存在直接返回mapperMethod,不存在则需要创建一个MapperMethod,然后put进methodCache
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

我们直接进入MapperMethod()的这个构造方法来进行分析

代码语言:java
复制
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  //可以发现参数中有Configuration,也就是说这里可以获取到所有mybatis相关配置包括myabtis的statement对象,然后可以发现这里有俩个成员变量一个是SqlCommand,一个是MethodSignature,为什么需要将method去包装成MapperMethod呢
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
    ...
}

我们看看为什么需要上面这俩个对象,new SqlCommand(config, mapperInterface, method)中包装了myabtis的所有配置,DAO接口和DAO接口的方法.下面我们进入SqlCommand的源码中看看SqlCommand具体是做什么的

代码语言:java
复制
  public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      //可以发现这一块if的代码是为了安全起见进行的判断,因为我们一般使用myabtis不可能会出现MappedStatement为空的情况,所以SqlCommand的核心代码肯定是在else里面
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        //这里获取了MappedStatement的id也就是我们的namespace.id,然后获取了sql的类型(update,insert,select,delete),这里猜测是因为最终是执行sqlsession中的select(),insert()...方法才进行的判断
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

下面看看MethodSignature对象是具体在做什么,通过上面的成员变量可以发现这个类是处理和判断返回值和参数的,返回值可以发现主要是通过我们之前说的核心对象Configuration中获取mapper文件中的resultTpye等标签来进行判断,我们主要看看ParamNameResolver是如何进行参数处理的.

代码语言:java
复制
  public static class MethodSignature {

    
    private final boolean returnsMany;
    //返回值类型是否是Map
    private final boolean returnsMap;
    //是否没有返回值
    private final boolean returnsVoid;
    private final boolean returnsCursor;
    //返回值类型
    private final Class<?> returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    //分页参数
    private final Integer rowBoundsIndex;
    //参数
    private final ParamNameResolver paramNameResolver;

    public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      this.returnsMap = this.mapKey != null;
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

}

ParamNameResolver

代码语言:java
复制
  public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      //可以发现这一块是通过@Param注解来获取到mybatis中执行的方法参数
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

最后我们回到下面这行代码,可以发现上面这部是在对mapper方法中的sql类型,返回值类型以及参数进行操作,然后就可以放心的使用sqlSession进行数据库操作了.我们之前因为方便直接使用了selssion.selectList()进行的操作(只有这一种),而源码中使用了mapperMethod.execute()方法,我们来看看mybatis源码中具体是如何进行的操作

代码语言:java
复制
   final MapperMethod mapperMethod = cachedMapperMethod(method);
   return mapperMethod.execute(sqlSession, args);
代码语言:java
复制
  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:
        //通过之前MethodSignature的处理可以进行判断如果方法没有返回值result=null,如果返回值多个调用executeForMany(),为map调用executeForMap,一个调用sqlSession.selectOne
        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;
  }
		

至此,上方红框的代码我们就基本分析完毕了,也就是如下图我们总结出来的执行流程.

4.mybatis读取配置文件(Configuration的封装)

上面我们以及总结了关于myabtsi中关于sql语句的执行,下面我们来看看下面代码在mybatis中是如何去进行操作的.

首先第一步是通过IO流的方式去读取我们的配置文件,这个应该是我们很熟悉的了.然后我们之前说过Configuration对象是对应了mybatis-config.xml中的标签属性,那么具体是如何将myabtis-config.xml封装成Configuration对象的.关于java中XML的解析方式mybatis这里采用的是XPath(因为这种方式比较简单),下图也大概介绍了读取xml文件的一个大致流程.

我们通过上图得知最终xml文件中的标签属性最终会读取到一个叫XNode的对象中,我们直接进入XNode的源码来分析一下.

代码语言:java
复制
public class XNode {

  private final Node node;
  //这里是标签名称如setting
  private final String name;
  //这里是标签内容
  private final String body;
  //这里是标签的属性
  private final Properties attributes;
  private final Properties variables;
  private final XPathParser xpathParser;

  public XNode(XPathParser xpathParser, Node node, Properties variables) {
    this.xpathParser = xpathParser;
    this.node = node;
    this.name = node.getNodeName();
    this.variables = variables;
    this.attributes = parseAttributes(node);
    this.body = parseBody(node);
  }

  public XNode newXNode(Node node) {
    return new XNode(xpathParser, node, variables);
  }

  public XNode getParent() {
    Node parent = node.getParentNode();
    if (parent == null || !(parent instanceof Element)) {
      return null;
    } else {
      return new XNode(xpathParser, parent, variables);
    }
  }

通过源码可以得知XPathParser.evalNode()返回的一个List对象可以获取一个标签内所有的标签内容,进而可以达到封装成java对象(mybatis中封装为Configuration类).我们只需要将输入流作为参数传入XPathParser()的构造方法中就可以获取到封装好的java对象.那么这一步操作很明显是在build()方法中去执行的,如下代码

代码语言:java
复制
 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
 //这里将inputStream传入build()方法中,所以推测build()方法中执行了将xml配置文件封装为Configuration对象
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

现在目的很明确,我们直接进入build()源码中进行分析

代码语言:java
复制
  public SqlSessionFactory build(InputStream inputStream) {
    //进入的是这个build()方法,然后对应到下面的重载方法
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //这个XMLConfigBuilder就是封装的我们刚刚说的XPathParser对象,最终也是执行的parser.parse()方法
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

这里我还是用源码看看XMLConfigBuilder是不是封装了XPathParser

代码语言:java
复制
public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  //这里可以很明确看到封装为成员变量的XPathParser
  private final XPathParser parser;
  private String environment;
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

  public XMLConfigBuilder(Reader reader) {
    this(reader, null, null);
  }

这样我们就直接进入之前说的parser.parse()方法中看看是不是读取了标签的所有内容.

代码语言:java
复制
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //parser.evalNode("/configuration")可以明确看到这里已经读取到了configuration标签下的所有XNode节点,因为这个方法返回的是一个List<XNode>类型,那么我们可以猜测parseConfiguration()方法应该是在将节点的内容封装成一个Configuration对象,因为这个parse()方法的返回值是一个configuration对象.
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

我们继续看看parseConfiguration是不是解析了configuration标签下的所有内容,对应mybatis-config.xml的配置都被解析出来了.

代码语言:java
复制
 
//可以发现mybatis-config.xml中所有的标签都在这个方法中进行了解析
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //这里读取的是mappers标签下的mapper标签内容
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

我们进入mapperElement()这个方法看看mapper.xml文件中的标签是否封装成了之前说的mappedStatement对象

代码语言:java
复制
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          //这里获取了mapper标签的resource属性,可以获取到mapper.xml文件的路径
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            //然后通过IO流去读取这个mapper.xml文件,还是通过XMLMapperBuilder去口模型解析
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //可以发现这里跟解析mybatis-config.xml文件的方式是一样的,我们继续进入mapperParser.parse()方法
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

看看parse()方法中的操作是不是跟我们读取mybatis-config.xml文件方式差不多

代码语言:java
复制
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //可以发现这里读取了mapper标签下的所有内容,这个configurationElement()方法应该也是读取mapper标签里面的内容
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

进入configurationElement()方法

代码语言:java
复制
 private void configurationElement(XNode context) {
    try {
       //解析mapper.xml中mapper标签下的所有内容
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      //这个buildStatementFromContext可能是进行MappedStatement封装的方法我们继续进入
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

进入buildStatementFromContext()方法

代码语言:java
复制
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      //继续进入这个重载的方法
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }
代码语言:java
复制
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        //推测这个statementParser.parseStatementNode()方法就是封装MappedStatement对象的方法继续进入
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

进入statementParser.parseStatementNode()方法,可以发现这个方法将mapper标签中所有标签的内容全部取了出来作为builderAssistant.addMappedStatement()方法的参数

代码语言:java
复制
 public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
	//将上述获取的mapper标签中所有的标签内容作为参数,去添加一个MappedStatement.
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

进入这个builderAssistant.addMappedStatement()方法看看

代码语言:java
复制
 public MappedStatement addMappedStatement(
      String id,
      SqlSource sqlSource,
      StatementType statementType,
      SqlCommandType sqlCommandType,
      Integer fetchSize,
      Integer timeout,
      String parameterMap,
      Class<?> parameterType,
      String resultMap,
      Class<?> resultType,
      ResultSetType resultSetType,
      boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//这里使用了建造者设计模式,创建一个statementBuilder
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
        .resource(resource)
        .fetchSize(fetchSize)
        .timeout(timeout)
        .statementType(statementType)
        .keyGenerator(keyGenerator)
        .keyProperty(keyProperty)
        .keyColumn(keyColumn)
        .databaseId(databaseId)
        .lang(lang)
        .resultOrdered(resultOrdered)
        .resultSets(resultSets)
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    //调用build()方法创建了MappedStatement对象并且最后将MappedStatement对象存入configuration中
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

5.构建SqlSessionFactory

代码语言:txt
复制
	至此,configuration对象已经被封装完毕,我们继续回到最开始的地方.MappedStatement通过建造者模式build()方法创建,而我们之前说到,configuration对象的封装也是在new SqlSessionFactoryBuilder().build(inputStream)中进行的,那么它一定也进行SqlSessionFactory的构建,我们重新进入build()方法看看.
代码语言:java
复制
 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
代码语言:java
复制
//中间的重载方法我这里就直接跳过了,我们直接看这个具体执行的方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //可以很明确看到其实最终返回的是这个build()方法而parser.parse()是解析配置文件后封装的Configuration对象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

我们直接进入build()看看是否为我们创建了SqlSessionFactory对象

代码语言:java
复制
 public SqlSessionFactory build(Configuration config) {
    //最终返回了DefaultSqlSessionFactory实现类
    return new DefaultSqlSessionFactory(config);
  }

6.SqlSession的创建

代码语言:txt
复制
	回到我们之前写的测试类代码,看看SqlSession创建需要做什么操作,这里我们进入openSession()方法
代码语言:java
复制
SqlSession sqlSession = sqlSessionFactory.openSession();

可以发现我们需要去看看openSession()方法的实现,由于我们已经知道最终build()方法返回了一个DefaultSqlSessionFactory,直接进入DefaultSqlSessionFactory的openSession().

代码语言:java
复制
  @Override
  public SqlSession openSession() {
    //从这里的执行器类型参数可以推测最终SqlSession还是由Executor去进行执行的
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

我们进入openSessionFromDataSource()方法中

代码语言:java
复制
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //通过configuration获取到环境对象(包括数据库连接配置等)
      final Environment environment = configuration.getEnvironment();
      //获取事务相关内容
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      //进行返回
      return new ````(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

至此我们测试类中上面获取配置文件,创建SqlSession流程也分析完毕,下面放上一张总结图

至此mybatis执行流程已经全部分析完毕

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MyBatis核心流程源码分析(下)
    • Mybatis核心对象如何与SqlSession建立联系
      • mybatis代理核心大致流程
      • mybatis代理源码分析
    • 4.mybatis读取配置文件(Configuration的封装)
      • 5.构建SqlSessionFactory
        • 6.SqlSession的创建
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档