本篇博客将主要对 mybatis 整体介绍,包括 mybatis 的项目结构,执行的主要流程,初始化流程,API 等各模块进行简单的串联,让你能够对 mybatis 有一个整体的把握。另外在 mybatis 源码的阅读过程中,如果不想写 demo 可以直接使用项目中的单元测试;

一、mybatis 结构介绍

mybatis的主要功能和使用 demo,在网上已经有很多了我就不再啰嗦了,同时 官方文档 也非常的详细;另外 mybatis 中使用了多种设计模式,包括建造者、动态代理、策略、装饰器模式等,在查看源码的时候,最好先对这些设计模式有一定的了解;

其中 mybatis 的模块结构如下:

mybatis 的执行流程如下:

  • 首先通过 Java API 或者 XML 配置完成初始化,最终所有的配置都在 Configuration 类中维护;
  • 然后通过 SqlSessionFactory 得到 SqlSession,这里 SqlSession 就是 mybatis 的顶层 API 了,主要通过他完成数据库的增删改查等操作;
  • 然后 SqlSession 将具体的操作委托给 Executor 执行,Executor 就是 mybatis 的调度核心了,主要职责有 SQL 语句生成、一二级缓存维护和事务的相关操作;
  • 然后 Executor 将数据库相关的操作委托给 StatementHandler,StatementHandler 中完成了 mybatis 最核心的工作,包括参数绑定,指定 SQL 语句,结果集映射等;

具体过程如图所示:

二、初始化

mybatis 中包含了很多的配置项,具体每一项的讲解 官网 也很详细,其结构大致如下:(另外正如上面说的 mybatis 的配置项最后都由 Configuration 类维护,这其实就是外观模式)

  1. configuration(配置)
  2. properties(属性)
  3. settings(设置)
  4. typeAliases(类型别名)
  5. typeHandlers(类型处理器)
  6. objectFactory(对象工厂)
  7. plugins(插件)
  8. environments(环境配置)
  9. environment(环境变量)
  10. transactionManager(事务管理器)
  11. dataSource(数据源)
  12. mappers(映射器)

1. Java API 初始化

Java API 初始化的方式虽然不常用,但是相较于 XML 的方式可以更清楚的看到 Configuration 的构成,其示例如下:

  1. PooledDataSource dataSource = new PooledDataSource();
  2. dataSource.setDriver("com.mysql.cj.jdbc.Driver");
  3. dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT");
  4. dataSource.setUsername("root");
  5. dataSource.setPassword("root");
  6. TransactionFactory transactionFactory = new JdbcTransactionFactory();
  7. Environment environment = new Environment("development", transactionFactory, dataSource);
  8. Configuration configuration = new Configuration(environment);
  9. configuration.addMapper(UserMapper.class);
  10. sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

2. XML 配置初始化

相交于 Java API 的方式,XML 配置初始化,必然会多出 XML 的解析部分;代码如下:

  1. String resource = "org/apache/ibatis/builder/MapperConfig.xml";
  2. Reader reader = Resources.getResourceAsReader(resource);
  3. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
  4. SqlSession sqlSession = sqlSessionFactory.openSession();

下面是一个相对完整的配置示例:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
  3. <configuration>
  4. <properties resource="org/apache/ibatis/databases/blog/blog-derby.properties"/>
  5. <settings>
  6. <setting name="cacheEnabled" value="true"/>
  7. <setting name="lazyLoadingEnabled" value="false"/>
  8. ...
  9. </settings>
  10. <typeAliases>
  11. <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
  12. <typeAlias alias="Blog" type="org.apache.ibatis.domain.blog.Blog"/>
  13. ...
  14. </typeAliases>
  15. <typeHandlers>
  16. <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.CustomStringTypeHandler"/>
  17. </typeHandlers>
  18. <objectFactory type="org.apache.ibatis.builder.ExampleObjectFactory">
  19. <property name="objectFactoryProperty" value="100"/>
  20. </objectFactory>
  21. <plugins>
  22. <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
  23. <property name="pluginProperty" value="100"/>
  24. </plugin>
  25. </plugins>
  26. <environments default="development">
  27. <environment id="development">
  28. <transactionManager type="JDBC">
  29. <property name="" value=""/>
  30. </transactionManager>
  31. <!--<dataSource type="UNPOOLED">-->
  32. <dataSource type="POOLED">
  33. <property name="driver" value="${driver}"/>
  34. <property name="url" value="${url}"/>
  35. <property name="username" value="${username}"/>
  36. <property name="password" value="${password}"/>
  37. </dataSource>
  38. </environment>
  39. </environments>
  40. <mappers>
  41. <mapper resource="org/apache/ibatis/builder/AuthorMapper.xml"/>
  42. <mapper resource="org/apache/ibatis/builder/BlogMapper.xml"/>
  43. ...
  44. </mappers>
  45. </configuration>

其解析的流程如下:

主要代码如下:

  1. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  2. try {
  3. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  4. return build(parser.parse());
  5. } catch (Exception e) {
  6. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  7. } finally {
  8. ErrorContext.instance().reset();
  9. try {
  10. inputStream.close();
  11. } catch (IOException e) { }
  12. }
  13. }
  14. public SqlSessionFactory build(Configuration config) {
  15. return new DefaultSqlSessionFactory(config);
  16. }

从上面的代码和流程图中可以看到,XML 初始化的主要流程被封装到了 XMLConfigBuilder 当中;主要的代码逻辑如下:

  1. public Configuration parse() {
  2. if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); }
  3. parsed = true;
  4. parseConfiguration(parser.evalNode("/configuration"));
  5. return configuration;
  6. }
  7. private void parseConfiguration(XNode root) {
  8. try {
  9. //issue #117 read properties first
  10. propertiesElement(root.evalNode("properties"));
  11. Properties settings = settingsAsProperties(root.evalNode("settings"));
  12. loadCustomVfs(settings);
  13. loadCustomLogImpl(settings);
  14. typeAliasesElement(root.evalNode("typeAliases"));
  15. pluginElement(root.evalNode("plugins"));
  16. objectFactoryElement(root.evalNode("objectFactory"));
  17. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  18. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  19. settingsElement(settings);
  20. // read it after objectFactory and objectWrapperFactory issue #631
  21. environmentsElement(root.evalNode("environments"));
  22. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  23. typeHandlerElement(root.evalNode("typeHandlers"));
  24. mapperElement(root.evalNode("mappers"));
  25. } catch (Exception e) {
  26. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  27. }
  28. }

三、SqlSession 使用方式

1. 直接指定 MappedStatement

  1. try (SqlSession session = sqlMapper.openSession()) {
  2. Author author = session.selectOne("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor", new Author(101));
  3. }

这种方式通过 namespace + sqlId 的方式直接指定 MappedStatement;这种方式因为直接编写字符串和强类型转换,既不安全也稍显麻烦,所以现在已经不推荐使用了;

  1. @Override
  2. public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
  3. try {
  4. MappedStatement ms = configuration.getMappedStatement(statement);
  5. Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
  6. registerCursor(cursor);
  7. return cursor;
  8. } catch (Exception e) {
  9. throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  10. } finally {
  11. ErrorContext.instance().reset();
  12. }
  13. }

2. 动态代理 Mapper 的方式

  1. try (SqlSession session = sqlMapper.openSession()) {
  2. AuthorMapper mapper = session.getMapper(AuthorMapper.class);
  3. Author author = mapper.selectAuthor(500);
  4. }

这种方式不经避免了以上的问题,同时也能够使用注解的方式编写 sql,而且可以使用 IDE 提示;现在一般都推荐使用这种方式;但是其最终也是调用了上面的接口;

首先在初始化的时候通过 bindMapperForNamespace,注册对应的 Mapper(要求namespace和Mapper的全限定名保持一致);

  1. // XMLMapperBuilder
  2. private void bindMapperForNamespace() {
  3. String namespace = builderAssistant.getCurrentNamespace();
  4. if (namespace != null) {
  5. Class<?> boundType = null;
  6. try {
  7. boundType = Resources.classForName(namespace);
  8. } catch (ClassNotFoundException e) { //ignore, bound type is not required }
  9. if (boundType != null) {
  10. if (!configuration.hasMapper(boundType)) {
  11. configuration.addLoadedResource("namespace:" + namespace);
  12. configuration.addMapper(boundType);
  13. }
  14. }
  15. }
  16. }
  17. // MapperRegistry
  18. public <T> void addMapper(Class<T> type) {
  19. if (type.isInterface()) {
  20. if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); }
  21. boolean loadCompleted = false;
  22. try {
  23. knownMappers.put(type, new MapperProxyFactory<>(type)); // 添加代理工厂
  24. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  25. parser.parse();
  26. loadCompleted = true;
  27. } finally {
  28. if (!loadCompleted) {
  29. knownMappers.remove(type);
  30. }
  31. }
  32. }
  33. }

使用的时候,通过 class 类名获取 MapperProxyFactory 代理工厂,制造一个新的 Mapper 代理(注意这里时每次都要生成一个代理类,因为其中包含了 SqlSession,而 SqlSession 是线程不安全的所以不能缓存,但是我觉得这里任然是可以优化的,有兴趣你可以自己尝试一下);

  1. try (SqlSession session = sqlMapper.openSession()) {
  2. AuthorMapper mapper = session.getMapper(AuthorMapper.class); // 代理类
  3. }
  4. // MapperRegistry
  5. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  6. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  7. if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); }
  8. try {
  9. return mapperProxyFactory.newInstance(sqlSession); // 创建代理对象
  10. } catch (Exception e) {
  11. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  12. }
  13. }
  14. // MapperProxyFactory
  15. public T newInstance(SqlSession sqlSession) {
  16. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  17. return newInstance(mapperProxy);
  18. }
  19. // MapperProxy
  20. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  21. try {
  22. if (Object.class.equals(method.getDeclaringClass())) { // 从Object中继承的方法
  23. return method.invoke(this, args);
  24. } else if (method.isDefault()) { // 有默认实现的接口方法
  25. return invokeDefaultMethod(proxy, method, args);
  26. }
  27. } catch (Throwable t) {
  28. throw ExceptionUtil.unwrapThrowable(t);
  29. }
  30. final MapperMethod mapperMethod = cachedMapperMethod(method);
  31. return mapperMethod.execute(sqlSession, args); // 然后由 MapperMethod 执行,这里使用策略模式,后面还会详细讲解
  32. }

总结

  • SqlSession 是线程不安全的,所以在示例代码中每次使用都会将其关闭?

    在 mybatis 中还有一个类 SqlSessionManager 里面有一个 ThreadLocal 用来管理 SqlSession,在 Spring 中也同样是用 SqlSessionHolder 来管理的,所以并不会每次都创建一个新的 SqlSession;

  • 以上内容只是大致将了 mybatis 的主要结构,后面的章节还会分模块进行讲解;

另外本文主要参考了《MyBatis技术内幕》,有兴趣的可以自行查看;

mybatis 源码分析(一)框架结构概览的更多相关文章

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

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

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

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

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

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

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

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

  5. MyBatis源码分析(3)—— Cache接口以及实现

    @(MyBatis)[Cache] MyBatis源码分析--Cache接口以及实现 Cache接口 MyBatis中的Cache以SPI实现,给需要集成其它Cache或者自定义Cache提供了接口. ...

  6. MyBatis源码分析(2)—— Plugin原理

    @(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...

  7. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

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

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

  9. Mybatis源码分析-BaseExecutor

    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...

  10. Mybatis源码分析-StatementHandler

    承接前文Mybatis源码分析-BaseExecutor,本文则对通过StatementHandler接口完成数据库的CRUD操作作简单的分析 StatementHandler#接口列表 //获取St ...

随机推荐

  1. 嵊州D3T2 福尔贝斯太太的快乐夏日 summer

    宗教,或是无节制的自由主义,是致人腐化的毒剂. 现在,一个人要经历 n 个事件,编号为 1 ∼ n. 经历 x 号事件,他的危险值就会增加 x. 一开始他的危险值是 0. 当一个人的危险值大于 0 且 ...

  2. SQL Server 根据日期分组、 根据时间段分组(每三个小时一组)

    所用数据表: 一.根据日期分组 1. 使用convert() 函数方式 --根据年月 ),CreatTime,)日期,COUNT(*) 次数,sum(Money)总数 from Orders ),Cr ...

  3. 基于SpringCloud的Microservices架构实战案例

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  4. 基于webpack4+vue-cli3项目的换肤功能

    起因 最近因公司需求,需要实现主题换肤功能,不仅仅是颜色的更改,还需要包括图片,字体等文件等更换,因此在百度里各种实现方案后,决定根据scss+style-loader/useable做换肤. 项目开 ...

  5. MyBatis 一对多映射

    From<MyBatis从入门到精通> <!-- 6.1.2.1 collection集合的嵌套结果映射 和association类似,集合的嵌套结果映射就是指通过一次SQL查询将所 ...

  6. Netty-新连接接入源码解读

    本片博文来看Netty的服务端是如何处理新连接接入问题的 什么是新连接接入?以及新连接接入前,Netty处于什么状态 netty的服务端NioServerSocketChannel初始化,注册在Bos ...

  7. [记录]Linux下大批量添加用户的方法

    Linux系统提供了创建大量用户的工具,可以让您立即创建大量用户,方法如下: (1)先编辑一个文本用户文件. 每一列按照/etc/passwd密码文件的格式书写,要注意每个用户的用户名.UID.宿主目 ...

  8. 【原创】一个shell脚本记录(实现rsync生产文件批量迁移功能)

    #!/bin/bash #Date:2018-01-08 #Author:xxxxxx #Function:xxxxxx #Change:2018-01-17 # #设置忽略CTRL+C信号 trap ...

  9. 【带着canvas去流浪(12)】用Three.js制作简易的MARVEL片头动画(上)

    目录 一. 大作业说明 二.基本思路 三.视频纹理表面修复--UV映射 3.1 问题描述 3.2 纹理贴图的基本原理-UV映射 3.3 关键示例代码 四.小结 示例代码托管在:http://www.g ...

  10. 洛谷P1640 [SCOI2010]连续攻击游戏 题解

    题目链接: https://www.luogu.org/problemnew/show/P1640 分析: 这道题用二分图来解决即可.应该可以作为网络流中的模板题来食用, 每一个武器有两个属性,但是只 ...