MyBatis 3源码解析

Posted xpz-python

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis 3源码解析相关的知识,希望对你有一定的参考价值。

MapperProxy

到了这了大家可能还有一个疑问,我调用的是DAO接口中的方法,和上面这些好像没关系。别急接下来我们就来看看二者是怎么联系起来的

在mybatis和Spring集合使用中,使用DAO时我们一般使用@Autowired注入,但是大家有没有一个疑问,DAO是一个接口,接口是不能创建对象的,这个是怎么完成的呢?

Mybatis获取如何获取Mapper?

先来看第一个疑惑:使用mybatis操作数据库时,方法是这样的:

SqlSessionFactory sessionFactory = null;  
String resource = "mybatis-conf.xml";  
 
sessionFactory = new SqlSessionFactoryBuilder().build(Resources  
              .getResourceAsReader(resource));
 
SqlSession sqlSession = sessionFactory.openSession();
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println("jdk proxy mapper : "+mapper);
UserPO userPo = mapper.getUserByUsername("zhangsan");

如何获取SqlSession上面我们已经了解了,这里重点关注session.getMapper(UserMapper.class)这一句
技术图片

上图描述了getMapper()这个方法的过程,我们来看MapperRegistry中的这个方法:

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

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

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

konwMapper会在创建MapperRegistry对象是初始化,key是我们定义的Dao的class对象,value是根据各Dao接口的class对象创建的MapperProxyFactory、接下来我们看MapperProxyFactory:


public class MapperProxyFactory<T> {
  //我们的Dao接口的class对象
  private final Class<T> mapperInterface;
 
  @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);
  }
 
}

到了这一步我们明白了,这是通过动态代理创建Dao接口的动态类的对象,而对接口所有方法的调用,最后都会回到调用mapperProxy的invoke方法上(这就是JDK动态代理)。我们去看看mapperProxy对象的invoke方法:


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

if判断我们调用的方法是否是对象中的,我们调用的都是接口的方法,所以直接走mapperMethod.execute()。mapperMethod标识我们调用接口中的那个方法:

public class MapperMethod {
 
  private final SqlCommand command;
  private final MethodSignature method;
 
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
 
  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;
  }
}

好,这里就和上面的SqlSession接上了,最后这些操作都会归为SqlSession中的update、selectList、select操作了。而这其实我们已经知道了,最后SqlSession其实是交给Statement执行SQL命令了。

mybatis-spring如何获取Mapper?

下面来解决另外一个疑惑@Autowired注入的是个什么鬼?

注入的其实就是动态代理创建的接口实现类型的对象,不过mybatis-spring把sqlSession.getMapper()这个动作交个了MapperFactoryBean。而这个实在Spring扫描dao层的时候,为每一个接口分别创建一个MapperFactoryBean:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
 
  private Class<T> mapperInterface;
 
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
 
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
 
}

mapperInterface就是dao的class对象,因为实现了FactoryBean接口,因此通过@Autowired获取对象时,实际是调用getObject方法获取对象,而这里有回到了sqlSession.getMapper()。到此终于和上面接上了

以上是关于MyBatis 3源码解析的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis——源码解析MyBatis框架底层的执行原理

MyBatis 3源码解析

MyBatis 源码解析

MyBatis 3源码解析

Mybatis 源码:Mybatis配置解析

MyBatis源码解析-搭建调试环境