MyBatis 使用了哪些设计模式

  • 1)简介
  • 2)工厂模式
  • 3)建造者模式(Builder)
  • 4)单例模式
  • 5)适配器模式
  • 6)代理模式
  • 7)模板方法模式
  • 8)装饰器模式

1)简介

MyBatis 的前身是 IBatis,IBatis 是由 Internet 和 Abatis 组合而成,其目的是想当做互联网的篱笆墙,围绕着数据库提供持久化服务的一个框架,2010 年正式改名为 MyBatis。它是一款优秀的持久层框架,支持自定义 SQL、存储过程及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作,还可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Ordinary Java Object,普通 Java 对象)为数据库中的记录。

关于 MyBatis 的介绍与使用,官方已经提供了比较详尽的中文参考文档,可点击这里查看,本文简单讲解MyBatis 使用到的设计模式。

注意:本课时使用的 MyBatis 源码为 3.5.5。

2)工厂模式

工厂模式想必都比较熟悉,它是 Java 中最常用的设计模式之一。工厂模式就是提供一个工厂类,当有客户端需要调用的时候,只调用这个工厂类就可以得到自己想要的结果,从而无需关注某类的具体实现过程。这就好比你去餐馆吃饭,可以直接点菜,而不用考虑厨师是怎么做的。

工厂模式在 MyBatis 中的典型代表是 SqlSessionFactory:SqlSession 是 MyBatis 中的重要 Java 接口,可以通过该接口来执行 SQL 命令、获取映射器示例和管理事务,而 SqlSessionFactory 正是用来产生 SqlSession 对象的,所以它在 MyBatis 中是比较核心的接口之一。

工厂模式应用解析:SqlSessionFactory 是一个接口类,它的子类 DefaultSqlSessionFactorys 有个openSession(ExecutorType execType) 的方法,其中使用了工厂模式,源码如下:

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

从该方法我们可以看出它会 configuration.newExecutor(tx, execType) 读取对应的环境配置,而此方法的源码如下:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {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);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

可以看出 newExecutor() 方法为标准的工厂模式,它会根据传递 ExecutorType 值生成相应的对象然后进行返回。

3)建造者模式(Builder)

建造者模式指的是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。也就是说建造者模式是通过多个模块一步步实现了对象的构建,相同的构建过程可以创建不同的产品。

例如,组装电脑,最终的产品就是一台主机,然而不同的人对它的要求是不同的,比如设计人员需要显卡配置高的;而影片爱好者则需要硬盘足够大的(能把视频都保存起来),但对于显卡却没有太大的要求,我们的装机人员根据每个人不同的要求,组装相应电脑的过程就是建造者模式。

建造者模式在 MyBatis 中的典型代表是 SqlSessionFactoryBuilder。

普通的对象都是通过 new 关键字直接创建的,但是如果创建对象需要的构造参数很多,且不能保证每个参数都是正确的或者不能一次性得到构建所需的所有参数,那么就需要将构建逻辑从对象本身抽离出来,让对象只关注功能,把构建交给构建类,这样可以简化对象的构建,也可以达到分步构建对象的目的,而 SqlSessionFactoryBuilder 的构建过程正是如此。

在 SqlSessionFactoryBuilder 中构建 SqlSessionFactory 对象的过程是这样的,首先需要通过 XMLConfigBuilder 对象读取并解析 XML 的配置文件,然后再将读取到的配置信息存入到 Configuration 类中,然后再通过 build 方法生成我们需要的 DefaultSqlSessionFactory 对象,实现源码如下(在 SqlSessionFactoryBuilder 类中):

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}
}
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

SqlSessionFactoryBuilder 类相当于一个建造工厂,先读取文件或者配置信息、再解析配置、然后通过反射生成对象,最后再把结果存入缓存,这样就一步步构建造出一个 SqlSessionFactory 对象。

4)单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。

单例模式也比较好理解,比如一个人一生当中只能有一个真实的身份证号,每个收费站的窗口都只能一辆车子一辆车子的经过,类似的场景都是属于单例模式;单例模式在 MyBatis 中的典型代表是 ErrorContext。

ErrorContext 是线程级别的的单例,每个线程中有一个此对象的单例,用于记录该线程的执行环境的错误信息。

ErrorContext 的实现源码如下:

public class ErrorContext {private static final String LINE_SEPARATOR = System.lineSeparator();// 每个线程存储的容器private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);public static ErrorContext instance() {return LOCAL.get();}// 忽略其他
}

可以看出 ErrorContext 使用 private 修饰的 ThreadLocal 来保证每个线程拥有一个 ErrorContext 对象,在调用 instance() 方法时再从 ThreadLocal 中获取此单例对象。

5)适配器模式

适配器模式是指将一个不兼容的接口转换成另一个可以兼容的接口,这样就可以使那些不兼容的类可以一起工作。
例如,最早之前我们用的耳机都是圆形的,而现在大多数的耳机和电源都统一成了方形的 typec 接口,那之前的圆形耳机就不能使用了,只能买一个适配器把圆形接口转化成方形的,而这个转换头就相当于程序中的适配器模式,适配器模式在 MyBatis 中的典型代表是 Log。

MyBatis 中的日志模块适配了以下多种日志类型:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

首先 MyBatis 定义了一个 Log 的接口,用于统一和规范接口的行为,源码如下:

public interface Log {boolean isDebugEnabled();boolean isTraceEnabled();void error(String s, Throwable e);void error(String s);void debug(String s);void trace(String s);void warn(String s);
}

然后 MyBatis 定义了多个适配接口,例如 Log4j2 实现源码如下:

public class Log4j2Impl implements Log {private final Log log;public Log4j2Impl(String clazz) {Logger logger = LogManager.getLogger(clazz);if (logger instanceof AbstractLogger) {log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);} else {log = new Log4j2LoggerImpl(logger);}}@Overridepublic boolean isDebugEnabled() {return log.isDebugEnabled();}@Overridepublic boolean isTraceEnabled() {return log.isTraceEnabled();}@Overridepublic void error(String s, Throwable e) {log.error(s, e);}@Overridepublic void error(String s) {log.error(s);}@Overridepublic void debug(String s) {log.debug(s);}@Overridepublic void trace(String s) {log.trace(s);}@Overridepublic void warn(String s) {log.warn(s);}
}

这样当你项目中添加了 Log4j2 时,MyBatis 就可以直接使用它打印 MyBatis 的日志信息了。Log 的所有子类如下图所示:

6)代理模式

代理模式指的是给某一个对象提供一个代理对象,并由代理对象控制原对象的调用。

代理模式在生活中也比较常见,比如我们常见的超市、小卖店其实都是一个个“代理”,他们的最上游是一个个生产厂家,他们这些代理负责把厂家生产出来的产品卖出去,代理模式在 MyBatis 中的典型代表是 MapperProxyFactory。

MapperProxyFactory 的 newInstance() 方法就是生成一个具体的代理来实现功能的,源码如下:

public class MapperProxyFactory<T> {private final Class<T> mapperInterface;private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();public MapperProxyFactory(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}public Class<T> getMapperInterface() {return mapperInterface;}public Map<Method, MapperMethodInvoker> getMethodCache() {return methodCache;}// 创建代理类@SuppressWarnings("unchecked")protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}
}

7)模板方法模式

模板方法模式是最常用的设计模式之一,它是指定义一个操作算法的骨架,而将一些步骤的实现延迟到子类中去实现,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。此模式是基于继承的思想实现代码复用的。
例如,我们喝茶的一般步骤都是这样的:

  • 把热水烧开
  • 把茶叶放入壶中
  • 等待一分钟左右
  • 把茶倒入杯子中
  • 喝茶

整个过程都是固定的,唯一变的就是泡入茶叶种类的不同,比如今天喝的是绿茶,明天可能喝的是红茶,那么我们就可以把流程定义为一个模板,而把茶叶的种类延伸到子类中去实现,这就是模板方法的实现思路,模板方法在 MyBatis 中的典型代表是 BaseExecutor。
在 MyBatis 中 BaseExecutor 实现了大部分 SQL 执行的逻辑,然后再把几个方法交给子类来实现,它的继承关系如下图所示:

比如 doUpdate() 就是交给子类自己去实现的,它在 BaseExecutor 中的定义如下:

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

在 SimpleExecutor 中的实现如下:

public class SimpleExecutor extends BaseExecutor {// 构造方法public SimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}// 更新方法@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {closeStatement(stmt);}}// 忽略其他代码...
}

可以看出 SimpleExecutor 每次使用完 Statement 对象之后,都会把它关闭掉,而 ReuseExecutor 中的实现源码如下:

public class ReuseExecutor extends BaseExecutor {private final Map<String, Statement> statementMap = new HashMap<>();public ReuseExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}// 更新方法@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);Statement stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);}// 忽略其他代码...
}

可以看出,ReuseExecutor 每次使用完 Statement 对象之后不会把它关闭掉。

8)装饰器模式

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构,这种类型的设计模式属于结构型模式,它是作为现有类的一个包装。

装饰器模式在生活中很常见,比如装修房子,我们在不改变房子结构的同时,给房子添加了很多的点缀;比如安装了天然气报警器,增加了热水器等附加的功能都属于装饰器模式。

装饰器模式在 MyBatis 中的典型代表是 Cache。

Cache 除了有数据存储和缓存的基本功能外(由 PerpetualCache 永久缓存实现),还有其他附加的 Cache 类,比如先进先出的 FifoCache、最近最少使用的 LruCache、防止多线程并发访问的 SynchronizedCache 等众多附加功能的缓存类,Cache 所有实现子类如下图所示:

MyBatis 源码可以访问:https://github.com/mybatis/mybatis-3

Java 源码剖析(13)--MyBatis 使用了哪些设计模式?相关推荐

  1. java hashset 实现_HashSet实现原理分析(Java源码剖析)

    本文将深入讨论HashSet实现原理的源码细节.在分析源码之前,首先我们需要对HashSet有一个基本的理解. HashSet只存储不同的值,set中是不会出现重复值的. HashSet和HashMa ...

  2. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  3. 【java集合框架源码剖析系列】java源码剖析之java集合中的折半插入排序算法

    注:关于排序算法,博主写过[数据结构排序算法系列]数据结构八大排序算法,基本上把所有的排序算法都详细的讲解过,而之所以单独将java集合中的排序算法拿出来讲解,是因为在阿里巴巴内推面试的时候面试官问过 ...

  4. java 字符串第一个字符_深入Java源码剖析之字符串常量

    字符串在Java生产开发中的使用频率是非常高的,可见,字符串对于我们而言非常关键.那么从C语言过来的同学会发现,在C中是没有String类型的,那么C语言要想实现字符串就必须使用char数组,通过一个 ...

  5. java 源码学习,Java源码剖析34讲学习笔记~4

    详解 ThreadPoolExecutor 的参数含义及源码执行流程 前言 在阿里巴巴的开发者手册中针对线程池有如下说明: [强制]线程池不允许使用 Executors 去创建,而是通过 Thread ...

  6. Java 源码剖析(16)--浅谈MySQL 的运行机制

    MySQL 的运行机制 1) MySQL 是如何运行的 2) 查询缓存的利弊 3)如何选择数据库引擎 4)InnoDB 自增主键 5)小结 1) MySQL 是如何运行的 MySQL 的执行流程是这样 ...

  7. Java HashSet和HashMap源码剖析

    转载自 Java HashSet和HashMap源码剖析 总体介绍 之所以把HashSet和HashMap放在一起讲解,是因为二者在Java里有着相同的实现,前者仅仅是对后者做了一层包装,也就是说Ha ...

  8. java实现gdal栅格矢量化,《GDAL源码剖析与开发指南》一一1.5 GDAL源码目录

    本节书摘来自异步社区出版社<GDAL源码剖析与开发指南>一书中的第1章,第1.5节,作者:李民录 更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.5 GDAL ...

  9. 【Java集合源码剖析】TreeMap源码剖析

    2019独角兽企业重金招聘Python工程师标准>>> 前言 本文不打算延续前几篇的风格(对所有的源码加入注释),因为要理解透TreeMap的所有源码,对博主来说,确实需要耗费大量的 ...

最新文章

  1. linux一直用户身份验证失败,linux – chsh:PAM身份验证失败
  2. Msfvenonm生成后门
  3. DataContractJsonSerializer类
  4. because it is not a variable 编译错误解决方案
  5. 用MyEclipse JPA创建项目(四)
  6. Javascript中的对象拷贝(对象复制/克隆)
  7. 网站安全之密码明文传输漏洞
  8. 伺服驱动系统的电磁干扰问题
  9. SAP 报表设计器相关TCODE
  10. QML类型:Dialog(Qt Quick Dialogs 模块)
  11. Macs Fan Control 官方正版中文网站 控制苹果电脑上风扇工具软件
  12. 融会贯通,从oracle...,融会贯通Oracle数据库的25条基本知识:
  13. 什么是方法的重载?(Java)
  14. windows11 版本 business editions consumer editions 区别介绍
  15. java JFreechart开发报表的实例demo下载
  16. [APIO2016]烟火表演
  17. 【国家集训队2011】【BZOJ2141】排队
  18. 了解vm.swappiness
  19. 虚拟化技术的分类及介绍
  20. 12天背诵楞严咒的技巧_楞严咒快速背诵方法,我试了n种,最后选择了这些土方法...

热门文章

  1. QT之OpenGL坐标系统
  2. 1 Microservice 简介
  3. LED数码管段码生成
  4. 写一个程序,判断能否形成三角形,若能,判断是等腰三角形、等边三角形、直角三角形、锐角三角形还是钝角三角形。
  5. Android手把手教你使用阿里云接口实现人脸定位、人脸检测、人脸对比功能。
  6. start.bat批处理文件
  7. CVPR读书笔记[7]:PCA的理解
  8. javascript判断浏览器是否是隐私模式
  9. 【FTP】Entering Extended Passive Mode
  10. Vue 自定义消息通知组件