::: motto 刘慈欣 面向宇宙,我们只是一粒尘埃, 而面向我们自己,宇宙只是一具尸体。 :::
Mybatis 简介 MyBatis 是一个持久层的 ORM 框架,使用简单,学习成本较低。可以执行自己手 写的SQL语句,比较灵活。但是MyBatis的自动化程度不高,移植性也不高,有 时从一个数据库迁移到另外一个数据库的时候需要自己修改配置,所以称只为半 自动ORM框架
Mybatis 在传统 JDBC 的基础上,主要解决了如下问题:
提供其强大的配置能力,用户可以通过配置 XML 文件很方便地开启或者关闭功能。
允许用户配置数据库连接池,使用连接池来管理数据库连接,减少频繁创建、释放连接资源带来的额外系统开销。
解除 SQL 和 Java 代码的耦合,将 SQL 配置在 XxxMapper.xml 中,极大减轻代码的维护成本。
使用参数映射的方式解决了向 SQL 传参的问题,自动将 Java 对象映射到 SQL 语句中参数占位符。
使用结果集映射的方式解决了 SQL 执行结果转换的问题,自动将 SQL 执行解决映射到 Java 对象,用户可以通过设置 resultType 来自定义输出结果类型。
Mybatis 体系结构 Mybatis 的目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ibatis ├── annotations // 注解定义模块 ├── binding // 绑定 Mapper 接口和 XML 文件,MapperRegistry, MapperProxyFactory 等都定义在这个模块下。 ├── builder // 配置解析模块,将 xml 配置文件以及映射文件解析封装到 Configuration 对象中 ├── cache // 缓存模块,包括一级缓存和二级缓存 ├── cursor // 游标查询模块 ├── datasource // 数据源模块 ├── exceptions // 异常定义 ├── executor // 执行器模块,负责 SQL 的执行以及结果映射处理 ├── io // 资源加载,Resources 处理 ├── jdbc // JDBC 相关操作 ├── lang ├── logging // 日志操作模块 ├── mapping Mapper 相关的 Java 类,如 MappedStatement, ResultMap, ParameterMap 等 ├── parsing // 解析器模块 ├── plugin // 插件模块 ├── reflection // 反射模块 ├── scripting // 动态 SQL 解析 ├── session // SQL 会话 ├── transaction // 事务 ├── type // 内置的类型处理器 └── util // 工具类
整个 Mybatis 分为三层:
1. 基础支撑层 基础支撑负责 Mybatis 的最基础的功能支撑。主要负责处理以下事务:
解析并加载 Mybatis 的全局配置 xml 文档(mybatis-config.xml
)。
解析 Mapper xml 文件, 将 Mapper xml 文件和 Mapper 接口绑定,生成 MappedStatement 对象。
解析 Mapper 接口上的注解,并生成 MappedStatement 对象。
Mybatis 缓存管理,连接池管理,事务管理。
3. 数据处理层 处理一次 SQL 执行的全过程流程。这里以查询操作为例,其主要执行流程就 2 步:
通过 Statement ID 去全局配置 configuration 拿到 MappedStatement 对象 1 MappedStatement ms = configuration.getMappedStatement(statement);
通过执行器去执行查询操作并返回。 1 return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
执行的具体流程是: 参数解析 -> SQL 解析 -> SQL 执行 -> 结果集映射。这其中还有很多细节处理,比如缓存的调用,事务管理,连接池调用等,这个在后面的流程中会一一分析。
3. 接口层 其核心是 SqlSession 接口,它暴露了 Mybatis 与上层应用交互的接口,主要就是 CRUD 接口,以及事务的提交,回滚等接口。除此之外,SqlSession 还给用户提供了一个 getConfiguration()
来获取 Mybatis 全局配置。
SqlSession 的接口调用支持 2 种方式:
第一种是通过 Statement ID 来进行调用, 在 Mybatis 中每一个 Statement 对象都代表着一次 SQL 执行调用。
1 2 3 SqlSession session = sqlSessionFactory.openSession();Object o = session.selectOne("org.mybatis.demo.mapper.UserMapper.selectUser" );System.out.println(o);
每个 Statement 对象都有一个唯一的 ID, 其生成规则就是在解析 Mapper xml 文件的时候,先拿到 <mapper>
的 namespace 属性,而 mapper 的每一个子节点都有一个唯一的 id 属性, 这样串起来就得到 Statement 的 ID:
1 2 3 4 5 6 <mapper namespace ="org.mybatis.demo.mapper.UserMapper" > <select id ="selectUser" resultType ="org.mybatis.demo.domain.User" > select * from user limit 0,1 </select > </mapper >
第二种调用方式是通过 Mapper 接口来调用。因为毕竟 Statement ID 并不好找,使用起来语义性不强,通常 IDE 也没有提示。所以通常我们用得多的还是第二种方式。 先通过 SqlSession 对象去获取 Mapper 代理对象,然后通过代理对象调用 Mapper 的 API。
1 2 3 4 SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.selectUser();System.out.println(user);
解析器设计 Mybatis 的解析器主要有 5 种:
XMLConfigBuilder: 全局配置文件(mybatis-config.xml
) 解析器,主要解析 properties 配置文档,全局设置,插件,TypeAliases 等。
XMLMapperBuilder: Mapper XML 文件解析器,负责解析二级缓存,ResultMap 等信息。
MapperBuilderAssistant: Mapper 解析器助手工具,协助 XMLMapperBuilder 解析二级缓存,ResultMap 等,创建 MappedStatement 对象。
XMLStatementBuilder: CURD 元素解析器。
XMLScriptBuilder: SQL 语句解析器,解析动态 SQL(${}
) 和静态 SQL 语句(#{}
),解析成一个一个的 SqlNode 对象。
SqlSourceBuilder: SQL 最终解析器,将 SqlNode 对象整合成最终的 SQL 语句。
解析器的入口在 XMLConfigBuilder.parseConfiguration()
方法,该方法执行完之后将解析到的数据打包放入 Configuration 对象, Configuration 对象将会作为参数传入 SqlSession 和 MappedStatement 等对象中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); Properties settings = settingsAsProperties(root.evalNode("settings" )); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(settings); environmentsElement(root.evalNode("environments" )); databaseIdProviderElement(root.evalNode("databaseIdProvider" )); typeHandlerElement(root.evalNode("typeHandlers" )); mapperElement(root.evalNode("mappers" )); } catch (Exception e) { throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
缓存设计 Mybatis 的缓存分为一级缓存和二级缓存。这两缓存的作用域和实现方式都有所有不同。
一级缓存
作用范围: 仅限当前 SqlSession
缓存实现: 实现简单,直接用 PerpetualCache 对象(底层实现就是一个 HashMap)。
一级缓存的生命周期很短,只有在同一个 session 中执行相同的 SQL 语句的时候才会命中一级缓存,当执行增,删,改,之类的写操作的时候,一级缓存会清空。
1 2 3 4 5 6 7 8 9 public int update (MappedStatement ms, Object parameter) throws SQLException{ ErrorContext.instance().resource(ms.getResource()).activity("executing an update" ).object(ms.getId()); if (closed) { throw new ExecutorException ("Executor was closed." ); } clearLocalCache(); return doUpdate(ms, parameter); }
而且,当执行事务提交或者回滚的时候,一级缓存也会清空。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public void commit (boolean required) throws SQLException{ if (closed) { throw new ExecutorException ("Cannot commit, transaction is already closed" ); } clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } } public void rollback (boolean required) throws SQLException{ if (!closed) { try { clearLocalCache(); flushStatements(true ); } finally { if (required) { transaction.rollback(); } } } }
二级缓存
作用范围: 应用级别,在当前应用范围内都是有效的。
实现方式: 基于责任链和装饰器模式实现。
Mybatis 二级缓存的实现使用了责任链 + 装饰器设计模式。缓存的具体实现类是 PerpetualCache , 其他缓存是通过装饰器模式对 PerpetualCache 一层包一层包装,每一层负责不同的责任。缓存调用的时候则是通过一层一层的委托调用的方式。 比如日志缓存(LoggingCache
)只负责记录日志,同步缓存(SynchronizedCache
) 只是在读写方法上加上同步锁…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class LoggingCache implements Cache { public Object getObject (Object key) { requests++; final Object value = delegate.getObject(key); if (value != null ) { hits++; } if (log.isDebugEnabled()) { log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio()); } return value; } } public class SynchronizedCache implements Cache { ... public synchronized void putObject (Object key, Object object) { delegate.putObject(key, object); } public synchronized Object getObject (Object key) { return delegate.getObject(key); } ... }
缓存的工作流程如下图所示:
代码实现在 CacheBuilder.build()
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public Cache build () { setDefaultImplementations(); Cache cache = newBaseCacheInstance(implementation, id); setCacheProperties(cache); if (PerpetualCache.class.equals(cache.getClass())) { for (Class<? extends Cache > decorator : decorators) { cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } cache = setStandardDecorators(cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { cache = new LoggingCache (cache); } return cache; } private Cache setStandardDecorators (Cache cache) { try { MetaObject metaCache = SystemMetaObject.forObject(cache); if (size != null && metaCache.hasSetter("size" )) { metaCache.setValue("size" , size); } if (clearInterval != null ) { cache = new ScheduledCache (cache); ((ScheduledCache) cache).setClearInterval(clearInterval); } if (readWrite) { cache = new SerializedCache (cache); } cache = new LoggingCache (cache); cache = new SynchronizedCache (cache); if (blocking) { cache = new BlockingCache (cache); } return cache; } catch (Exception e) { throw new CacheException ("Error building standard cache decorators. Cause: " + e, e); } }
::: tip 说明 为了保持 session 之间的事务隔离性,避免脏读,Mybatis 在实现二级缓存的时候,会为每个 session 建立一个缓存暂存区。 在操作缓存的时候都是先操作缓存暂存区,事务提交或者回滚之后,在把缓存从暂存区移动到二级缓存。 :::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public <E> List<E> query (MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null ) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null ) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null ) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
SQL 语句解析 Mybatis SQL 语句解析的主要流程:
静态 SQL 的解析比较简单,直接把参数传入进去就可以得到一条具体的 SQL 了。
1 2 3 public BoundSql getBoundSql (Object parameterObject) { return sqlSource.getBoundSql(parameterObject); }
而动态 SQL 解析需要从根节点一直往下执行,最终得到完整的 SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public BoundSql getBoundSql (Object parameterObject) { DynamicContext context = new DynamicContext (configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder (configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); context.getBindings().forEach(boundSql::setAdditionalParameter); return boundSql; } public boolean apply (DynamicContext context) { contents.forEach(node -> node.apply(context)); return true ; }
在具体解析某一条 SQL 语句的时候,Mybatis 是将这条 SQL 语句解析成一颗 SqlNode 树。
1 2 3 4 5 6 7 8 <select id ="selectUser" resultType ="org.mybatis.demo.domain.User" > select id,name,from user <where > <if test ="id > 0" > and id=#{id} </if > </where > </select >
解析出来的 SqlNode 树如下图所示
SQL 语句的执行 这一部分应该是属于 Mybatis 核心了,我们通过一个完整的使用实例来解析这个过程:
1 2 3 4 5 6 7 8 9 10 11 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" );SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream);SqlSession session = sqlSessionFactory.openSession();UserMapper mapper = session.getMapper(UserMapper.class);User user = mapper.selectUser();System.out.println(user);
主体分为 4 个流程
解析 XML 配置,构建 Configuration
对象,传入 DefaultSqlSessionFactory 对象。 1 2 3 public SqlSessionFactory build (Configuration config) { return new DefaultSqlSessionFactory (config); }
创建 SqlSession 会话
获取 Mapper 代理对象
调用 Mapper 接口执行 CRUD 方法
配置的解析流程前面已经讲过,不再赘述,接下来重点看下后面三个流程。
创建 SqlSession 会话流程 具体实现在 DefaultSqlSessionFactory.openSessionFromDataSource()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private SqlSession openSessionFromDataSource (ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null ; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession (configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } public Executor newExecutor (Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor (this , transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor (this , transaction); } else { executor = new SimpleExecutor (this , transaction); } if (cacheEnabled) { executor = new CachingExecutor (executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
创建 SqlSession 的过程:
创建事务管理器
创建 SQL 执行器,如果启用了缓存,再用 CachingExecutor 包一层。如果匹配了插件,则使用 InterceptorChain 生成执行器的代理对象。
创建 SqlSession(默认使用 DefaultSqlSession)对象,SqlSession 对象中包含了全局配置 Configuration 对象和执行器 Executor 对象。
三类执行器:
SimpleExecutor: 每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
ReuseExecutor: 使用一个 statementMap
来缓存当前 SqlSession 的所有 Statement 对象。每次执行的时候先用 SQL 作为 Key statementMap
查询一下,如果找到了就返回重复使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private Statement prepareStatement (StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); if (hasStatementFor(sql)) { stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; }
BatchExecutor: 执行 update(没有select,JDBC 批处理不支持select),将所有 sql 都 addBatch()
到批处理中,等待统一执行 executeBatch()
,它缓存了多个Statement对象, 每个Statement对象都是 addBatch()
完毕后,等待逐一执行 executeBatch()
批处理。与JDBC批处理相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 public int doUpdate (MappedStatement ms, Object parameterObject) throws SQLException { final Configuration configuration = ms.getConfiguration(); final StatementHandler handler = configuration.newStatementHandler(this , ms, parameterObject, RowBounds.DEFAULT, null , null ); final BoundSql boundSql = handler.getBoundSql(); final String sql = boundSql.getSql(); final Statement stmt; if (sql.equals(currentSql) && ms.equals(currentStatement)) { int last = statementList.size() - 1 ; stmt = statementList.get(last); applyTransactionTimeout(stmt); handler.parameterize(stmt); BatchResult batchResult = batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); currentSql = sql; currentStatement = ms; statementList.add(stmt); batchResultList.add(new BatchResult (ms, sql, parameterObject)); } handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; } public List<BatchResult> doFlushStatements (boolean isRollback) throws SQLException { try { List<BatchResult> results = new ArrayList <>(); if (isRollback) { return Collections.emptyList(); } for (int i = 0 , n = statementList.size(); i < n; i++) { Statement stmt = statementList.get(i); applyTransactionTimeout(stmt); BatchResult batchResult = batchResultList.get(i); try { batchResult.setUpdateCounts(stmt.executeBatch()); MappedStatement ms = batchResult.getMappedStatement(); List<Object> parameterObjects = batchResult.getParameterObjects(); KeyGenerator keyGenerator = ms.getKeyGenerator(); if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) { Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator; jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects); } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { for (Object parameter : parameterObjects) { keyGenerator.processAfter(this , ms, stmt, parameter); } } closeStatement(stmt); } catch (BatchUpdateException e) { StringBuilder message = new StringBuilder (); message .append(batchResult.getMappedStatement().getId()) .append(" (batch index #" ) .append(i + 1 ) .append(")" ) .append(" failed." ); if (i > 0 ) { message .append(" " ) .append(i) .append(" prior sub executor(s) completed successfully, but will be rolled back." ); } throw new BatchExecutorException (message.toString(), e, results, batchResult); } results.add(batchResult); } return results; } finally { for (Statement stmt : statementList) { closeStatement(stmt); } currentSql = null ; statementList.clear(); batchResultList.clear(); } }
获取 Mapper 代理对象流程 进入 sqlSession.getMapper()
方法,会发现调的是 Configration 对象的 getMapper
方法(前面已经知道所有的 Mapper xml 解析之后都封装到 Configuration 对象中了):
1 2 3 4 5 6 7 8 9 10 public <T> T getMapper (Class<T> type) { return configuration.getMapper(type, this ); } public <T> T getMapper (Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
继续进入 mapperRegistry.getMapper()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 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); } }
进入核心方法 MapperProxyFactory.newInstance()
方法:
1 2 3 4 5 6 7 8 9 10 11 public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy <>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } @SuppressWarnings("unchecked") protected T newInstance (MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class [] {mapperInterface}, mapperProxy); }
Mapper 方法执行流程 上面已经知道 SqlSession.getMapper()
返回的是动态代理类 MapperProxy
。那么调用 Mapper 接口的所有的方法都调用 MapperProxy.invoke()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class MapperProxy <T> implements InvocationHandler , Serializable { ... public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethodInvoker cachedInvoker (Method method) throws Throwable { try { return MapUtil.computeIfAbsent( methodCache, method, m -> { if (m.isDefault()) { try { if (privateLookupInMethod == null ) { return new DefaultMethodInvoker (getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker (getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException (e); } } else { return new PlainMethodInvoker ( new MapperMethod (mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } } ... }
cachedInvoker()
方法会返回一个 MapperMethodInvoker 对象,它包含了 MapperMethod 对象,它的 invoke()
方法实现很简单,其实就是调用了 MapperMethod.execute()
方法:
1 2 3 4 public Object invoke (Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); }
继续进入 MapperMethod.execute()
方法,这个方法判断你当前执行的方式是增删改查哪一种,并通过 SqlSession 执行相应的操作并返回结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public Object execute (SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break ; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break ; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break ; } case SELECT: 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); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break ; case FLUSH: result = sqlSession.flushStatements(); break ; default : 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; }
sqlSession.selectOne
方法会会调到 DefaultSqlSession.selectList 的方法。这个方法获取了获取了 MappedStatement 对象,并最终调用了 Executor.query
方法:
1 2 3 4 5 6 7 8 9 10 11 12 private <E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
然后,通过一层一层的调用,先会去缓存中去查询,如果没有命中就会调用 BaseExecutor.queryFromDatabase
方法从数据库读取(前面缓存模块已经分析过), 最终会来到 Executor.doQuery
方法, 这是一个抽象方法,每个 Executor 都会有自己的实现,这里以 SimpleExecutor 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
StatementHandler 是一个接口,它的继承结构如如下:
我们通常是直接使用 RoutingStatementHandler 来创建 StatementHandler , 你可以把它理解为 Statement 的简单工厂,根据不同的 StatementType
创建不同的 StatementHandler 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public RoutingStatementHandler (Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case PREPARED: delegate = new PreparedStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; case CALLABLE: delegate = new CallableStatementHandler (executor, ms, parameter, rowBounds, resultHandler, boundSql); break ; default : throw new ExecutorException ("Unknown statement type: " + ms.getStatementType()); } }
这里我们进入 PreparedStatementHandler 的 query
方法看看最终的实现:
1 2 3 4 5 6 7 8 public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }
至此,一个 Mapper 接口的调用流程就完成了。
Mybatis 插件原理 Mybatis 允许你在已映射语句执行过程中的某一点进行拦截调用。这个调用就是通过 Mybatis 插件来完成的,所以 Mybatis 的插件其实说白就是一组拦截器。
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor(执行器) (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler(参数处理) (getParameterObject, setParameters)
ResultSetHandler(结果集处理) (handleResultSets, handleOutputParameters)
StatementHandler(SQL语句构建) (prepare, parameterize, batch, update, query)
先来看一个插件 Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Intercepts({@Signature( type = Executor.class, // 拦截对象类型,只能是 Executor,ParameterHandler,ResultSetHandler,StatementHandler 中的一个 method = "query", // 拦截的方法名称,并不是所有的方法都允许拦截 args = { // 拦截方法的参数,考虑到方法重载,不同的参数对应不同的方法签名 MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class })}) public class TestPlugin implements Interceptor { @Override public Object intercept (Invocation invocation) throws Throwable { System.out.println("===== 测试拦截器 ======" ); Object[] args = invocation.getArgs(); MappedStatement mappedStatement = (MappedStatement) args[0 ]; System.out.println(mappedStatement.getBoundSql(args[1 ]).getSql()); return invocation.proceed(); } @Override public Object plugin (Object target) { return Interceptor.super .plugin(target); } @Override public void setProperties (Properties properties) { Interceptor.super .setProperties(properties); } }
这里两点需要注意的地方:
type 参数只能是 Executor,ParameterHandler,ResultSetHandler,StatementHandler 中的一个,写其他的不会生效(后面源码会分析为什么不生效)。
method 是能设置上面允许使用插件来拦截的方法。
注意参数的类型和个数要跟你需要拦截的方法一一对应上,尤其是出现方法重载的情况,可能出现实际拦截的方法跟你想要拦截的方法不一致。
写好插件之后,你还需要在 mybatis-config.xml
文档中配置好才会生效:
1 2 3 <plugins > <plugin interceptor ="org.mybatis.demo.plugins.TestPlugin" > </plugin > </plugins >
插件的解析 Mybatis 插件的解析实现在 XMLConfigBuilder.pluginElement() 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void pluginElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor" ); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } } public void addInterceptor (Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); }
InterceptorChain ,是一个拦截器链,我们来看下它的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList <>(); public Object pluginAll (Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor (Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors () { return Collections.unmodifiableList(interceptors); } }
插件的应用 IntrceptorChain.pluginAll()
是用来将拦截器匹配到拦截对象的,Executor,ParameterHandler,ResultSetHandler,StatementHandler 在创建的时候都调用了该方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public ParameterHandler newParameterHandler (MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler (Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler (executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } public StatementHandler newStatementHandler (Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler (executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } public Executor newExecutor (Transaction transaction, ExecutorType executorType, boolean autoCommit) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor (this , transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor (this , transaction); } else { executor = new SimpleExecutor (this , transaction); } if (cacheEnabled) { executor = new CachingExecutor (executor, autoCommit); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
插件的实现原理也非常简单,就是为拦截对象生成一个代理对象,具体实现是通过 Plugin 工具类实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static Object wrap (Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0 ) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin (target, interceptor, signatureMap)); } return target; }
插件的调用 所有被拦截成功的对象都会生成代理对象并重新赋值给该对象,所以该对象在执行任何方法的时候都会先调用 Plugin (实现了 InvocationHandler 接口)的 invoke
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); } }
如果匹配拦截器成功,就会调用拦截器的调用拦截器的 intercept()
方法,并传入 Invocation 对象,而我们在处理完拦截任务之后, 可以通过 Invocation.procced()
来调用原始方法(被代理对象方法)
public Object intercept(Invocation invocation) throws Throwable
{
System.out.println("===== 测试拦截器 ======");
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
// 打印 SQL 语句
System.out.println(mappedStatement.getBoundSql(args[1]).getSql());
// 调用原始对象方法
return invocation.proceed();
}
总结 Mybatis 工作流程
SqlSessionFactoryBuilder 解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;
解析完这些配置后会生成一个 Configration 对象,这个对象中包含了 MyBatis 需要的所有配置,然后会用这个 Configration 对象创建一个 SqlSessionFactory 对象;
调用 SqlSessionFactory.openSesison
创建 SqlSession 对象,这个对象包含了一个事务管理对象和一个 SQL 执行器(Executor),然后就可以通过 SqlSession 执行各种 CRUD 方法了;
调用 SqlSession.getMapper
方法,获得 Mapper 接口的动态代理对象 MapperProxy;
调用 Mapper 的 API 其实就是调用 MapperProxy 的 invoke
方法,然后依次层层调用 MapperMethod.execute()
方法 => 调用 SqlSession.selectOne()
方法 => 调用 Executor.query()
…
调用 BaseExecutor.doQuery()
方法,创建一个 StatementHandler 对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。 然后调用 StatementHandler 预编译参数以及设置参数值,使用 ParameterHandler 来给 SQL 设置参数。
调用 StatementHandler.query()
方法调用 JDBC 底层增删查改 API,获取执行结果后使用 ResultSetHandler 对结果集进行封装转换,返回处理后的结果集。 MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参;
对应的源码调用流程:
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
org.apache.ibatis.session.Configuration#newExecutor(Transaction,ExecutorType)
org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
org.apache.ibatis.binding.MapperRegistry#getMapper
org.apache.ibatis.binding.MapperProxyFactory#newInstance(MapperProxy<T>
)
org.apache.ibatis.binding.MapperProxy#invoke
org.apache.ibatis.binding.MapperMethod#execute
org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(String, Object)
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(String, Object, RowBounds, ResultHandler)
org.apache.ibatis.executor.CachingExecutor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)
org.apache.ibatis.executor.BaseExecutor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
org.apache.ibatis.executor.SimpleExecutor#doQuery
org.apache.ibatis.executor.statement.PreparedStatementHandler#query
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
源码解读工程地址在:https://gitee.com/blackfox/mybatis-3.5.12 。基于 Mybatis 最新版 v3.5.12,核心流程都加了中文注释,里面还附带的 demo 工程,方便学习者调试。