2.6 DataSource
MyBatis 提供了两个 javax.sql.DataSource 接口实现,分别是 PooledDataSource 和 UnpooledDataSource。MyBatis 使用不同的 DataSourceFactory 接口实现创建不同类型的 DataSource。
2.6.1 工厂方法模式
在工厂方法模式中,定义一个用于创建对象的工厂接口,并根据工厂接口的具体实现类决定具体实例化哪一个产品类。
工厂方法由四个角色构成:
- 工厂接口(Factory):核心接口,调用者会直接与工厂接口交互用于获取具体的产品实现类
- 具体工厂类(ConcreteFactory):实现类,用于实例化产品对象
- 产品接口(Product):用于定义产品类的功能,具体工厂类产生的所有产品对象都必须实现该接口
- 具体产品类(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:具体的装饰器实现类,该实现类要向被装饰对象添加某些功能。
使用装饰器模式有两个明显优点:
- 相较于承继,装饰器模式更灵活,也更容易扩展
- 当有新功能需要添加时,只需要添加新的装饰器实现类,无须修改已有类的代码,符合开闭原则
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 的基础上提供了日志功能,通过
requests
和hits
计算缓存的命中率 - 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 由四部分组成,分别是:
- MappedStatement 的 id
- 指定查询结果集的范围,也就是 RowBounds.offset 和 RowBounds.limit
- 查询所使用的 SQL 语句,也就是 boundSql.getSql() 方法返回的 SQL 语句,其中可能包含“?”占位符
- 用户传递给上述 SQL 语句的实际参数值
2.10 本章小结
- XML 解析的基础知识以及解析器模块的具体实现
- MyBatis 对 Java 反射的封装,Type 接口的基础知识以及复杂属性表达式在类层面和对象层面的处理
- MyBatis 如何实现数据在 Java 类型与 JDBC 类型之间的转换以及别名功能
- 日志模块的相关实现
- MyBatis 中 JDK 动态代理的应用
- DataSource 模块的实现和原理
- Transaction 模块如何实现事务
- binding 模块如何将 Mapper 与映射配置信息相关联
- Cache 模块的实现