首页 > 其他分享 >第 2 章 基础支持层(下)

第 2 章 基础支持层(下)

时间:2024-10-12 09:49:32浏览次数:10  
标签:缓存 接口 Cache 基础 private final 支持 public

2.6 DataSource

MyBatis 提供了两个 javax.sql.DataSource 接口实现,分别是 PooledDataSource 和 UnpooledDataSource。MyBatis 使用不同的 DataSourceFactory 接口实现创建不同类型的 DataSource。

2.6.1 工厂方法模式

在工厂方法模式中,定义一个用于创建对象的工厂接口,并根据工厂接口的具体实现类决定具体实例化哪一个产品类。

在这里插入图片描述

工厂方法由四个角色构成:

  1. 工厂接口(Factory):核心接口,调用者会直接与工厂接口交互用于获取具体的产品实现类
  2. 具体工厂类(ConcreteFactory):实现类,用于实例化产品对象
  3. 产品接口(Product):用于定义产品类的功能,具体工厂类产生的所有产品对象都必须实现该接口
  4. 具体产品类(ConcreteProduct):实现产品接口的实现类

当需要添加新的第三方数据源组件时,只需要添加对应的工厂实现类,新数据源就能被 MyBatis 使用。

2.6.2 DataSourceFactory

在数据源模块中,DataSourceFactory 接口扮演工厂接口的角色。UnpooledDataSourceFactory 和 PooledDataSourceFactory 则扮演着具体工厂类的角色。

在这里插入图片描述

2.6.3 UnpooledDataSource

javax.sql.DataSource 接口在数据源模块中扮演了产品接口的角色,MyBatis 提供了两个 DataSource 接口的实现类,分别是 UnpooledDataSource 和 PooledDataSource,它们扮演了具体的产品类的角色。

在这里插入图片描述

2.6.4 PooledDataSource

数据库连接的创建过程是非常耗时的,数据库能够建立的连接数也非常有限。使用数据库连接池就显得尤为必要。

PooledDataSource 实现了简易数据库连接池的功能,如图

在这里插入图片描述

public class PoolState {

  protected PooledDataSource dataSource;

  protected final List<PooledConnection> idleConnections = new ArrayList<>();
  protected final List<PooledConnection> activeConnections = new ArrayList<>();
  
}

2.7 Transaction

MyBatis 使用 Transaction 接口对数据库事务进行抽象,它有 JdbcTransaction 和 ManagedTransaction 两个实现。

在这里插入图片描述

JdbcTransaction 依赖于 JDBC Connection 控制事务的提交和回滚。

ManagedTransaction 事务和提交和回滚都是空实现,依靠窗口管理的。

2.8 binding 模块

binding 模块提供 Mapper 接口,定义 SQL 语句对应的方法,这些方法在 MyBatis 初始化过程中会与映射配置文件中定义的 SQL 语句相关联。如果存在无法关系的 SQL 语句,就会抛出异常。

2.8.1 MapperRegistry & MapperProxyFactory

MapperRegistry 是 Mapper 接口及其对应的代理对象工厂的注册中心。

public class MapperRegistry {
    // Configuration 对象,MyBatis 全局唯一的配置对象,包含了所有配置信息
    private final Configuration config;
    // 记录 Mapper 接口与对应 MapperProxyFactory 之间的关系
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
}

MyBatis 初始化过程中人读取映射配置文件以及 Mapper 接口中的注解信息,填充到 knownMappers 集合中。

在需要执行 SQL 语句时,会通过 knownMappers 获取 MapperProxyFactory,并使用 JDK 动态代理生成代理对象执行 SQL 语句。

2.8.2 MapperProxy

MapperProxy 实现了 InvocationHandler 接口,核心功能如下

public class MapperProxy<T> implements InvocationHandler, Serializable {
    // 记录了关联的 SqlSession 对象
    private final SqlSession sqlSession;
    // Mapper 接口对应的 Class 对象
    private final Class<T> mapperInterface;
    // 用于缓存 MapperMethod 对象,key:Mapper 接口中方法对应的 Method 对象,value: 对应的 MapperMethod 对象。
    private final Map<Method, MapperMethodInvoker> methodCache;
}
2.8.3 MapperMethod

MapperMethod 中封装了 Mapper 接口中对应方法的信息,以及对应 SQL 语句的信息。MapperMethod 可以理解为连接 Mapper 接口以及映射配置文件中定义的 SQL 语句的桥梁。

public class MapperMethod {
	// 记录了 SQL 语句的名称和类型
    private final SqlCommand command;
    // Mapper 接口中对应方法的相关信息
    private final MethodSignature method;
}

MapperMethod 中最核心的方法是 execute(),它会根据 SQL 语句的类型调用 SqlSession 对应的方法完成数据库操作。

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

2.9 缓存模块

MyBatis 中的缓存是两层结构,分为一级缓存和二级缓存,但在本质是都是 Cache 接口的实现。

2.9.1 装饰器模式

装饰器可以动态地为对象添加功能,它是基于组合的方式实现该功能的。

在这里插入图片描述

  • Component(组件):定义了全部组件实现类以及所有装饰器实现的行为
  • ConcreteComponent(具体组件实现类):实现了 Component 接口。通常情况下就是被装饰器装饰的原始对象。
  • Decorator(装饰器):所有装饰器的父类,它是一个实现了 Component 接口的抽象类,
  • ConcreteDecorator:具体的装饰器实现类,该实现类要向被装饰对象添加某些功能。

使用装饰器模式有两个明显优点:

  1. 相较于承继,装饰器模式更灵活,也更容易扩展
  2. 当有新功能需要添加时,只需要添加新的装饰器实现类,无须修改已有类的代码,符合开闭原则
2.9.2 Cache 接口及其实现
public interface Cache {
	// 缓存对象的 id
    String getId();
	// 向缓存中添加数据,key:cacheKey,value:查询结果
    void putObject(Object key, Object value);
	// 根据指定的 key,在缓存中查询结果
    Object getObject(Object key);
	// 删除 key 对应的缓存项
    Object removeObject(Object key);
	// 清空缓存
    void clear();
	// 缓存项的个数,不会被 MyBatis 核心代码使用
    int getSize();
	// 获取读写锁,不会被 MyBatis 核心代码使用
    default ReadWriteLock getReadWriteLock() {
    	return null;
    }
}

实现类 PerpetualCache 提供了 Cache 接口的基本实现。底层使用 HashMap 记录缓存项,并实现 Cache 接口中定义的相应方法。

public class PerpetualCache implements Cache {

	private final String id;
	private final Map<Object, Object> cache = new HashMap<>();

	public PerpetualCache(String id) {
		this.id = id;
	}

	@Override
	public String getId() {
		return id;
	}

	@Override
	public int getSize() {
		return cache.size();
	}

	@Override
	public void putObject(Object key, Object value) {
		cache.put(key, value);
	}

	@Override
	public Object getObject(Object key) {
		return cache.get(key);
	}

	@Override
	public Object removeObject(Object key) {
		return cache.remove(key);
	}

	@Override
	public void clear() {
		cache.clear();
	}
}

Cache 的其他实现类,会在 PerpetualCache 的基础上提供一些额外功能,通过多个组合后满足一个特定的需求。

BlockingCache:阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据。

public class BlockingCache implements Cache {
	// 阻塞超时时长
    private long timeout;
    // 被装饰的底层 Cache 对象
    private final Cache delegate;
    // 每个 key 都有对应的 ReentrantLock 对象
    private final ConcurrentHashMap<Object, CountDownLatch> locks;
}

FifoCache & LruCache

public class FifoCache implements Cache {
	// 被装饰的底层 Cache 对象
	private final Cache delegate;
	// 用于记录 key 进入缓存的先后顺序
	private final Deque<Object> keyList;
	// 记录了缓存项的上限,超过该值,则需要清理最老的缓存项
	private int size;

	public FifoCache(Cache delegate) {
		this.delegate = delegate;
		this.keyList = new LinkedList<>();
		this.size = 1024;
	}
}
public class LruCache implements Cache {
	// 被装饰的底层 Cache 对象
	private final Cache delegate;
	// LinkedHashMap,记录 key 最近的使用情况
	private Map<Object, Object> keyMap;
	// 记录最少被使用的缓存项的 key
	private Object eldestKey;

	public LruCache(Cache delegate) {
		this.delegate = delegate;
		setSize(1024);
	}
}

SoftCache & WeakCache

关于强、软、弱、虚引用,会在其他笔记中整理

public class SoftCache implements Cache {
	// 在 SoftCache 中,保存最近使用的一部分缓存项不被 GC 回收
	private final Deque<Object> hardLinksToAvoidGarbageCollection;
    // 引用队列,用于记录已经被 GC 回收的缓存项所对应的 SoftEntry 对象
	private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
    // 被装饰的底层 Cache 对象
	private final Cache delegate;
    // 强连接的个数
	private int numberOfHardLinks;

	public SoftCache(Cache delegate) {
		this.delegate = delegate;
		this.numberOfHardLinks = 256;
		this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
		this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
	}
}

ScheduledCache & LoggingCache & SynchronizedCache & SerializedCache

  • ScheduledCache 是周期性清理缓存的装饰器,默认一小时清理一次
  • LoggingCache 在 Cache 的基础上提供了日志功能,通过 requestshits 计算缓存的命中率
  • SynchronizedCache 通过在每个方法上添加 synchronized 关键字,为 Cache 添加了同步功能。
  • SerializedCache 提供了将 value 对象序列化的功能。添加缓存时,会将 value 序列化为 byte[] 存入缓存;获取缓存时,会将 byte[] 反序列化成 Java 对象。

Tips:perpetual(adj. 永恒的),delegate(n. 代表)

2.9.3 CacheKey

在 Cache 中唯一确定一个缓存项需要使用缓存项的 key,这个 key 不能仅仅通过一个 String 表示,所以需要定义 CacheKey 类型

public class CacheKey implements Cloneable, Serializable {

	private static final int DEFAULT_MULTIPLIER = 37;
	private static final int DEFAULT_HASHCODE = 17;

    // 参与计算 hashcode,默认 37
	private final int multiplier;
    // CacheKey 对象的 hashcode,初始 17
	private int hashcode;
    // 校验和
	private long checksum;
    // updateList 集合的个数
	private int count;
    // 由该集合中的所有对象共同决定两个 CacheKey 是否相同
	private List<Object> updateList;

	public CacheKey() {
		this.hashcode = DEFAULT_HASHCODE;
		this.multiplier = DEFAULT_MULTIPLIER;
		this.count = 0;
		this.updateList = new ArrayList<>();
	}
}

实际上,CacheKey 对象的 updateList 由四部分组成,分别是:

  1. MappedStatement 的 id
  2. 指定查询结果集的范围,也就是 RowBounds.offset 和 RowBounds.limit
  3. 查询所使用的 SQL 语句,也就是 boundSql.getSql() 方法返回的 SQL 语句,其中可能包含“?”占位符
  4. 用户传递给上述 SQL 语句的实际参数值

2.10 本章小结

  1. XML 解析的基础知识以及解析器模块的具体实现
  2. MyBatis 对 Java 反射的封装,Type 接口的基础知识以及复杂属性表达式在类层面和对象层面的处理
  3. MyBatis 如何实现数据在 Java 类型与 JDBC 类型之间的转换以及别名功能
  4. 日志模块的相关实现
  5. MyBatis 中 JDK 动态代理的应用
  6. DataSource 模块的实现和原理
  7. Transaction 模块如何实现事务
  8. binding 模块如何将 Mapper 与映射配置信息相关联
  9. Cache 模块的实现

标签:缓存,接口,Cache,基础,private,final,支持,public
From: https://blog.csdn.net/weixin_45740473/article/details/142813542

相关文章

  • win11 24H2怎么安装_u盘安装win11 24H2详细步骤【支持新旧机型安装】
    10月1日,微软正式发布了Windows1124H2正式版。对于win1124h2新机器安装肯定是可以的,对于旧电脑在硬件配置上可能无法满足Windows1124h2的最低系统要求,如果按官方要求是无法安装win11的。但是如果采用第三方pe方式安装的话,配置不太低的话还是可以安装win11的。因为我们......
  • apisix~自定义文件上传代理插件~支持form-data文件和kv参数
    参考文献https://stackoverflow.com/questions/24535189/composing-multipart-form-data-with-a-different-content-type-on-each-parts-with-jhttps://www.reddit.com/r/lua/comments/yaizxv/lua_post_multipartformdata_and_a_file_via/?rdt=60519https://github.com/rstudi......
  • 【02】手把手教你0基础部署SpringCloud微服务商城教学-Mybatis篇(下)
    上期回顾:【01】手把手教你0基础部署SpringCloud微服务商城教学-Mybatis篇(上)Part1.续接上文Mybatis-plus的批处理功能接下来我们学习一下IService的批量查询,我们用以往的for循环做一个对比这是for循环部分的代码privateUserbuilderUser(inti){Useruser=new......
  • 【Unity基础】Unity用脚本实现内购(IAP)
    本文介绍了如何使用脚本实现内购功能。先看下脚本,代码中根据执行过程添加了序号。usingUnityEngine;usingUnityEngine.Purchasing;usingUnityEngine.UI;namespaceSamples.Purchasing.Core.BuyingConsumables{publicclassBuyingConsumables:MonoBehaviour,......
  • MySQL基础知识
    基础篇通用语法及分类DDL:数据定义语言,用来定义数据库对象(数据库、表、字段)DML:数据操作语言,用来对数据库表中的数据进行增删改DQL:数据查询语言,用来查询数据库中表的记录DCL:数据控制语言,用来创建数据库用户、控制数据库的控制权限DDL(数据定义语言)数据定义语言数据......
  • 人工智能的高数基础2 导数
    1.概念速度角度:在物理学中,速度是描述物体位置随时间变化快慢的量。假设我们有一个函数f(t1)表示物体在时间t1的位置,f(t2)表示物体在时间t2的位置,那么在t1到t2时间段内,物体移动的距离为f(t2)-f(t1),平均速度为:                             ......
  • 鸿蒙初学002-应用程序包基础知识
    应用与应用程序包用户应用程序泛指运行在设备的操作系统之上,为用户提供特定服务的程序,简称“应用”。一个应用所对应的软件包文件,称为“应用程序包”。当前系统提供了应用程序包开发、安装、查询、更新、卸载的管理机制,便于开发者开发和管理应用。同时,系统还屏蔽了不同的芯片平......
  • 2024年还不知道如何清理c盘?最齐全的C盘清理指南!(非常详细)零基础入门到精通,收藏这一篇就
    这段时间以来,我收到最多的问题还是问如何解决C盘爆满,那么今天就来给大家详细讲述一下该怎么给C盘“瘦身”。我之前在文章《带你全面了解你的C盘!并且给它“瘦身”!》中讲到过C盘各个文件夹的作用,也提到过一些清理C盘的方法,但是它并不全面,大家都知道C盘中存放着很多的系统文件......
  • 2024最新最全:Wireshark抓包详解(非常详细)零基础入门到精通,收藏这篇就够了
    在网络工程、安全分析和网络维护中,经常需要诊断和解决各种网络问题。Wireshark作为一款强大的网络抓包工具,因其支持多种操作系统、网络协议和网络接口,成为了网络工程师、安全专家和学习者的必备工具。本文将详细讲解如何通过Wireshark抓包并分析网络问题,帮助读者更好地理解......
  • Java基础语法-类型转换
    类型转换由于Java是强类型语言,所以要进行有些运算的时候,需要用到类型转换。低------------------------->高byte,short,char->int->long->float->double运算中,不同类型的数据先转化为同一类型,然后再进行运算。强制转换(类型)变量名高-低自动转换低-高inti=128;......