Mybatis源码解析之mapper接口的代理模式详解

 更新时间:2023年12月25日 11:47:43   作者:二狗家有矿  
这篇文章主要介绍了Mybatis源码解析之mapper接口的代理模式详解,在mybatis中执行sql时有两种方式,一种是基于statementId,也就是直接调用SqlSession的方法,需要的朋友可以参考下

一、简介

在mybatis中执行sql时有两种方式,一种是基于statementId,也就是直接调用SqlSession的方法,如sqlSession.update(“statementId”); 还有一种方法是基于java接口,也是日常开发中最常用的方式。 mapper接口中的每个方法都可以喝mapper xml中的一条sql语句对应,我们可以直接通过调用接口方法的方式进行sql执行。因为mybatis会为mapper接口通过jdk动态代理的方法生成接口的实现类,本篇文章将针对mapper接口的代理展开分析。 以下是mapper接口的代理模式的核心组件的类图。

Mapper代理模式类图

二、MapperRegistry

MapperRegistry通过Map结构的属性knownMappers中维护着mybatis中所有的mapper接口。

1. MapperRegistry#addMapper(class)

当开发者在配置文件中配置了通过mappers节点的子节点mapper配置了mapper接口时,会调用configuation#addMapper(Class)记录mapper接口,而configuration又委托了MapperRegistry#addMapper(class)处理逻辑。

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

可以看到,MapperRegistry以mapper接口的类型为key值,将接口类型封装成MapperProxyFactory作为value值放入knownMappers。

2. MapperRegistry#getMapper(Class, SqlSession)

该方法基于SqlSession参数向外提供了对应类型的mapper接口的对象。

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

根据mapper接口类型找到对应的MapperProxyFactory时,调用其newInstance方法得到对应的对象返回。

三、MapperProxyFactory

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

如果对jdk动态代理有一定了解,很容易就能看出来MapperProxyFactory的newInstance方法是很典型的生成代理对象的方式。

四、MapperProxy

MapperProxy作为InvocationHandler的实现类,是jdk动态代理模式的核心。

1. MapperProxy#invoke(Object, Method, Object[])

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

对于Object方法和default方法,执行原方法逻辑即可。 对于对于sql语句的方法,交给MapperMethod 处理。

五、MapperMethod

Mapper类负责去处理mapper接口中的sql方法。

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

MapperMethod还有一个静态内部类MethodSignature用作mapper方法的标签,SqlCommand用作sql命令。 可以看到,根据sql命令的类型(insert|update|delete|select|flush)和返回类型分别调用SqlSession的不同方法,然后对insert|update|delete方法的返回值做适配。

MapperMethod#rowCountResult(int)

private Object rowCountResult(int rowCount) {
 final Object result;
  if (method.returnsVoid()) {
    result = null;
  } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
    result = rowCount;
  } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
    result = (long)rowCount;
  } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
    result = rowCount > 0;
  } else {
    throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
  }
  return result;
}

insert|update|delete方法方法的返回值就是sql命令的匹配行数,而在mapper方法中支持Integer、Long、Boolean和void类型的返回,因此做简单适配。

到此这篇关于Mybatis源码解析之mapper接口的代理模式详解的文章就介绍到这了,更多相关mapper接口的代理模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现随机验证码功能实例代码

    Java实现随机验证码功能实例代码

    在这里,我们使用servlet来实现随机验证码的实现,有需要的朋友可以参考一下
    2013-08-08
  • springboot整合xxl-job的实现示例

    springboot整合xxl-job的实现示例

    本文主要介绍了springboot整合xxl-job的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Java语言实现简单FTP软件 FTP上传下载队列窗口实现(7)

    Java语言实现简单FTP软件 FTP上传下载队列窗口实现(7)

    这篇文章主要为大家详细介绍了Java语言实现简单FTP软件,FTP上传下载队列窗口的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • java线程池工作队列饱和策略代码示例

    java线程池工作队列饱和策略代码示例

    这篇文章主要介绍了java线程池工作队列饱和策略代码示例,涉及线程池的简单介绍,工作队列饱和策略的分析及代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Java如何调用C++ DLL库

    Java如何调用C++ DLL库

    本文重点给大家介绍java中调用c++ dll库的方法,本文分步骤介绍的非常详细,感兴趣的朋友可以参考下
    2016-06-06
  • Java中List add添加不同类型元素的讲解

    Java中List add添加不同类型元素的讲解

    今天小编就为大家分享一篇关于java的List add不同类型的对象,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • 使用shardingsphere对SQLServer坑的解决

    使用shardingsphere对SQLServer坑的解决

    本文主要介绍了使用shardingsphere对SQLServer坑的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-03-03
  • Java自学书籍Top 10

    Java自学书籍Top 10

    这篇文章主要为大家推荐了Java书籍Top 10,是由Java Inside推荐的十本不错的Java书籍,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • 举例讲解Java中do-while语句的使用方法

    举例讲解Java中do-while语句的使用方法

    这篇文章主要介绍了Java中do-while语句的使用方法例子,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • 使用递归删除树形结构的所有子节点(java和mysql实现)

    使用递归删除树形结构的所有子节点(java和mysql实现)

    下面小编就为大家带来一篇使用递归删除树形结构的所有子节点(java和mysql实现)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10

最新评论