1、MyBatis扩展点plugins

mybatis的扩展是通过拦截器Interceptor来实现的,本质上就是JDK的动态代理,所以它只能对接口进行拦截,我们一步步看一下MyBatis是如何将这些扩展暴露给我们开发者使用的。

SqlSession的创建过程【重点】
mybatis中的SQL都是通过DefaultSqlSession去执行的。

MyBatis 是怎么构造 DefaultSqlSession 的?:【重点】
通过查看源码,得知 MyBatis 是通过 DefaultSqlSessionFactory 来构造 DefaultSqlSession 的。
DefaultSqlSessionFactory#openSessionFromDataSource(ExecutorType, TransactionIsolationLevel, boolean)

/**
* @param ExecutorType 执行器的类型。MyBatis中提供了三种执行器:SIMPLE, REUSE, BATCH。默认的是 SIMPLE
* @param TransactionIsolationLevel 事务隔离级别
* @param autoCommit 是否自动提交事务
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 通过 Environment 获取事务工厂 TransactionFactory。没有指定Environment,则使用 ManagedTransactionFactorytx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 从 TransactionFactory 中获取一个 Transactionfinal Executor executor = configuration.newExecutor(tx, execType); // 从 Configuration 中获取一个新的 Executor。(Configuration 对应的是 mybatis-config.xml 中的配置)return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}

重点看一下 Configuration#newExecutor(Transaction transaction, ExecutorType executorType)

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 默认使用 SimpleExecutorExecutor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction); // 使用 BatchExecutor} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction); // 使用 ReuseExecutor} else {executor = new SimpleExecutor(this, transaction); // 默认使用 SimpleExecutor}if (cacheEnabled) {executor = new CachingExecutor(executor); // 使用 CachingExecutor}executor = (Executor) interceptorChain.pluginAll(executor); // 执行所有的MyBatis拦截器,并返回 Executorreturn executor;
}

至此,我们找到了MyBatis的一个扩展点——拦截器interceptor。

MyBatis Inteceptor是使用JDK的动态代理来实现的,所以它只能对接口进行拦截
里面两个很重要的注解是:@Intercepts、@Signature
@Intercepts : 标记要拦截的方法签名
@Signature : 方法签名,唯一的标记一个接口的方法

通过查看源码,我们还可以知道,MyBatis所有的代理拦截都是通过 InterceptorChain.pluginAll(Object target) 来实现的
至此,我们得到下图:

通过上图可知,Mybatis支持对 Executor 、 StatementHandler 、 ResultSetHandler 和 PameterHandler 进行拦截,也就是说会对这4种对象进行代理。

Executor : 作用是执行SQL语句(所有的sql),并且对事务、缓存等提供统一接口。(在这一层上做拦截的权限会更大)
StatementHandler : 作用是对 statement 进行预处理,并且提供统一的原子的增、删、改、查接口。(如果要在SQL执行前进行拦截的话,拦截这里就可以了)
ResultSetHandler : 作用是对返回结果ResultSet进行处理。
PameterHandler : 作用是对参数进行赋值。

2、源码解读具体实现(以Executor接口为例)

2.1、创建SqlSession时,SqlSessionFactroy会解析mybatis.xml配置文件中的plugins标签,并将Interceptor属性定义的Interceptor放到interceptorChain中;
  // SqlSessionFactoryBuilder.java  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);// 解析mybatis.xml配置文件,并创建DefaultSqlSessionFactoryreturn build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}
  // XMLConfigBuilder.javapublic Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}// 解析mybatis.xml中的各个标签private void parseConfiguration(XNode root) {try {propertiesElement(root.evalNode("properties")); //issue #117 read properties firsttypeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));settingsElement(root.evalNode("settings"));environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631databaseIdProviderElement(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);}}// 解析plugins标签,并把Interceptor放到interceptorChain中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);}}}// Configuration,mybatis文件的抽象类public void addInterceptor(Interceptor interceptor) {interceptorChain.addInterceptor(interceptor);}
2.2、DefaultSqlSessionFactory.openSession()时使用JDK动态代理生成@Signature注解指定的被代理类(包含代理的方法以及方法参数)
  // DefaultSqlSessionFactory.javapublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}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);// 使用Configuration创建Executorfinal Executor executor = configuration.newExecutor(tx, execType, autoCommit);return new DefaultSqlSession(configuration, executor);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
2.3、InterceptorChain生成的具体过程
  // InterceptorChain.javapublic Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}
  // Interceptor的具体实现类(即我们业务上要实现的功能)@Overridepublic Object plugin(Object arg0) {return Plugin.wrap(arg0, this);}
  // Plugin.javapublic static Object wrap(Object target, Interceptor interceptor) {// getSignatureMap获取Interceptor类上的@Intercepts(@Signature)内容Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);// 生成目标类target(Executor.class)的代理类,实现我们需要的plugin功能if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}// 解析实现Interceptor接口的类上定义的@Intercepts(@Signature)内容,获取需要拦截的类和方法。// 例如:@Signature(type = Executor.class, method = "query",  args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);if (interceptsAnnotation == null) { // issue #251throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      }Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();for (Signature sig : sigs) {Set<Method> methods = signatureMap.get(sig.type());if (methods == null) {methods = new HashSet<Method>();signatureMap.put(sig.type(), methods);}try {// sig.type()即Executor.classMethod method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}

3、demo关键步骤

3.1、实现自定义的Interceptor
  // 自定义拦截器@Intercepts({@Signature(type = Executor.class, method = "query",  args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})public class MyTestInterceptor implements Interceptor {private static final String MSG = "octopus route table info is not exit!";@Overridepublic Object intercept(Invocation arg0) throws Throwable {Object obj = null;try {obj = arg0.proceed();} catch (Throwable e) {if (e.getCause() instanceof MySQLSyntaxErrorException) {MySQLSyntaxErrorException ex = (MySQLSyntaxErrorException) e.getCause();System.out.println("====" + ex.getErrorCode());System.out.println("====" + ex.getSQLState());System.out.println("====" + ex.getMessage());System.out.println("====" + ex.getCause());if (MSG.equals(ex.getMessage())) {throw new RouteTableNoExistException();}}}return obj;}@Overridepublic Object plugin(Object arg0) {return Plugin.wrap(arg0, this);}@Overridepublic void setProperties(Properties arg0) {System.out.println("env value: " + arg0.getProperty("names"));}}
3.2、在mybatis.xml中配置plugins
<configuration><plugins><plugin interceptor="com.pinganfu.interceptor.MyTestInterceptor" /></plugins> <environments default="development"><environment id="development"><transactionManager type="MANAGED"><property name="closeConnection" value="false" /></transactionManager><dataSource type="POOLED"><property name="driver" value="${driver}" /><property name="url" value="${jdbcUrl}" /><property name="username" value="${username}" /><property name="password" value="${password}" /></dataSource></environment></environments>   <mappers><mapper resource="mappers/TBATMapper.xml" /></mappers>
</configuration>
3.3、获取SqlSession
  Properties pro = new Properties();try {pro.load(Resources.getResourceAsStream("jdbc.properties"));// 加载mybatis.xml中的pluginsInputStream in = Resources.getResourceAsStream("mybatis.xml");sqlSession = new SqlSessionFactoryBuilder().build(in, "development", pro).openSession();} catch (IOException e) {e.printStackTrace();}

4、mybatis针对各种异常的处理

mybatis通过DefaultSqlSession执行时,会将发生的所有异常统一包装成PersistenceException再抛出,我们可以通过PersistenceException.getCause()获取具体的异常。

  // DefaultSqlSession.javapublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);return result;} catch (Exception e) {// 对执行发生的所有Exception进行wrap之后再抛出throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}
  // ExceptionFactory.javapublic static RuntimeException wrapException(String message, Exception e) {// 将Exception进行统一包装成PersistenceExceptionreturn new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);}

MyBatis源码分析——MyBatis的扩展点(pugins)相关推荐

  1. springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)

    springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三) 例: package com.example.demo.service;import com.exa ...

  2. MyBatis 源码分析-技术分享

    2019独角兽企业重金招聘Python工程师标准>>> MyBatis 源码分析 MyBatis的主要成员 Configuration MyBatis所有的配置信息都保存在Confi ...

  3. Mybatis源码分析: MapperMethod功能讲解

    canmengqian </div><!--end: blogTitle 博客的标题和副标题 --> <div id="navigator"> ...

  4. MyBatis 源码分析 - 配置文件解析过程

    文章目录 * 本文速览 1.简介 2.配置文件解析过程分析 2.1 配置文件解析入口 2.2 解析 properties 配置 2.3 解析 settings 配置 2.3.1 settings 节点 ...

  5. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  6. MyBatis源码分析(一)MyBatis整体架构分析

    文章目录 系列文章索引 一.为什么要用MyBatis 1.原始JDBC的痛点 2.Hibernate 和 JPA 3.MyBatis的特点 4.MyBatis整体架构 5.MyBatis主要组件及其相 ...

  7. mybatis源码分析之事务管理器

    2019独角兽企业重金招聘Python工程师标准>>> 上一篇:mybatis源码分析之Configuration 主要分析了构建SqlSessionFactory的过程中配置文件的 ...

  8. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  9. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

最新文章

  1. 有重叠与无重叠序列之序列检测与序列产生
  2. linux iptables导致httpd网页打不开
  3. android保持数据库,android – 保持Firebase实时数据库中的数据始终保持同步
  4. C# WebProxy POST 或者 GET
  5. Python 字符串操作(string替换、删除、截取、复制、连接、比较、查找、包含、大小写转换、分割等)...
  6. 使用ros发布UVC相机和串口IMU数据
  7. 使用Spring使用Java发送电子邮件– GMail SMTP服务器示例
  8. 一般左边后台点击收缩展开的效果
  9. python常用模块之time和datetime
  10. sqlalchemy mysql_SQLAlchemy(1)
  11. 文件管理搜不到Android 里的文件,Android:在原始文件夹中添加文件后窗口找不到内容容器视图...
  12. Ubuntu 15.04下安装Docker
  13. WIN10桌面上的“此电脑”图标不见了怎么办?
  14. java structs_java深入探究12-框架之Structs
  15. incaseformat病毒专杀:怎么查杀incaseformat病毒?紧急扩散!
  16. 微信小程序:老人疯狂裂变引流视频推广微信小程序
  17. 【渝粤教育】广东开放大学 企业财务报表分析 形成性考核 (26)
  18. 恶意代码分析实战——高级反汇编
  19. 个人开发者与企业开发者的区别
  20. 云服务器突发性能实例t5与通用型服务器的区别?

热门文章

  1. 跨工厂物料状态/特定工厂的物料状态
  2. SAP ByD 期末结账步骤简化(不完整)方法
  3. 释放数据价值,大数据分析如何助力电商获客又增收?
  4. php 规格,PHP 设计模式系列之 specification规格模式_PHP
  5. extract进程 oracle,ogg extract进程stoped问题
  6. 取消计算机关机设置,旧驱动程序教您如何设置和取消计算机的自动关机命令
  7. python怎么处理数据_python panda怎么处理数据
  8. mysql重启电脑提示145_MYSQL错误:Can’t open file: ‘#215;#215;#215;.MYI’ (errno: 145)修复方法...
  9. js怎么取到遍历中的特定值_LeetCode 1028 hard 从先序遍历还原二叉树 Python解题记录...
  10. node与mysql开源_node与mysql的相互使用————node+mysql