文档章节

Mybatis源码概览(二) ---Plugin扩展与Spring结合原理

robin-yao
 robin-yao
发布于 2016/03/23 11:48
字数 2100
阅读 692
收藏 6

    本文主要介绍Mybatis通过动态代理避免对sqlSession直接调用,而是通过MapperProxy代理技术生成了具体dao接口的Mapper实例,里面封装了对sqlSession的调用;Mybatis预留了Interceptor接口,用户可以扩展该接口,实现自定义插件;Mybatis与Spring结合主要通过Spring 的FactoryBean技术实现;


MapperProxy

    把Mybatis源码概览(一)中的helloWorld例子的注释代码打开 如下

BlogDao mapper = session.getMapper(BlogDao.class);
List<Blog> blogs= mapper.selectAll();

    这样通过session.getMapper获取某个Dao接口的Mapper代理实例,这样后面查询就不需要直接对sqlSession来操作,在具体应用中会省略掉很多代码。具体生产Mapper代理原理我们可以Debug一步一步分析。

    第一加载解析Mybatis配置文件时 跟进代码,下面这个片段是解析Mybatis几大属性 其中最后有个mappers

//XMLConfigBuilder类中
private void parseConfiguration(XNode root) {
  try {
    Properties settings = settingsAsPropertiess(root.evalNode("settings"));
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    loadCustomVfs(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectionFactoryElement(root.evalNode("reflectionFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    //解析具体的mapper映射,转到下断代码
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

   

//XMLConfigBuilder类中
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 {
        //解析通过xml resource引用其他mapper文件的方式
        //<mapper resource="mapper/BlogMapper.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);
          InputStream inputStream = Resources.getResourceAsStream(resource);
           //获取到mapper映射文件 然后进行解析到configuration,
          //这样以后可以直接通过configuration取到该mapper
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //执行解析,此步过后,可以在configuration中看到MapperRegistry属性里面已经有实例,
          //而且其knownMappers已经有值,这一步主要调用MapperRegistry类中的addMapper方法
          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.");
        }
      }
    }
  }
}

//MapperRegistry类

//解析到mapper对应的dao接口 添加进去,并为该接口实例一个MapperProxyFacotry
//(里面就是通过JDK Proxy动态生产一个具体接口实例)
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 {
      //为每个mapper接口添加一个MapperProxyFacotry
      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);
      }
    }
  }
}


    这里详细介绍下MapperMethod,MapperProxy,MapperProxyFactory,MapperRegistry这四个类之间的关系:

MapperRegistry可以理解为一个map ,维护了Mapper接口与具体的MapperProxyFactory的关系,registry一般直接放在congfiguration中,可以直接用来查询;获得到具体MapperProxyFactory之后,MapperProxyFactory主要是在newInstance中调用Proxy.newInstance来生产对应Mapper接口的具体MapperProxy,MapperProxy实现了InvocationHandler接口,这个MapperProxy为了提高效率,里面为该Mapper对应的每一个方法维护了一个对应的MapperMethod,这样实现了对MapperMethod的重复利用;MapperProxy在invoke方法中会根据调用的mapper方法名找一个对应的MapperMethod来执行具体的调用sqlSession发起SQL请求;如果没找到就new一个 ,并放到map缓存起来;

这里主要是通过动态代理技术把MapperMethod对sqlSession的执行操作封装到Mapper代理中

    下面主要看下MapperProxy和MapperMethod源码

 MapperProxy类

//实现了InvocationHandler,并把每个mapper方法对应的MapperMethod缓存起来
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 {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //通过mapperMethod发起具体的请求
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  //缓存MapperMethod实例,实现对象重复利用
  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;
  }

}

    MapperMethod类

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

 //---------省略代码-----//
 //可以看到最后还是转换到对sqlSession的调用
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断具体SQL类型 执行增删改查操作
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      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;
  }
  
  //-------省略代码--------//
 }


    上面代理生产的准备工作完成后,我们在执行mapper接口具体方法时会到configuration->mapperRegistry中查找MapperProxy,返回具体的mapperProxy实例

//MapperRegistry类
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接口直接调用就完成了。


Plugin

    Mybatis预留了Interceptor接口来实现Plugin技术,这里只简单介绍一下

    首先实现一个Interceptor接口类,然后在配置xml中配置plugin。这样就可以根据Interceptor拦截我们需要的执行过程,比如可以动态修改MappedStatement,实现对SQL,SQL参数的修改。

    这里介绍一个比较好用的分页插件 就是利用该原理实现:Mybatis分页插件 github https://github.com/pagehelper/Mybatis-PageHelper


Mybatis-Spring

    一般扩展spring 都会用到spring schema技术,并实现相应的handler 解析类,解析相应的扩展配置,这里不多介绍。

Mybatis-Spring jar中最重要的几个类就是MapperFactoryBean,SqlSessionFactoryBean,SqlSessionTemplate。

    先看看SqlSessionTemplate实现了SqlSession接口,对sqlSession做了一层包装。这个类构造时候需要SqlSessionFactory参数,主要还是用来生成SqlSession的,但是这里生产的是对SqlSession做了代理的,实现了对方法调用的异常处理,事务,session关闭等处理。

    看下面SqlSessionTemplate关键代码

//其构造方法,需要传入sqlSessionFactory来生产sqlSession
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  notNull(executorType, "Property 'executorType' is required");

  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  //生成代理
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
      SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class },
      new SqlSessionInterceptor());
}
//内部类 实现invocationHandler接口
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //utils 类的静态方法
    SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
    try {
      //实现具体调用
      Object result = method.invoke(sqlSession, args);
      //提交事务
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      //异常处理
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
       //关闭session
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

    SqlSessionFactoryBean主要是实现了spring FactoryBean,里面属性配置包含了datasource(数据源),configLocation(mybatis配置信息,比如plugin等),mapperLocations(我们写的sql mapper映射xml地址);有了这些我们就可以通过SqlSessionFactoryBean 为我们提供SqlSessionFactory实例。我们就可以把SqlSessionFactory交给SqlSessionTemplate为我们生成session代理。

    MapperFactoryBean类主要实现了spring FactoryBean接口,通过getObject为我们提供Mapper代理,避免对sqlSession的手工操作。MapperFactoryBean扩展了抽象类SqlSessionDaoSupport,里面有对SqlSessionTemplate的封装,为我们提供session代理。

    MapperFactoryBean中的关键代码

@Override
public T getObject() throws Exception {
  //借助sqlSessiontTemplate生成的session代理,查找出对应我们dao接口对应的mapper实例
  //这样我们在spring中只需要定义相应的dao接口方法参数 即可,不用手工一一来操作sqlsession
  return getSqlSession().getMapper(this.mapperInterface);
}

到这mybatis-spring大概分析完毕,里面还有很多细枝末叶,这里就不介绍了。

本文链接:http://my.oschina.net/robinyao/blog/645886

© 著作权归作者所有

robin-yao
粉丝 167
博文 54
码字总数 61436
作品 0
杭州
私信 提问
Mybatis源码概览(一)

一般拿到源码会无从下手,我的基本思路一般就是根据一个基本的helloWorld Debug下去,把主线先大概理一遍,然后再具体分析细节,没有必要一个类一个类细看,看了也会忘掉。自己理源码的时候看...

robin-yao
2016/03/22
870
0
早前学习Java记录

Spring 对 iBATIS 的支持】 Spring 通过 DAO 模式,提供了对 iBATIS 的良好支持。 SqlMapClient:是 iBATIS 中的主要接口,通过 xml 配置文件可以让 Spring 容器来管理 SqlMapClient 对象的创...

大风厂蔡成功
2016/07/10
43
0
MyBatis-Plus Gradle 代码生成插件了解一下

Mybatis Plus Gradle Generator 介绍 Web开发中使用mybatis比较多,MyBatis Plus是一个比较好的扩展,并且还可以自动生成代码,比较方便。 但是MyBatis Plus 对maven比较友好,对Gradle就比较...

流水不腐小夏
2018/12/21
0
0
spring boot 1.5.4 入门和原理(二)

1 spring boot入门 1.1 环境准备 JDK 7及以上 eclipse开发工具 项目管理工具Maven 本文采用、RELEASE(或1.5.2.RELEASE)调试通过。 spring-boot相关项目源码, 码云地址:https://git.oschi...

wyait
2017/09/18
0
0
Spring+mybatis测试项目总结

1.项目目的 a.通过Spring+Mybatis实现通过web访问达成mySql的操作 b.理解Spring+Mybatis的配置 c.理解maven对项目的管理 2.配置文件 a.采用maven约定结构,src/main/java、src/main/resource...

飓风2000
2014/07/26
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

uni app 零基础小白到项目实战

$emit 子组件传给父组件$ref 父组件操作子组件 公用模板 uni-app全局变量的几种实现方法 const websiteUrl = 'http'const now = Date.now || function() { return new Date().getTime......

达达前端小酒馆
31分钟前
7
0
Tomcat是如何实现异步Servlet的

前言 通过我之前的Tomcat系列文章,相信看我博客的同学对Tomcat应该有一个比较清晰的了解了,在前几篇博客我们讨论了Tomcat在SpringBoot框架中是如何启动的,讨论了Tomcat的内部组件是如何设...

木木匠
56分钟前
31
0
mysql中间件分享(Mysql-prxoy,Atlas,DBProxy,Amoeba,cobar,TDDL)

hello 各位小伙伴大家好,我是小栈君,这期我们分享关于mysql中间件的研究,也就是数据层的读写分离和负载均衡,希望能够在实际的应用中能够帮助到各位小伙伴。 下期我们将继续分享go语言的系...

IT干货栈
今天
15
0
OSChina 周一乱弹 —— 人生,还真是到处是意外

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @这次装个文艺青年吧 :#今日歌曲推荐# 分享lil peep的单曲《High School》 《High School》- lil peep 手机党少年们想听歌,请使劲儿戳(这里...

小小编辑
今天
1K
13
Spring使用ThreadPoolTaskExecutor自定义线程池及实现异步调用

多线程一直是工作或面试过程中的高频知识点,今天给大家分享一下使用 ThreadPoolTaskExecutor 来自定义线程池和实现异步调用多线程。 一、ThreadPoolTaskExecutor 本文采用 Executors 的工厂...

CREATE_17
今天
12
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部