MyBatis 中 Mapper 接口的使用原理

MyBatis 中 Mapper 接口的使用原理

MyBatis 3 推荐使用 Mapper 接口的方式来执行 xml 配置中的 SQL,用起来很方便,也很灵活。在方便之余,想了解一下这是如何实现的,之前也大致知道是通过 JDK 的动态代理做到的,但这次想知道细节。

东西越多就越复杂,所以就以一个简单的仅依赖 MyBatis 3.4.0 的 CRUD 来逐步了解 Mapper 接口的调用。

通常是通过 xml 配置文件来创建SqlSessionFactory对象,然后再获取SqlSession对象,接着获取自定义的 Mapper 接口的代理对象,最后调用接口方法,示例如下:

/**
 *
 * @author xi
 * @date 2018/10/01 14:12
 */
public class Demo {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
        Role role = roleMapper.getRole(1L);
        System.out.println(role);
    }
}

如何解析配置文件,创建工厂,获取会话皆不是本次关注的重点,直接看下面这行即可:

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);

获取自定义 Mapper 代理对象的方法位于:org.apache.ibatis.session.SqlSession#getMapper,还是个范型方法

  /**
   * Retrieves a mapper.
   * @param <T> the mapper type
   * @param type Mapper interface class
   * @return a mapper bound to this SqlSession
   */
  <T> T getMapper(Class<T> type);

实现该方法的子类有:DefaultSqlSessionSqlSessionManager,这里关注默认实现即可:org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }

这里面出现了 Configuration 对象,简单来说就是包含了 xml 配置解析内容的对象,同样它也不是现在关注的重点,继续往下跟进:org.apache.ibatis.session.Configuration#getMapper

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

这里出现了MapperRegistry对象,它是解析 Mapper.xml 中的内容(mapper标签中的namespace就包含了 Mapper 接口的全限定名称)得来的,含有一个 HashMap 类型的成员变量org.apache.ibatis.binding.MapperRegistry#knownMappers,key 是 Mapper 接口的Class对象,value 是org.apache.ibatis.binding.MapperProxyFactory,从名称就可以看出是用来创建 Mapper 接口的代理对象的工厂,后面会用到。

具体这个knownMappers是怎么填充的,详见org.apache.ibatis.binding.MapperRegistry#addMapper方法,暂时不管,先往下走:org.apache.ibatis.binding.MapperRegistry#getMapper

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

根据 Mapper 接口的类型,从knownMappers中拿到对应的工厂,然后创建代理对象,继续跟进:org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)

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

这里又出现了一个MapperProxy对象,理解起来应该是一个代理对象,打开一看它实现了java.lang.reflect.InvocationHandler接口,这是挂羊头卖狗肉哇。
先不看狗肉了,继续跟进:org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)

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

在这,看到了熟悉的java.lang.reflect.Proxy,这里的mapperInterface是创建工厂时传入的 Mapper 接口。真正的 Mapper 接口的代理对象此时才产生,是真羊头。

这不是既熟悉又陌生的 JDK 动态代理么,说它熟悉,因为前面的狗肉mapperProxy是一个InvocationHandler对象,它拦截了所有对代理对象接口方法的调用。说它陌生是因为之前使用 JDK 动态代理时会有接口的具体实现子类,这里没看到。那就先看看org.apache.ibatis.binding.MapperProxy

/**
 * @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 {
      // 判断 method 是不是 Object 类的方法,如 hashCode()、toString()
    if (Object.class.equals(method.getDeclaringClass())) {
      try {// 如果是,则调用当前 MapperProxy 对象的这些方法
      // 跟 Mapper 接口的代理对象没关系
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    // 到这了,说明调用的是接口中的方法,具体的执行就不是本次关注的重点了
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

// 对 MapperMethod 做了缓存,这个 methodCache 是个 ConcurrentHashMap,在 MapperProxyFactory 中创建的
  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. 通过 JDK 动态代理模式,创建 Mapper 接口的代理对象,拦截对接口方法的调用;
  2. Mapper 接口中不能使用重载,具体原因参见org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand,MyBatis 是通过mapperInterface.getName() + "." + method.getName()去获取 xml 中解析出来的 SQL 的,具体可能还要看一下org.apache.ibatis.session.Configuration#mappedStatements

最后的最后,古话说的好:遇事不决,先开大龙(看源码)(逃

原文地址:https://www.cnblogs.com/magexi/p/11756515.html

时间: 2024-10-07 16:13:36

MyBatis 中 Mapper 接口的使用原理的相关文章

Mybatis中mapper.xml中的模糊查询

Mybatis中mapper.xml中的模糊查询 <!-- 方法一: 直接使用 % 拼接字符串 注意:此处不能写成 "%#{name}%" ,#{name}就成了字符串的一部分, 会发生这样一个异常: The error occurred while setting parameters, 应该写成: "%"#{name}"%",即#{name}是一个整体,前后加上% --> <if test="name != nul

MyBatis+Spring 基于接口编程的原理分析

整合Spring3及MyBatis3 对于整合Spring及Mybatis不作详细介绍,可以参考: MyBatis 3 User Guide Simplified Chinese.pdf,贴出我的主要代码如下: package org.denger.mapper;    import org.apache.ibatis.annotations.Param;  import org.apache.ibatis.annotations.Select;  import org.denger.po.Us

mybatis 中mapper 的namespace有什么用

原文:http://zhidao.baidu.com/link?url=ovFuTn7-02s7Qd40BOnwHImuPxNg8tXJF3nrx1SSngNY5e0CaSP1E4C9E5J6Xv5fI9P_dTMqHeBRGOID9bk9IcY1o9h6O21l6rHRAwj_Km3 ----------------------------------------------------------------------------------------------------------

mybatis的mapper接口加入Log4j日志

配置Log4J比较简单, 比如需要记录这个mapper接口的日志: package org.mybatis.example;public interface BlogMapper {   @Select("SELECT * FROM blog WHERE id = #{id}")   Blog selectBlog(int id);} 只要在应用的classpath中创建一个名称为log4j.properties的文件, 文件的具体内容如下: # Global logging conf

关于使用mybatis中mapper instrances,通过session另一种操作方式

1 String resource = "mybatis-config.xml"; 2 InputStream inputStream = null; 3 try { 4 // 获取SqlSessionFactory 5 inputStream = Resources.getResourceAsStream(resource); 6 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream)

MyBatis的Mapper接口以及Example的实例函数及详解

来源:https://blog.csdn.net/biandous/article/details/65630783 一.mapper接口中的方法解析 mapper接口中的函数及方法 方法 功能说明 int countByExample(UserExample example) thorws SQLException 按条件计数 int deleteByPrimaryKey(Integer id) thorws SQLException 按主键删除 int deleteByExample(Use

MyBatis中Mapper的返回值类型

insert.update.delete语句的返回值类型 对数据库执行修改操作时,数据库会返回受影响的行数. 在MyBatis(使用版本3.4.6,早期版本不支持)中insert.update.delete语句的返回值可以是Integer.Long和Boolean.在定义Mapper接口时直接指定需要的类型即可,无需在对应的<insert><update><delete>标签中显示声明. 对应的代码在 org.apache.ibatis.binding.MapperMe

关于Mybatis中Mapper是使用XML还是注解的一些思考

XML 据说可以灵活的进行注解,但是修改以后还是要重新发布程序.当然,你可以说,在Tomcat中改了,然后热加载了,不就可以了.可是一般情况下都是几台,十几台服务器.都是用发布系统,持续集成的方式部署.这点灵活性也就没什么意义了.当然,一定要说XML支持好,这点我不否认.然而在注解中支持了大部分功能,如果实在复杂一点的SQL可以使用<script>方式或者使用Provider也行. 那再说,ResultMap支持的不好,但从3.某个版本,支持使用id,这样也可以在一定程度上进行复用了. 如果再

Mybatis中Mapper代理形式开发与spring整合

1.导入jar包 2.分包 cogfig:存放配置文件 mapper:存放映射与接口 pojo:存放实体类 test:测试代码 3.编写配置文件 SqlMapConfig.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybat