首页 > 编程语言 >MyBatis中经典的五种设计模式源码剖析,打死都不要忘记!

MyBatis中经典的五种设计模式源码剖析,打死都不要忘记!

时间:2024-03-15 17:32:20浏览次数:30  
标签:缓存 return 对象 Object 源码 MyBatis 设计模式 方法 public

MyBatis 3.5版本中也广泛使用了多种设计模式,下面是其中一些主要使用的设计模式

MyBatis

一、构建器模式

  • XMLConfigBuilder: 用于解析 MyBatis 配置文件
  • XMLMapperBuilder: 用于解析映射文件(Mapper.xml)
  • SqlSourceBuilder: 用于构建不同类型的 SqlSource 实现
  1. XMLConfigBuilder

XMLConfigBuilder 类用于解析 MyBatis 的全局配置文件,它的主要实现在 parse() 方法中。该方法通过 Java 的 SAX (Simple API for XML) 解析器来读取 XML 配置文件,并将配置项构建到 Configuration 对象中。

  1. XMLMapperBuilder

XMLMapperBuilder 类用于解析映射文件(Mapper.xml),它的主要实现在 parse() 方法中。该方法同样使用 SAX 解析器来读取映射文件,并根据不同的节点类型构建出相应的 MappedStatement 对象,最终添加到 Configuration 对象中。

  1. SqlSourceBuilder

SqlSourceBuilder 是一个抽象类,它定义了 parse() 方法的框架,用于解析不同类型的 SQL 节点(如 <select><insert>等)。具体的解析工作由其子类 XMLScriptBuilderXMLStaticSqlSource 来完成。

XMLScriptBuilder 用于解析动态 SQL 节点,它会将节点内容构建成 SqlSource 接口的实现类 DynamicSqlSource

XMLStaticSqlSource 用于解析静态 SQL 节点,它会将节点内容作为字符串直接构建出 StaticSqlSource 实例。

以上这些构建器类通过组合使用,共同完成了对 MyBatis 全局配置文件和映射文件的解析和构建工作。

// 创建 XMLConfigBuilder
XMLConfigBuilder configBuilder = ...
// 解析全局配置文件,构建 Configuration 对象
Configuration config = configBuilder.parse();

// 创建 XMLMapperBuilder
XMLMapperBuilder mapperBuilder = new XMLMapperBuilder(config, ...);
// 解析映射文件,构建 MappedStatement 对象并添加到 Configuration 中
mapperBuilder.parse();

// 创建 SqlSourceBuilder
SqlSourceBuilder sqlSourceBuilder = new XMLScriptBuilder(...);
// 解析 SQL 节点,构建 SqlSource 实现
SqlSource sqlSource = sqlSourceBuilder.parse(...);

二、工厂模式

  • SqlSessionFactory: 用于创建 SqlSession 实例
  • ObjectFactory: 用于创建结果对象的实例
  • TypeHandlerRegistry: 管理 TypeHandler 实例的注册与获取
  1. SqlSessionFactory

SqlSessionFactory 接口定义了 openSession() 方法,用于创建 SqlSession 对象。它的默认实现是 DefaultSqlSessionFactory 类。

在 DefaultSqlSessionFactory 中,通过构造函数或 openSessionFromDataSource() 方法创建 SqlSession 实例。以 openSession() 方法为例:

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); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
  1. ObjectFactory

ObjectFactory 接口定义了创建结果对象实例的方法。默认实现是 DefaultObjectFactory 类,它使用反射机制创建对象实例。

public <T> T create(Class<T> type) {
  return create(type, null, null);
}

public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
  Class<?> classToCreate = resolveInterface(type);
  // we know types are assignable
  return (T) createInstance(classToCreate, constructorArgTypes, constructorArgs);
}
  1. TypeHandlerRegistry

TypeHandlerRegistry 类用于管理 TypeHandler 实例的注册和获取。它维护了一个 TypeHandler 的注册表,开发者也可以通过 register() 方法注册自定义的 TypeHandler。

public <T> void register(TypeInfo<T> typeInfo, TypeHandler<? extends T> typeHandler) {
    register(MappedTypes.forTypeInfo(typeInfo), typeHandler);
}

private <T> void register(MappedTypes<T> mappedTypes, TypeHandler<? extends T> typeHandler) {
    if (mappedTypes.hasUnknownType()) {
        throw new IllegalArgumentException("Cannot add handlers for unknown types.");
    }
    bundlers.put(mappedTypes, typeHandler);
}

public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
    return getTypeHandler((Type) type, null);
}

public <T> TypeHandler<T> getTypeHandler(Type type, TypeHandlerRegistry instance) {
    if (instance == null) {
        instance = this;
    }
    final Map<TypeInfo<?>, TypeHandler<?>> typeHandlers = instance.getTypeHandlers();
    TypeHandler<?> handler = typeHandlers.get(TypeParameterSetter.resolveType(type));
    if (handler == null) {
        handler = typeHandlers.get(MappedTypes.forType(type, null));
    }
    @SuppressWarnings("unchecked")
    // This cast should be safe as we'll only get handlers of the expected type
    TypeHandler<T> cachedHandler = (TypeHandler<T>) handler;
    return cachedHandler;
}

三、代理模式

  • MapperProxy: 基于 JDK 动态代理创建 Mapper 接口的代理实例
  • ConnectionProxy: 为 Connection 对象创建代理
    MyBatis 3.5版本中使用了 JDK 动态代理来实现代理模式,下面是关键源码分析:
  1. MapperProxy

MapperProxy 实现了 InvocationHandler 接口,用于创建 Mapper 接口的代理实例。关键代码如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 如果是 Object 类中定义的方法,直接调用
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    // 忽略一些方法
    } else if (isDefaultMethod(method)) {
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 创建 MapperMethod 实例,它定义了如何执行映射语句
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

MapperProxy 会缓存 MapperMethod 实例,在执行 Mapper 接口方法时,会调用对应 MapperMethod 的 execute 方法,该方法内部会进一步调用 SqlSession 的方法执行映射语句。

  1. ConnectionProxy

ConnectionProxy 实现了 InvocationHandler 接口,用于为 Connection 对象创建代理。关键代码如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 忽略 Object 类定义的方法
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    // 忽略一些方法
    } else if (CLOSE.hashCode() == method.getName().hashCode() && CLOSE.equals(method.getName())) {
      return null;
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 如果是其他方法,执行原始 Connection 对象的对应方法
  try {
    return method.invoke(connection, args);
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

ConnectionProxy 的主要作用是拦截 Connection 的 close 方法,防止连接被过早关闭,其他方法调用都会转发给原始的 Connection 对象执行。通过代理模式,MyBatis 可以在原有功能的基础上添加一些额外的处理逻辑,如 SQL 缓存、延迟加载等,从而提高性能和扩展性。

四、 模板方法模式

  • BaseExecutor: 提供了 update、query 等模板方法
  • SimpleExecutor、ReuseExecutor 等继承并实现具体逻辑

下面我们来看 BaseExecutorquery 方法的实现:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  // 获取缓存键
  CacheKey cacheKey = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}

private <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
  // 从二级缓存中获取结果
  List<E> list = resultHandlerCacheCtrl.getList(cacheKey, boundSql, resultHandler);
  if (list != null) {
    return list;
  }
  // 执行查询
  list = delegateExecute(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
  // 存入二级缓存
  resultHandlerCacheCtrl.storeList(cacheKey, list);
  return list;
}

private <E> List<E> delegateExecute(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
  // 由具体的执行器实现查询逻辑
  return delegate.execute(ms, parameter, rowBounds, resultHandler, boundSql);
}

query 方法中,先根据传入的参数创建缓存键,然后尝试从二级缓存中获取结果。如果缓存中没有,就调用 delegateExecute 方法执行具体的查询逻辑。查询结果会被存入二级缓存中。

delegateExecute 方法只是一个模板方法,它将具体的查询逻辑委托给了子类的 execute 方法去实现。

下面以 SimpleExecutor 为例,看一下它是如何实现 execute 方法的:

public <E> List<E> execute(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  stmt = handler.prepare(statement, transaction.getTimeout());
  return handler.query(stmt, resultHandler);
}

SimpleExecutorexecute 方法首先创建了一个 StatementHandler 对象,然后调用它的 prepare 方法创建 Statement。最后,再调用 StatementHandlerquery 方法执行查询,并返回结果。

通过这种模板方法模式的设计,BaseExecutor 定义了一个算法的框架,而具体的执行步骤由子类去实现

五、装饰器模式

  • Cache decorators: 为缓存提供装饰器扩展功能
  • ResultLoaderMap: 延迟加载的实现使用了装饰器模式
    在 MyBatis 3.5 版本中,装饰器模式主要应用在缓存和延迟加载功能上。下面分别对这两个部分的源码实现进行分析:
  1. Cache decorators: 为缓存提供装饰器扩展功能

MyBatis 使用装饰器模式为缓存功能提供了可扩展性,主要通过 CacheDecorator 抽象类和多个具体的装饰器类实现。

// CacheDecorator 是一个抽象装饰器类
public abstract class CacheDecorator implements Cache {
  // 持有被装饰的缓存对象
  protected final Cache delegate;

  // 构造函数将被装饰对象传入
  public CacheDecorator(Cache delegate) {
    this.delegate = delegate;
  }

  // 其他方法的实现都会调用被装饰对象的相应方法
  @Override
  public String getId() {
    return delegate.getId();
  }
  // ...
}

CacheDecorator 持有一个被装饰的 Cache 对象,并在其方法实现中调用该对象的相应方法。

具体的装饰器类继承自 CacheDecorator并添加了额外的功能,例如:

  • LruCache(最近最少使用缓存): 添加了维护缓存大小的功能
  • ScheduledCache(定时刷新缓存): 添加了定时刷新线程清理缓存的功能
  • SerializedCache(序列化缓存): 添加了缓存对象序列化功能
  • LoggingCache(日志缓存): 添加了缓存操作日志输出功能

通过装饰器模式的设计,MyBatis 可以很方便地扩展缓存的功能,只需要新增一个装饰器类就可以了。

  1. ResultLoaderMap: 延迟加载的实现使用了装饰器模式

MyBatis 使用 ResultLoaderMap 装饰器类为映射结果集提供了延迟加载功能。

public class ResultLoaderMap extends AbstractObjectDecorator {

  // 持有被装饰对象的元数据信息
  private final MetaObject metaResultObject;
  // ...

  public ResultLoaderMap(Object mapResult, Configuration configuration) {
    // 将被装饰对象传入父类
    super(mapResult);
    metaResultObject = configuration.newMetaObject(mapResult);
  }

  // 重写 setValue 方法,实现延迟加载
  @Override
  public void setValue(Object prop, Object value) {
    if (prop instanceof String) {
      // 调用父类 setValue 设置属性值
      super.setValue(prop, value);
    } else {
      // 通过 MetaObject 设置值,实现延迟加载
      metaResultObject.setValue(prop, value);
    }
  }
}

ResultLoaderMap 继承自 AbstractObjectDecorator,在构造函数中将被装饰对象传递给父类。
setValue 方法被重写,如果属性是 String 类型,则直接调用父类的 setValue 方法设置值;否则通过 MetaObject 设置值,实现延迟加载的逻辑。

六、代理模式

  1. 插件运行原理基于观察者模式

MyBatis 中有四大对象: ExecutorParameterHandlerResultSetHandlerStatementHandler。插件可以选择性地监听这四个对象的方法,并在方法执行前后进行拦截和处理。

这个过程是通过动态代理实现的,MyBatis 会为目标对象创建一个代理对象,插件可以在代理对象的方法执行前后注入自己的逻辑。

public Object plugin(Object target) {
    // 生成代理对象
    return Plugin.wrap(target, this);
}

public static Object wrap(Object target, Interceptor interceptor) {
    // 获取目标对象的类
    Class<?> type = target.getClass();
    // 获取所有接口
    Class<?>[] interfaces = getAllInterfaces(type, null);
    if (interfaces.length > 0) {
        // 创建代理对象
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor));
    }
    return target;
}

Pluginwrap 方法中,通过 JDK 动态代理创建了一个目标对象的代理对象,代理对象的所有方法调用都会被 Plugin 实例的 invoke 方法拦截。

  1. 插件通过监听四大对象的方法调用来实现自身逻辑

Executor 插件为例,它需要实现 Interceptor 接口,并重写 intercept 方法:

@Intercepts({@Signature(type = Executor.class, method = "update", args = {...})})
public class ExamplePlugin implements Interceptor {
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 执行被拦截方法之前进行的处理
        ...
        
        // 执行被拦截方法
        Object result = invocation.proceed();
        
        // 执行被拦截方法之后进行的处理
        ...
        
        return result;
    }
    
    @Override
    public Object plugin(Object target) {
        // 返回目标对象的代理对象
        return Plugin.wrap(target, this);
    }
}

intercept 方法中,插件可以在执行被拦截方法之前和之后注入自己的逻辑。插件通过 invocation.proceed() 调用执行被拦截方法。

插件需要在 plugin 方法中返回目标对象的代理对象,这样 MyBatis 在使用目标对象时就会使用这个代理对象,从而实现了插件逻辑的注入。

标签:缓存,return,对象,Object,源码,MyBatis,设计模式,方法,public
From: https://blog.csdn.net/weixin_44716935/article/details/136744163

相关文章

  • Java访问者模式源码剖析及使用场景
    访问者模式一、介绍二、报表系统开发三、MyBatis中如何使用访问者模式?一、介绍Java中的访问者(Visitor)模式是一种行为型设计模式,它将数据结构与数据操作分离,使得在不修改数据结构的情况下可以增加新的操作。该模式主要包含以下几个角色:抽象访问者(Visitor):定......
  • ThreadLocal源码解析
    ThreadLocalpublicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)//map不为null,之前设置过情况map.set(this,value);elsecreateMap(t,value);}privatevoidset......
  • 设计模式——抽象工厂实验
    抽象工厂实验实验场景:电子商务系统中创建的订单分为国内订单(DomesticOrder)和海外订单(OverseasOrder);国内订单使用人民币支付(RMBPayment),海外订单使用美元支付(USDPayment)。实验要求:设计使用抽象工厂模式来实现订单创建功能。实验内容:将订单工厂中的接口封装为order-api......
  • [原创] KCP 源码分析(上)
    KCP协议是一种可靠的传输协议,对比TCP取消了累计确认(延迟ACK)、减小RTO增长速度、选择性重传而非全部重传。通过用流量换取低时延。KCP中最重要的两个数据结构IKCPCB和IKCPSEG,一个IKCPCB对应一个KCP连接,通过这个结构体维护发送缓存、接收缓存、超时重传时间、窗口大小等。I......
  • MyBatis 之五:MyBatis 的缓存机制
    (关注+点赞是我继续的最大动力,谢谢支持!) 缓存MyBatis提供了缓存机制来提高应用程序性能,特别是对于那些频繁读取但更新不那么频繁的数据。MyBatis提供了一级缓存和二级缓存一级缓存(本地缓存)一级缓存默认是开启,可以直接使用的。属于SqlSession级别的缓存。一级缓存是......
  • 全套大型体检中心PEIS源码 医院PEIS管理系统源码
    大型体检中心PEIS源码 医院PEIS管理系统源码医院智慧体检系统,该系统覆盖医院体检科、体检中心的所有业务,完成从预约、登记、收费、检查、检验、出报告、分析、报表等所有工作。系统可以对团检的每个环节设有操作界面,从检前的预约、记录、EXCEL数据批量导入、自动筛选、自......
  • 【已解决】Mybatis-plus中@TableLogic注解失效问题
    逻辑删除逻辑删除是指通过修改数据的状态或添加额外字段来表示数据的删除状态,而不是直接从数据库中物理删除数据记录。通常,会在数据库表中新增一个字段(如deleted),用来标识数据是否被删除。MyBatisPlus中实现逻辑删除在使用MyBatisPlus进行数据库操作时,实现逻辑删除......
  • Django admin管理工具的使用、定制及源码解析
    Djangoadmin管理工具的使用、定制及源码解析admin组件使用Django提供了基于web的管理工具。Django自动管理工具是django.contrib的一部分。你可以在项目的settings.py中的INSTALLED_APPS看到它:#ApplicationdefinitionINSTALLED_APPS=['django.contrib.a......
  • 小程序开发平台源码系统:万能建站门店小程序功能 带完整的搭建教程以及代码包
    在移动互联网时代,小程序以其独特的优势,迅速占领了市场的一席之地。然而,对于许多中小企业和个人开发者来说,缺乏专业的开发团队和技术支持,使得小程序开发成为一项难以逾越的技术门槛。小编给大家分享一款万能建站门店小程序源码系统,旨在降低小程序开发的难度,让更多的人能够轻松搭......
  • 团购小程序源码系统:快递代收+社区便利店+推送商品等功能 带完整的安装部署教程
    在移动互联网高速发展的今天,小程序以其轻便、快捷、无需下载的特点,迅速成为商家与用户之间的桥梁。为了满足社区团购市场的需求,小编给大家分享一款功能强大的团购小程序源码系统,该系统集成了快递代收、社区便利店、推送商品等多项功能,为商家提供了一个高效、便捷的运营平台。......