mybatis实现源码简析

原创 2018年04月17日 15:21:51
一、概述

用spring+mybatis搭建项目的时候,刚开始就有个困惑,Mapper只有接口,没有实现,那么怎么样实现底层的sql查询呢,后来网上查资料,加上有了设计模式的相关知识后,才知道用的是java的动态代理技术生成了代理类MapperProxy,mybatis的运行逻辑上分为二部分,一部分根据接口生成接口动态代理类,二是执行接口的方法时,实际上执行的是接口的动态代理的invoke方法,执行参数绑定、操作、返回参数的解析;

二、源码解析:

我们都知道mybatis的执行离不开sqlsession,那么代理类又是如何多SqlSession进行封装的呢?带着这些疑问,让我们通过分析源代码的方式来解释这些问题。

sqlSession的调用:

//获取session  
        SqlSession session = sqlSessionFactory.openSession();  
        //获取mapper接口的代理对象  
        UserMapper userMapper = session.getMapper(UserMapper.class);  
        //调用代理对象方法  
        User user = userMapper.findUserById(27);  
        System.out.println(user);  
        //关闭session  
        session.close();  
        System.out.println("---------执行完毕-----------");  
我们主要看下面这个方法
UserMapper userMapper = session.getMapper(UserMapper.class);  

@Override
public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this);
}
这个是上面getMapper的实现,跟进代码里面发现了MapperRegistry类,

mybatis封装mapper接口的主要有四个类,位于org.apache.ibatis.binding包下面,MapperRegistry是注册代理接口与获取代理类实例的工具类,如下:

/**
 *    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 org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 * @author Lasse Voss
 */
public class MapperRegistry {

  //全局配置文件对象
  private final Configuration config;
  //一个Map,key是Mapper类型的Class类型对象,value是创建Mapper代理对象的工厂
 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

 //根据Mapper的Class类型对象和SqlSession对象得到Mapper的代理对象
  @SuppressWarnings("unchecked")
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //根据type获取mapper代理对象工厂的实例,如果没有获取到,抛出异常
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
     //创建一个当前接口的代理对象传到sqlSession
     return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
  
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }

  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<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  /**
   * @since 3.2.2
   */
  public Collection<Class<?>> getMappers() {
    return Collections.unmodifiableCollection(knownMappers.keySet());
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

  /**
   * @since 3.2.2
   */
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  
}
MapperProxyFactory对象代码如下:

/**
 *    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> {

  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) {
   //用jdk的动态代理技术生成要代理的mapper接口的动态代理类;
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  //传入sqlSession,生成MapperProxy的代理类,MapperProxy实现了jdk的动态代理接口InvocationHandler
  public T newInstance(SqlSession sqlSession) {
  //创建一个代理类对象,设置SqlSession的值,MapperProxy是一个动态代理对象
 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
以上是mybatis初始化的的时候做的事情,但是对于真正执行sql操作的时候又做了哪些操作呢,当调用mapper
接口时,实际调用的是接口的动态代理的invoke方法,先看下代理对象MapperProxy源码如下,入口是invoke方法;

/**
 *    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 {

  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();
  }
}
在cachedMapperMethod方法里面,先判断缓存里面是否已经缓存了MapperMethod,如果没有,则新建一个
MapperMethod,看下MapperMethod源码的实现,主要是下面这个方法:

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

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
  final String methodName = method.getName();
  final Class<?> declaringClass = method.getDeclaringClass();
 //根据接口名+方法名获取MappedStatement,一条MappedStatement记录就是mybatis中配置的一条
  //sql记录,包含id=namespace+sql的id,操作类型(select、delete、update)等等一系列信息;
  MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
      configuration);
  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 {
    name = ms.getId();//com.newnoa.api.mapper.ApiMapper.getApiByServiceAndPort
    type = ms.getSqlCommandType();//SELECT
    if (type == SqlCommandType.UNKNOWN) {
      throw new BindingException("Unknown execution method for: " + name);
    }
  }
}

public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
//解析返回类型,一般是自定义类型 ,例如:class com.newnoa.api.entity.po.ApiDB
 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);
  //通过param注解获得参数的名称和顺序,名称和xml中定义的要相同
 this.paramNameResolver = new ParamNameResolver(configuration, method);
}
接着就是执行MapperMethod的execute方法了:
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
//根据command操作来决定是什么操作,例如:select、update、insert、update
 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()) {//返回Map
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) {//返回游标
        result = executeForCursor(sqlSession, args);
      } else {
        //组装参数,通过前面得到的参数名称和序号和方法执行传进来的参数拼装成一个map
        //key是参数的名称,value是参数的值
       Object param = method.convertArgsToSqlCommandParam(args);
       //这个大家应该很熟悉了,就是通过mybatis的xml原始的调用方式
       //com.newnoa.api.mapper.ApiMapper.getApiByServiceAndPort
      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;
}
 后面debug跟到了DefaultSqlSession类的方法,其实selectOne底层调用的也是selectList方法:
@Override
public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  List<T> list = this.<T>selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null;
  }
}

后面执行又跟到SimpleExecutor类的query方法:
@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);
  }
}

最后到类PreparedStatementHandler里面:

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}
这是debug后得到statement的结果,可以看出mybatis底层就是封装的jdbc:


注:因为源码还是很复杂的,我也只能大致看个大概,同时写这边文章也是方便自己记忆,所以可能会有理解出错
的地方,还望各位大神指导

 

MyBatis视频—国内首套:源码级讲授的MyBatis视频

MyBatis是目前非常流行的一个轻巧、便利的持久化层框架。 本视频除对MyBatis日常基本使用示范外,还涉及诸多细节。以及较深入源码的讲解MyBatis运行原理、插件机制和一些企业实用场景。   
  • 2017年04月11日 13:31

MyBatis源码简析

MyBatis用的时间也不算短了,虽然应用的很熟练了,但是源码并没有研究过,今天就“忙里偷闲”来研究一下。 实际中使用MyBatis很少单独使用的,几乎都是和Spring配合使用,所以我们先看一下常见...
  • a18716374124
  • a18716374124
  • 2017-11-11 12:59:32
  • 183

Mybatis实现批量插入和更新(Mybatis3.2源码修改)

Mybatis框架针对批量方便存在一些缺陷,本文针对inser和update批量对Mybatis源码进行了修改,供大家参考: BaseStatementHandler.java //增加变量存储传入参...
  • dreamprogrammer
  • dreamprogrammer
  • 2014-11-29 20:07:09
  • 643

关于Mybatis的$和#的细节,源码解析

前言 在JDBC中,主要使用的是两种语句,一种是支持参数化和预编译的PrepareStatement,能够支持原生的Sql,也支持设置占位符的方式,参数化输入的参数,防止Sql注入,一种是支持原生S...
  • lz710117239
  • lz710117239
  • 2017-07-27 19:58:24
  • 935

Floodlight核心包源码简析

  • 2014年04月21日 14:36
  • 562KB
  • 下载

Virtual DOM的介绍

译者:TinkGu链接:http://www.zcfy.cc/article/1211原文:https://hashnode.com/post/the-one-thing-that-no-one-pr...
  • xinghuowuzhao
  • xinghuowuzhao
  • 2018-02-28 08:11:59
  • 24

mybatis源码解析之SqlSession接口。

*SqlSessions 由 SqlSessionFactory 实例创建的。SqlSessionFactory 对 象 包 含 创 建 SqlSession 实 例 的 所 有 方 法 。 而 Sq...
  • JDJH1024
  • JDJH1024
  • 2017-08-12 15:24:48
  • 5518

Mybatis源码分析 之 接口调用

在之前的文章中,我们分析了Mybatis对于配置文件的解析。已经里面的一些关键类。下面我们就来分析一下Mybatis把接口和Mapper文件是如何通过创建代理类来进行绑定。这是Mybatis中的一大亮...
  • u012410733
  • u012410733
  • 2017-04-03 16:29:45
  • 643

mybatis分页源码

  • 2013年07月07日 21:22
  • 4KB
  • 下载

MyBatis 分页源码简单

  • 2017年07月17日 10:27
  • 10KB
  • 下载
收藏助手
不良信息举报
您举报文章:mybatis实现源码简析
举报原因:
原因补充:

(最多只允许输入30个字)