`

Mybatis源码分析——运行原理及流程

 
阅读更多
可以参考微信公众号中的文章,格式比较清晰,链接:http://mp.weixin.qq.com/s/eZpFfLtpJ4zE24HLYliUyA
欢迎关注微信公众号

1、配置配置文件,扫描mapper文件 
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:XXX.xml"/>
</bean>


2、配置SqlSessionFactoryBean(创建mybatis的工厂类),这里dataSource的配置就不贴出来了 
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <property name="basePackage" value="XXX.mapper" />
     <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
SqlSessionFactoryBean类afterPropertiesSet方法在执行之前所有属性都已设置完成,其功能是校验dataSource(配置文件配置的)和sqlSessionFactoryBuilder(默认new出来的一个新对象),然后创建sqlSessionFactory(后面再讲这个)  。
启动服务时,就会将所有的mappedStatements加载到sqlSession中了 

3、将扫描的mapper接口都一一进行注册代理,调用流程如下: MapperFactoryBean.getObject() --> SqlSessionTemplate.getMapper(Class<T> type) --> Configuration.getMapper(Class<T> type, SqlSession sqlSession) --> MapperRegistry.getMapper(Class<T> type, SqlSession sqlSession)  
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);
  }
}
MapperProxy类代理代码:MapperProxy.newMapperProxy(type, sqlSession);`
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);
}
将所有Mapper接口都进行代理后,后续调用接口中的方法时,都会进入到MapperProxy.invoke()中执行相应逻辑 。

4、启动服务完成后,调用mapper接口时,MapperProxy会自动进行代理,调用invoke方法(因为在服务启动时,就将所有的Mapper接口设置了MapperProxy代理) 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);
  }
  //通过代理类和方法名(全路径方法名) 获取Mapper接口类
  final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
  //设置statement、sqlSession、declaringInterface等,为执行sql做准备
  final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
  //执行sql,并返回结果 
  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方法,具体执行操作类;如果返回的是list,则对应的是returnsMany,返回的是Map,则对应的是returnsMap 
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;
}
最后具体执行sql语句都是调用sqlSession中的方法:sqlSession.insert、sqlSession.update、sqlSession.delete、sqlSession.select、sqlSession.<E>selectList、sqlSession.selectOne等。

5、sqlSession执行具体sql操作过程 
执行sqlSession.<E>selectList时,需要到SqlSession的实现类(SqlSessionTemplate)中调用方法,注意看下面代码,这里面是通过sqlSessionProxy来调用selectList,说明在此做了一个代理 
public <E> List<E> selectList(String statement, Object parameter) {
  return this.sqlSessionProxy.<E> selectList(statement, parameter);
}
进入SqlSessionTemplate类后,此类有个构造方法
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());
}
看最后一句代码,sqlSessionProxy新创建了一个代理实例,执行具体sqlSessionProxy中的方法(sqlSessionProxy.<E> selectList)时,进入到SqlSessionInterceptor.invoke()方法中  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   //获取一个sqlSession 
  final SqlSession sqlSession = getSqlSession(
      SqlSessionTemplate.this.sqlSessionFactory,
      SqlSessionTemplate.this.executorType,
      SqlSessionTemplate.this.exceptionTranslator);
  try {
    //真正执行selectList方法     
    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 {
    //执行完成后,需要关闭SqlSession   
    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  }
}
在invoke方法中,首先需要获取一个SqlSession,通过sessionFactory.openSession(executorType)获取,该代码在SqlSessionUtils类中
SqlSession session = sessionFactory.openSession(executorType);
debug到上面的Object result = method.invoke(sqlSession, args);后,继续往下运行,就进入了DefaultSqlSession中的selectList中 
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    List<E> result = executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    return result;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
此处代码`executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);`继续执行。如果在配置文件中配置有Plugin,则会进入到你配置的插件类中, 在进入配置的插件类中之前,会到代理工具类Plugin(org.apache.ibatis.plugin.Plugin.java)里面  
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    if (methods != null && methods.contains(method)) {
      return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}
然后才通过interceptor.intercept(new Invocation(target, method, args))进入到你配置的插件类里面去,直到配置的插件类代码执行完毕后,才真正执行`executor.<E>query()`方法,此时继续运行,则进入到BaseExecutor中的query()方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) throw new ExecutorException("Executor was closed.");
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache(); // issue #482
    }
  }
  return list;
}
注意看,这里会先从本地缓存中获取你想要的结果,这个key的值对应的就是namespace+statementId+sql语句,如果缓存中没有(需要将缓存功能开启后,才会将结果缓存到本地缓存中),再去查询数据库queryFromDatabase()
在queryFromDatabase()中doQuery()进行查询,跳到SimpleExecutor类中的doQuery()中 
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
在执行handler.<E>query时,会将其委托给PreparedStatementHandler执行query() 
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  //执行查询操作
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}
执行操作ps.execute()时,进入到PreparedStatement中的execute()方法,该方法中有句代码
rs = executeInternal(this.maxRows, sendPacket, doStreaming, (this.firstCharOfStmt == 'S'),
      metadataFromCache, false);
进入到executeInternal方法后,具体执行代码是
rs = locallyScopedConnection.execSQL(this, null, maxRowsToRetrieve, sendPacket,
   this.resultSetType, this.resultSetConcurrency,
   createStreamingResultSet, this.currentCatalog,
   metadataFromCache, isBatch);
继续执行此句代码,进入到ConnectionImpl类中的execSQL方法中,该方法中return执行的结果
return this.io.sqlQueryDirect(callingStatement, null, null,
      packet, maxRows, resultSetType,
      resultSetConcurrency, streamResults, catalog,
      cachedMetadata);
然后进入到MysqlIO类中的sqlQueryDirect方法,下面代码就是发送执行语句
Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket,
      false, null, 0);
继续往下到sendCommand方法中
send(this.sendPacket, this.sendPacket.getPosition());
进入到send()方法,最后通过socket连接数据库,将需要执行的语句发给数据库服务器,让其执行,然后将结果返回给mysqlOutput,最后将结果回填回去。

到此,mybatis运行流程基本上就接受了。

6、总结 
通过对mybatis源码的分析,可以看出其整个流程很多地方都用了动态代理,注册Mapper接口是使用了代理,执行sqlSession中的具体操作时,也使用了代理,其他地方也有用到代理;从Mybatis的源码分析,其实其整个运行流程也不难理解,只要理解其原理后,在后续的使用中才能得心应手。

有理解不到位的地方,还望各位大佬指正
1
1
分享到:
评论

相关推荐

    Mybatis源码分析.pdf

    总结起来,MyBatis源码分析涵盖了从配置加载到数据库操作的全过程,涉及到了配置解析、SQL执行、结果映射等多个关键环节,以及Executor、StatementHandler等核心组件。通过深入学习MyBatis的源码,开发者不仅可以...

    MyBatis官方包和源码包.zip

    在源码分析中,我们可以关注以下几个关键部分: 1. Executor:执行器接口,它是MyBatis的执行核心,处理SQL的执行和结果映射。 2. MappedStatement:存储了SQL的配置信息,包括ID、SQL语句、参数映射和结果映射。 3....

    myBatis3.2.2(含源码、文档)

    在标题"myBatis3.2.2(含源码、文档)"中,我们可以推断这是关于MyBatis框架的特定版本——3.2.2的资源包。这个版本可能包含了框架的源代码,这对于开发者来说非常有价值,因为通过查看源码,可以深入理解框架内部的...

    MyBatis的动态SQL实现原理.pdf

    #### 三、MyBatis解析动态SQL源码分析 MyBatis在解析动态SQL时涉及的关键组件包括`XMLStatementBuilder`、`XMLLanguageDriver`和`XMLScriptBuilder`。具体流程如下: 1. **XMLStatementBuilder**:负责解析映射...

    myBatis系列之七:事务管理

    7. **源码分析**:对于深入理解MyBatis的事务管理,阅读其源码是很有帮助的。MyBatis的事务管理涉及到Transaction接口和其实现类,如JdbcTransaction,它们协同工作以处理事务的生命周期。 通过以上讲解,我们可以...

    mybatis反向工程的基本思路

    标题 "mybatis反向工程的基本思路" 涉及到的是MyBatis框架中的一个重要功能——逆向工程(Reverse Engineering)。MyBatis的反向工程是自动化生成SQL映射文件、DAO接口和实体类的过程,极大地提高了开发效率,减少了...

    JSP源码——lerx2_utf8_v2_beta2_20121214.zip

    通过分析lerx2_utf8_v2_beta2的源码,开发者可以学习到如何在实际项目中应用JSP,理解其工作原理,同时也能了解到项目管理、版本控制和测试策略。对于想要深入理解和提升JSP开发技能的程序员来说,这是一个宝贵的...

    架构探险 从零开始写javaweb框架书上源码

    《架构探险:从零开始写JavaWeb框架》是一本深入探讨JavaWeb开发技术的书籍,其核心内容是通过源码分析来帮助读者理解并构建自己的Web框架。书中的源码提供了实际的编程实践,使读者能够亲身体验到JavaWeb框架的实现...

    MyBatis_Study:MyBatis学习的原始码,博客地址:http

    《深入解析MyBatis——基于"MyBatis_Study"源码的学习》 MyBatis,一个优秀的持久层框架,以其灵活、高效的特性深受广大开发者喜爱。这个名为"MyBatis_Study"的项目,旨在帮助开发者深入理解MyBatis的内部工作原理...

    Spring源码分析

    ### Spring源码分析 #### Spring框架概述 Spring框架是一个开源框架,它于2003年由Rod Johnson创建,旨在简化企业级应用的开发过程。Spring框架最初的理念来源于Rod Johnson在其著作《Expert One-On-One J2EE ...

    ProSpring——Spring专业开发指南

    《ProSpring——Spring专业开发指南》是一本深入探讨Spring框架的专业书籍,旨在帮助开发者全面理解和掌握Spring的核心概念、功能及最佳实践。通过阅读本书,你可以深入理解Spring如何为Java应用程序提供强大的依赖...

    ibatis框架源码剖析光盘资料

    《ibatis框架源码剖析》是一本深入探讨mybatis前身——ibatis的源码解析书籍。通过对源码的深入分析,我们可以理解ibatis的核心机制,掌握数据库操作的底层原理,从而更好地利用和优化这个强大的持久层框架。在这个...

    课设毕设基于SSM的新农大校园论坛系统LW+源码可运行.zip

    【标题】"课设毕设基于SSM的新农大校园论坛系统LW+源码可运行.zip"是一个针对学生课程设计或毕业设计的项目,它采用了一种常见的Java web开发框架——SSM(Spring、SpringMVC、MyBatis)。这个系统主要是为新农大的...

    未知微信小程序的设计与实现+ssm后端源码案例设计.zip

    2. 深入理解SSM框架的运作原理,尤其是Spring的依赖注入和事务管理,SpringMVC的请求处理流程,以及MyBatis的映射配置和动态SQL。 3. 分析源码案例,理解前后端交互逻辑,关注数据安全、性能优化等方面的设计。 4. ...

    JSP基于SSM新生入校学校介绍网站设计可升级SpringBoot毕业源码案例设计.zip

    该毕业设计源码包含了完整的开发流程,从需求分析到设计、编码、测试,再到部署运行,对于学习者来说,是一个宝贵的实战经验。通过阅读和理解源码,可以深入理解SSM和SpringBoot的协同工作原理,掌握Web应用的开发...

    基于java的项目代码--源码

    【基于Java的项目代码——源码】:这个项目是一个用Java语言编写的科研项目申报书管理系统,展示了如何在Java环境中实现一个完整的应用系统。在Java编程中,我们通常会使用面向对象的设计原则,包括封装、继承和多态...

    spring宝典书源码07

    源码分析可以帮助我们理解如何配置Spring Security,保护应用程序免受未经授权的访问。 10. **测试支持**:Spring提供强大的单元测试和集成测试支持,如MockMVC、TestContext框架等。通过源码,我们可以了解如何...

    基于SSM的学生学籍管理系统源码.zip

    这个标题揭示了我们要讨论的核心内容——一个使用SSM(Spring、SpringMVC和MyBatis)框架开发的学生学籍管理系统。SSM是Java web开发中常见的三层架构模式,用于实现业务逻辑、数据访问和视图的分离。Spring作为核心...

    spring源码代码

    Spring 源码分析对于理解其内部工作原理、优化应用性能以及进行二次开发至关重要。让我们深入探讨 Spring 框架的核心组成部分和主要特性。 1. **依赖注入(Dependency Injection, DI)** Spring 的核心特性之一是...

    java项目之微信小程序-零担物流智慧管理平台设计(源码+说明文档+演示视频).zip

    说明文档可能涵盖了项目的需求分析、系统设计、功能实现及测试等内容。需求部分会详细描述平台应满足的业务需求,如订单创建、查询、修改和取消等;系统设计将介绍整体架构和技术选型;功能实现部分会逐一解析每个...

Global site tag (gtag.js) - Google Analytics