百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Mybatis中的设计模式

bigegpt 2024-08-21 12:03 2 浏览

之前就自己关注的问题研究了mybatis的源代码,今天我们来看一下mybatis中都使用了哪些设计模式。

关于设计模式,我平时也有不少使用,但是总感觉理解的不是很深刻,学习源码并观察设计模式在其中的应用,也可以更加深入的了解设计模式。

建造者(Builder)设计模式

将一个复杂的对象的构造与他的表示分离,是同样的构造过程可以创建不同的表示,他是将一个复杂的对象分解为多个简单的对象,然后一步一步的构建而成。

一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式和Builder模式。

相对于工厂模式会产出一个完整的产品,Builder应用于更加复杂的对象的构建,甚至只会构建产品的一个部分。

使用过Mybatis的肯定很熟悉这段代码吧

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();

我们使用简单的两行代码之后,便可以获取我们的mapper,然后直接查询数据库了。但是在这两行简单的代码背后,却是非常复杂的配置解析过程。

在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的mybatis-config.xml和所有的*Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取*Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。

在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。

常用的Builder:

  • SqlSessionFactoryBuilder:构建SqlSession
  • XMLConfigBuilder:构建Mybatis-config.xml成对象
  • XMLMapperBuilder:解析*Mapper.xml成对象
  • XMLStatementBuilder:解析单个的select、insert等statement标签
  • ParameterMapping.Builder:构建每一个#{}参数成为一个ParameterMapping
  • MappedStatement.Builder:构建每一个select、insert等statement标签
  • ResultMap.Builder:构建ResultMap标签
  • ResultMapping.Builder:构建ResultMap标签中的每个参数

工厂设计模式

工厂模式相对来说很简单

在工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

  • LogFactory
//根据传入的类名来构建Log
  public static Log getLog(String logger) {
    try {
      //构造函数,参数必须是一个,为String型,指明logger的名称
      return logConstructor.newInstance(new Object[] { logger });
    } catch (Throwable t) {
      throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
    }
  }
  • SqlSessionFactory
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);
      //然后产生一个DefaultSqlSession
      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();
    }
  }

单例设计模式

这个很简单,不再说明

public final class LogFactory {
  //...
	//单例模式,不得自己new实例
  private LogFactory() {
    // disable construction
  }
}

代理设计模式

代理模式大概是Mybatis的核心了吧,我在《Mybatis核心源码-通过sqlSession获取映射器代理工厂》一文中写了通过sqlSession.getMapper获取对应的代理对象,然后通过代理对象获取对应的mapper.xml,这里便用到了代理设计模式,所以我不再赘述了,有兴趣的小伙伴可以看一下对应的文章。

组合设计模式

组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。

组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。

同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的内部结构解耦。

在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。

这里面的if标签,trim标签在mybatis中都对应一个SqlNode,例如IfSqlNode、TrimSqlNode、StaticTextSqlNode等

在mybatis中,在创建SqlSource时,会解析当前select标签中所有的动态标签,构建成SqlNode并放到集合中,最终构建成MixedSqlNode混合节点。


在这张图中,MixedSqlNode就是根结点,而IfSqlNode、TrimSqlNode等动态标签就是树枝节点,StaticTextSqlNode(#{})TextSqlNode(${})则是叶子节点。

组合模式树的各个节点

其中的StaticTextSqlNode(#{})TextSqlNode(${})是叶子结点,所有的非叶子结点最终都会通过递归调用获取到对应的叶子结点,也就是动态sql标签中的sql,然后进行拼接。
MixedSqlNode根结点下的所有树枝节点:

IfSqlNode树枝节点下会有一个StaticTextSqlNode叶子节点。

在组合模式中,根节点和树枝节点本质上属于同一种数据类型,他们具备一致的行为(apply

了解完动态标签的数据结构后,我们来看一个复杂的含有动态sql的select语句标签是怎么被拼接成一个完整的sql的。

首先作为根结点的MixedSqlNode执行他的apply方法:

public class MixedSqlNode implements SqlNode {
  //组合模式,拥有一个SqlNode的List
  private List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    //依次调用list里每个元素的apply
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }
}

实际上MixedSqlNode循环调用了树枝节点的apply方法

作为树枝节点的IfSqlNode中有一个叶子节点StaticTextSqlNode,树枝节点的apply在解析完Ognl表达式后,调用了叶子节点StaticTextSqlNode的apply方法。而叶子节点的apply方法就是简单的使用append方法进行了字符串拼接。

public class IfSqlNode implements SqlNode {
  private ExpressionEvaluator evaluator;
  private String test;
  private SqlNode contents;

  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    //mixed里面是一个静态标签,if标签包裹的地方
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    //如果满足条件,则apply,并返回true
    //Ognl表达式
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

}

通过使用组合模式,mybatis的动态标签可以通过递归的方式将每一个树枝节点(动态标签)进行拼接,最终形成一个完整的sql。

模板方法设计模式

模板方法设计模式是非常常用的设计模式,我平时在开发中也经常用到,通过抽象类定义统一的模板,然后让实现类去实现具体的细节

在Mybatis中,通过StatementHandler接口,定义了统一的sql执行过程:

然后通过它的实现类PreparedStatementHandler等去实现具体的细节:

模板模式其实非常简单,通过抽象类去定义统一的模板,然后通过实现类去补充具体的实现细节。

适配器设计模式

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。

mybatis中最值得称赞的是mybatis对于各个日志的集成工作,我在《Mybatis整合日志框架-工厂设计模式+适配器设计模式》一文中详细解释了mybatis通过定义Log接口,Mybatis提供了多种日志框架的实现,这些实现都匹配这个Log接口所定义的接口方法,最终实现了所有外部日志框架到Mybatis日志包的适配


装饰者设计模式

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。

其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。

我在《Mybatis一级缓存原理解析》一问中解析了一级缓存的原理,Mybatis在创建executor时其实是直接创建了CachingExecutor,然后将SimpleExecutor通过构造器的方式传给了CachingExecutor,使用CachingExecutor在执行时除了执行自己特有的二级缓存后,其他的都是通过包装类SimpleExecutor去执行的。

相关推荐

悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)

新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...

高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源

凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...

微服务架构实战:商家管理后台与sso设计,SSO客户端设计

SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...

还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑

在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...

Seata源码—6.Seata AT模式的数据源代理二

大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...

30分钟了解K8S(30分钟了解微积分)

微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...

SpringBoot条件化配置(@Conditional)全面解析与实战指南

一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...

一招解决所有依赖冲突(克服依赖)

背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...

你读过Mybatis的源码?说说它用到了几种设计模式

学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...

golang对接阿里云私有Bucket上传图片、授权访问图片

1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...

spring中的资源的加载(spring加载原理)

最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...

Android资源使用(android资源文件)

Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...

如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)

深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...

@Autowired与@Resource原理知识点详解

springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...

java的redis连接工具篇(java redis client)

在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...