Mybatis我们一般都是和Spring一起使用的,它们是怎么融合到一起的,又各自发挥了什么作用?

就拿这个Mapper来说,我们定义了一个接口,声明了一个方法,然后对应的xml写了这个sql语句, 它怎么就执行成功了?这家伙是怎么实现的,带着这个好奇心,我一步步跟踪,慢慢揭开了它的面纱。

一、初始化时的埋点

MapperFactoryBean的父类SqlSessionDaoSupport中setSqlSessionFactory方法构建了一个sqlSession:

public abstract class SqlSessionDaoSupport extends DaoSupport {

   private SqlSession sqlSession;

   private boolean externalSqlSession;

   public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
} //......
}

将会调用这个构造器:

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

产生一个sqlSession代理,SqlSessionTemplate的内部类:

private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final 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) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}

二、获取Mapper

MapperFactoryBean是MapperScannerConfigurer在扫描包后往每个Mapper的beanDefine中添加给BeanClass属性的:

definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("addToConfig", this.addToConfig);

那么当我们获取每个Mapper都会走MapperFactoryBean:

 public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

这个就是在前面提到的SqlSessionTemplate中根据Class类型来获取:

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

Configuration#getMapper:

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

MapperRegistry#getMapper:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
if (!knownMappers.contains(type))
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
return MapperProxy.newMapperProxy(type, sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

public class MapperProxy implements InvocationHandler, Serializable

 public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
ClassLoader classLoader = mapperInterface.getClassLoader();
Class<?>[] interfaces = new Class[]{mapperInterface};
MapperProxy proxy = new MapperProxy(sqlSession);
return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}

通过上面一系列方法,获取的是一个MapperProxy。

虽然我们只定义了接口没有实现类,但纵观这个dao层,做的都是和数据库打交道的事,唯一不同的是sql语句不同,为了便于管理,将所有的sql写在配置文件中,然后根据配置的规则和相应接口生成代理类。

走到这里,共经过了两次代理:

  • 第一次是在SqlSessionTemplate中,持有一个内部类SqlSessionInterceptor,将所有基于SqlSession的操作转移给DefaultSqlSession。

  • 第二次是针对Mapper的代理,为接口生成代理类。这个代理类持有了上面的SqlSessionTemplate(也间接持有了DefaultSqlSession)。

第一次可以说是为了融入Spring而做的代理,让每个Mapper在创建之初就自然而然地持有 了一个SqlSession,后面的操作就是水到渠成。第二次的代理是必然的,根据Mybatis的设计,接口和Xml配置组合的方式,框架在背后为我们生成了代理类,这才符合Java规范嘛。

三、方法调用

抛开Spring的调用栈,从service层来看,例如下面的某个service的某个方法:

@Autowired
private XxMapper xxMapper; @Override
public List<ComResVo> getListByXxId(Integer xxId) {
if(null == xxId){
return null;
}
return this.xxMapper.selectXxListById(xxId);
}

mapper作为属性注入到service中,当时通过MapperFactoryBean的getObject方法获取的就是一个代理类,这个时候调用就会转移到MapperProxy的invoke方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
final Object result = mapperMethod.execute(args);
if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

MapperMethod#execute

public Object execute(Object[] args) {
Object result = null;
if (SqlCommandType.INSERT == type) {
Object param = getParam(args);
result = sqlSession.insert(commandName, param);
} else if (SqlCommandType.UPDATE == type) {
Object param = getParam(args);
result = sqlSession.update(commandName, param);
} else if (SqlCommandType.DELETE == type) {
Object param = getParam(args);
result = sqlSession.delete(commandName, param);
} else if (SqlCommandType.SELECT == type) {
if (returnsVoid && resultHandlerIndex != null) {
executeWithResultHandler(args);
} else if (returnsMany) {
result = executeForMany(args);
} else if (returnsMap) {
result = executeForMap(args);
} else {
Object param = getParam(args);
result = sqlSession.selectOne(commandName, param);
}
} else {
throw new BindingException("Unknown execution method for: " + commandName);
}
return result;
}

execute方法会根据方法类型选择对应的sqlSession方法,在这里至少把增删改查给区分开了,查询方法还给细化了。而传入的sqlSession是一个SqlSessionTemplate,它相关的调用又会转移至它持有的一个sqlSessionProxy(DefaultSqlSession)。

DefaultSqlSession持有给定的Executor,将所有方法最终绑定到Executor的query和update方法。

然后就是分别执行doQuery和doUpdate方法,构造StatementHandler,doQuery方法还要传入一个ResultHandler,处理返回的结果集。

从上面的分析也大致知道了整个流程,对Mybatis的处理方式也有了一定的了解。如果叫我写这么一个框架,我会怎么写?

我想最好的方法就是先不看这个源码,把它的功能全部搞懂,这个可以叫需求分析了,看看它实现了哪些功能,智能到什么程度,然后我自己去实现,这个过程可能会遇到很多问题,搞不出来可以适当参考下,全部搞完还要对比下,看看人家设计的高明之处。

今天貌似是愚人节,节日快乐!

Mybatis源码分析之Mapper的创建和获取的更多相关文章

  1. mybatis源码分析之03SqlSession的创建

    在上一篇中,说到了mybatis是如何构造一个SqlSessionFactory实例的,顾名思意,SqlSessionFactory就是用于创建SqlSession的工厂类. 好,现在我们接着昨天的来 ...

  2. Mybatis源码分析之Mapper执行SQL过程(三)

    上两篇已经讲解了SqlSessionFactory的创建和SqlSession创建过程.今天我们来分析myabtis的sql是如何一步一步走到Excutor. 还是之前的demo    public  ...

  3. Mybatis源码分析之Mapper文件解析

    感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火! xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析: public void ...

  4. 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  5. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  6. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  7. MyBatis源码分析(5)——内置DataSource实现

    @(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...

  8. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  9. MyBatis源码分析之环境准备篇

    前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...

随机推荐

  1. oracle的小语句

    select * from v$nls_parameters; 查询数据库中现在的常量 alter session set NLS_DATE_FORMAT='yyyy-mm-dd'; 更改日期显示方式

  2. HDU6127 简单几何 暴力二分

    LINK 题意:给出n个点,每个点有个权值,可以和任意另外一点构成线段,值为权值积.现问过原点的直线中交所有线段的权值和的最大值,注意直线必不经过点. 思路:直线可以将点集分为两侧,此时的权值为两侧点 ...

  3. LightOJ 1239 - Convex Fence 凸包周长

    LINK 题意:类似POJ的宫殿围墙那道,只不过这道题数据稍微强了一点,有共线的情况 思路:求凸包周长加一个圆周长 /** @Date : 2017-07-20 15:46:44 * @FileNam ...

  4. Python学习笔记(四十)— 内置模块(9)HTMLParser

    摘抄自:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432002312 ...

  5. Hibernate + mysql 查询伪劣时:= 出现 Space is not allowed after parameter prefix ':' MySQL异常

    需求: 要求查询一个站点在地市和省的排名信息出来. 效果图: 代码实现: 注释的部分是起初采用的hibernate 查询,但无论怎么写都会报 space is not allowed after pa ...

  6. [ JS 进阶 ] 闭包,作用域链,垃圾回收,内存泄露

    原网址:https://segmentfault.com/a/1190000002778015 1. 什么是闭包? 来看一些关于闭包的定义: 闭包是指有权访问另一个函数作用域中变量的函数 --< ...

  7. perl6正则 2: 字母,数字,空格,下划线, 字符集

    数字, 字母, 下划线 在perl6中, 如果是 数字, 字母, 下划线, 在正则里可以正接写上. > so / True > so 'perl6_' ~~ /_/ True > 非 ...

  8. 宋牧春: Linux设备树文件结构与解析深度分析(1) 【转】

    转自:https://mp.weixin.qq.com/s/OX-aXd5MYlE_YoZ3p32qWA 作者简介 宋牧春,linux内核爱好者,喜欢阅读各种开源代码(uboot.linux.ucos ...

  9. 一个python爬虫工具类

    写了一个爬虫工具类. # -*- coding: utf-8 -*- # @Time : 2018/8/7 16:29 # @Author : cxa # @File : utils.py # @So ...

  10. Mysql存储之ORM框架SQLAlchemy(一)

    上一篇我们说了mysql存储的原生语句方式,因为原生语句每次写都比较的复杂,所以这里我们说一种引用实体类的方式来操作数据库. 什么是ORM ORM技术:Object-Relational Mappin ...