第 2 章 基础支持层(下)

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 模块的实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值