Merge pull request #9 from doocs/master

merge
pull/16/head
huifer 6 years ago committed by GitHub
commit 2168880b24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -61,3 +61,12 @@
### 编码规范
### 设计模式
## 贡献者
感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<a href="https://opencollective.com/doocs/contributors.svg?width=890&button=true"><img src="https://opencollective.com/doocs/contributors.svg?width=890&button=false" /></a>
<!-- ALL-CONTRIBUTORS-LIST:END -->

@ -0,0 +1,617 @@
MyBatis中的缓存分为一级缓存、二级缓存但在本质上是相同的它们使用的都是Cache接口的实现。MyBatis缓存模块的设计 使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。
## 1 Cache组件
MyBatis中缓存模块相关的代码位于org.apache.ibatis.cache包下其中Cache接口是缓存模块中最核心的接口它定义了所有缓存的基本行为。
```java
public interface Cache {
/**
* 获取当前缓存的Id
*/
String getId();
/**
* 存入缓存的key和valuekey一般为CacheKey对象
*/
void putObject(Object key, Object value);
/**
* 根据key获取缓存值
*/
Object getObject(Object key);
/**
* 删除指定的缓存项
*/
Object removeObject(Object key);
/**
* 清空缓存
*/
void clear();
/**
* 获取缓存的大小
*/
int getSize();
/**
*
* 获取读写锁,可以看到,这个接口方法提供了默认的实现!!
* 这是Java8的新特性只是平时开发时很少用到
*
*/
default ReadWriteLock getReadWriteLock() {
return null;
}
}
```
如下图所示Cache接口的实现类有很多但大部分都是装饰器只有PerpetualCache提供了Cache 接口的基本实现。
![avator](/images/mybatis/Cache组件.png)
### 1.1 PerpetualCache
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用HashMap记录缓存项也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。
```java
public class PerpetualCache implements Cache {
// Cache对象的唯一标识
private final String id;
// 其所有的缓存功能实现都是基于JDK的HashMap提供的方法
private 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();
}
/**
* 其重写了Object中的equals()和hashCode()方法两者都只关心id字段
*/
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
```
下面来看一下cache.decorators包下提供的装饰器它们都直接实现了Cache接口扮演着装饰器的角色。这些装饰器会在PerpetualCache的基础上提供一些额外的功能通过多个组合后满足一个特定的需求。
### 1.2 BlockingCache
BlockingCache是阻塞版本的缓存装饰器它会保证只有一个线程到数据库中查找指定key对应的数据。
```java
public class BlockingCache implements Cache {
// 阻塞超时时长
private long timeout;
// 持有的被装饰者
private final Cache delegate;
// 每个key都有其对应的ReentrantLock锁对象
private final ConcurrentHashMap<Object, ReentrantLock> locks;
// 初始化 持有的持有的被装饰者 和 锁集合
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
}
```
假设线程A在BlockingCache中未查找到keyA对应的缓存项时线程A会获取keyA对应的锁这样后续线程A在查找keyA时其它线程会被阻塞。
```java
// 根据key获取锁对象然后上锁
private void acquireLock(Object key) {
// 获取key对应的锁对象
Lock lock = getLockForKey(key);
// 获取锁,带超时时长
if (timeout > 0) {
try {
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
if (!acquired) { // 超时,则抛出异常
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} catch (InterruptedException e) {
// 如果获取锁失败,则阻塞一段时间
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {
// 上锁
lock.lock();
}
}
private ReentrantLock getLockForKey(Object key) {
// Java8新特性Map系列类中新增的方法
// V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
// 表示若key对应的value为空则将第二个参数的返回值存入该Map集合并返回
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
```
假设线程A从数据库中查找到keyA对应的结果对象后将结果对象放入到BlockingCache中此时线程A会释放keyA对应的锁唤醒阻塞在该锁上的线程。其它线程即可从BlockingCache中获取keyA对应的数据而不是再次访问数据库。
```java
@Override
public void putObject(Object key, Object value) {
try {
// 存入key和其对应的缓存项
delegate.putObject(key, value);
} finally {
// 最后释放锁
releaseLock(key);
}
}
private void releaseLock(Object key) {
ReentrantLock lock = locks.get(key);
// 锁是否被当前线程持有
if (lock.isHeldByCurrentThread()) {
// 是,则释放锁
lock.unlock();
}
}
```
### 1.3 FifoCache和LruCache
在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache是先入先出版本的装饰器当向缓存添加数据时如果缓存项的个数已经达到上限则会将缓存中最老即最早进入缓存的缓存项删除。
```java
public class FifoCache implements Cache {
// 被装饰对象
private final Cache delegate;
// 用一个FIFO的队列记录key的顺序其具体实现为LinkedList
private final Deque<Object> keyList;
// 决定了缓存的容量上限
private int size;
// 国际惯例通过构造方法初始化自己的属性缓存容量上限默认为1024个
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<>();
this.size = 1024;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(int size) {
this.size = size;
}
@Override
public void putObject(Object key, Object value) {
// 存储缓存项之前先在keyList中注册
cycleKeyList(key);
// 存储缓存项
delegate.putObject(key, value);
}
private void cycleKeyList(Object key) {
// 在keyList队列中注册要添加的key
keyList.addLast(key);
// 如果注册这个key会超出容积上限则把最老的一个缓存项清除掉
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
@Override
public Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
// 除了清理缓存项还要清理key的注册列表
@Override
public void clear() {
delegate.clear();
keyList.clear();
}
}
```
LruCache是按照"近期最少使用算法"Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。
```java
public class LruCache implements Cache {
// 被装饰者
private final Cache delegate;
// 这里使用的是LinkedHashMap它继承了HashMap但它的元素是有序的
private Map<Object, Object> keyMap;
// 最近最少被使用的缓存项的key
private Object eldestKey;
// 国际惯例,构造方法中进行属性初始化
public LruCache(Cache delegate) {
this.delegate = delegate;
// 这里初始化了keyMap并定义了eldestKey的取值规则
setSize(1024);
}
public void setSize(final int size) {
// 初始化keyMap同时指定该Map的初始容积及加载因子第三个参数true表示该LinkedHashMap
// 记录的顺序是accessOrderLinkedHashMap.get()方法会改变其中元素的顺序
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
// 当调用LinkedHashMap.put()方法时,该方法会被调用
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
// 当已达到缓存上限更新eldestKey字段后面将其删除
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
// 存储缓存项
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
// 记录缓存项的key超出容量则清除最久未使用的缓存项
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
// eldestKey不为空则表示已经达到缓存上限
if (eldestKey != null) {
// 清除最久未使用的缓存
delegate.removeObject(eldestKey);
// 制空
eldestKey = null;
}
}
@Override
public Object getObject(Object key) {
// 访问key元素 会改变该元素在LinkedHashMap中的顺序
keyMap.get(key); //touch
return delegate.getObject(key);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyMap.clear();
}
}
```
### 1.4 SoftCache和WeakCache
在分析SoftCache和WeakCache实现之前我们再温习一下Java提供的4种引用类型强引用StrongReference、软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
- 强引用
平时用的最多的如Object obj new Object()新建的Object对象就是被强引用的。如果一个对象被强引用即使是JVM内存空间不足要抛出OutOfMemoryError异常GC也绝不会回收该对象。
- 软引用
仅次于强引用的一种引用它使用类SoftReference来表示。当JVM内存不足时GC会回收那些只被软引用指向的对象从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象例如 数据库缓存中的对象就可以从数据库中恢复所以软引用可以用来实现缓存下面要介绍的SoftCache就是通过软引用实现的。
另外由于在程序使用软引用之前的某个时刻其所指向的对象可能己经被GC回收掉了所以通过 Reference.get()方法来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。
- 弱引用
弱引用使用WeakReference表示它不会阻止所引用的对象被GC回收。在JVM进行垃圾回收时如果指向一个对象的所有引用都是弱引用那么该对象会被回收。
所以只被弱引用所指向的对象其生存周期是两次GC之间的这段时间而只被软引用所指向的对象可以经历多次GC直到出现内存紧张的情况才被回收。
- 虚引用
最弱的一种引用类型由类PhantomReference表示。虚引用可以用来实现比较精细的内存使用控制但很少使用。
- 引用队列ReferenceQueue )
很多场景下我们的程序需要在一个对象被GC时得到通知引用队列就是用于收集这些信息的队列。在创建SoftReference对象时可以为其关联一个引用队列当SoftReference所引用的对象被GC时 JVM就会将该SoftReference对象添加到与之关联的引用队列中。当需要检测这些通知信息时就可以从引用队列中获取这些SoftReference对象。不仅是SoftReference弱引用和虚引用都可以关联相应的队列。
现在来看一下SoftCache的具体实现。
```java
public class SoftCache implements Cache {
// 这里使用了LinkedList作为容器在SoftCache中最近使用的一部分缓存项不会被GC
// 这是通过将其value添加到hardLinksToAvoidGarbageCollection集合实现的有强引用指向其value
private final Deque<Object> hardLinksToAvoidGarbageCollection;
// 引用队列用于记录已经被GC的缓存项所对应的SoftEntry对象
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
// 持有的被装饰者
private final Cache delegate;
// 强连接的个数默认为256
private int numberOfHardLinks;
// 构造方法进行属性的初始化
public SoftCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
private static class SoftEntry extends SoftReference<Object> {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
// 指向value的引用是软引用并且关联了 引用队列
super(value, garbageCollectionQueue);
// 强引用
this.key = key;
}
}
@Override
public void putObject(Object key, Object value) {
// 清除已经被GC的缓存项
removeGarbageCollectedItems();
// 添加缓存
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
private void removeGarbageCollectedItems() {
SoftEntry sv;
// 遍历queueOfGarbageCollectedEntries集合清除已经被GC的缓存项value
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
// 用一个软引用指向 key对应的缓存项
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
// 检测缓存中是否有对应的缓存项
if (softReference != null) {
// 获取softReference引用的value
result = softReference.get();
// 如果softReference引用的对象已经被GC则从缓存中清除对应的缓存项
if (result == null) {
delegate.removeObject(key);
} else {
synchronized (hardLinksToAvoidGarbageCollection) {
// 将缓存项的value添加到hardLinksToAvoidGarbageCollection集合中保存
hardLinksToAvoidGarbageCollection.addFirst(result);
// 如果hardLinksToAvoidGarbageCollection的容积已经超过numberOfHardLinks
// 则将最老的缓存项从hardLinksToAvoidGarbageCollection中清除FIFO
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
// 清除指定的缓存项之前也会先清理被GC的缓存项
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
// 清理强引用集合
hardLinksToAvoidGarbageCollection.clear();
}
// 清理被GC的缓存项
removeGarbageCollectedItems();
// 清理最底层的缓存项
delegate.clear();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
}
```
WeakCache的实现与SoftCache基本类似唯一的区别在于其中使用WeakEntry继承了WeakReference封装真正的 value 对象,其他实现完全一样。
另外还有ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache等。ScheduledCache是周期性清理缓存的装饰器它的clearInterval字段记录了两次缓存清理之间的时间间隔默认是一小时lastClear字段记录了最近一次清理的时间戳。ScheduledCache 的getObject()、putObject()、removeObject()等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 字段和 request 字段记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。
SynchronizedCache通过在每个方法上添加 synchronized关键字为Cache添加了同步功能有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类。
SerializedCache 提供了将 value 对象序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象进行序列化,井将序列化后的 byte[] 数组作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[] 数组反序列化成 Java 对象。不使用 SerializedCache 装饰器进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而 使用SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java 原生序列化。
## 2 CacheKey
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key进行比较MyBatis 中因为涉及动态 SQL 等 多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey 类来表示缓存项的 key在一个 CacheKey 对象中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象是否相同。
```java
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
// 参与计算hashcode默认值DEFAULT_MULTIPLYER = 37
private final int multiplier;
// 当前CacheKey对象的hashcode默认值DEFAULT_HASHCODE = 17
private int hashcode;
// 校验和
private long checksum;
private int count;
// 由该集合中的所有元素 共同决定两个CacheKey对象是否相同一般会使用一下四个元素
// MappedStatement的id、查询结果集的范围参数RowBounds的offset和limit
// SQL语句其中可能包含占位符"?"、SQL语句中占位符的实际参数
private List<Object> updateList;
// 构造方法初始化属性
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<>();
}
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
// 重新计算count、checksum和hashcode的值
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
// 将object添加到updateList集合
updateList.add(object);
}
public int getUpdateCount() {
return updateList.size();
}
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
/**
* CacheKey重写了equals()和hashCode()方法,这两个方法使用上面介绍
* 的count、checksum、hashcode、updateList比较两个CacheKey对象是否相同
*/
@Override
public boolean equals(Object object) {
// 如果为同一对象直接返回true
if (this == object) {
return true;
}
// 如果object都不是CacheKey类型直接返回false
if (!(object instanceof CacheKey)) {
return false;
}
// 类型转换一下
final CacheKey cacheKey = (CacheKey) object;
// 依次比较hashcode、checksum、count如果不等直接返回false
if (hashcode != cacheKey.hashcode) {
return false;
}
if (checksum != cacheKey.checksum) {
return false;
}
if (count != cacheKey.count) {
return false;
}
// 比较updateList中的元素是否相同不同直接返回false
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return hashcode;
}
@Override
public String toString() {
StringJoiner returnValue = new StringJoiner(":");
returnValue.add(String.valueOf(hashcode));
returnValue.add(String.valueOf(checksum));
updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);
return returnValue.toString();
}
@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<>(updateList);
return clonedCacheKey;
}
}
```
## 3 小结
至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java 反射机制的封装;然后分析了类型转换 TypeHandler 组件,了解了 MyBatis 如何实现数据在 Java 类型与 JDBC 类型之间的转换。
之后分析了MyBatis 提供的 DataSource 模块的实现和原理,深入解析了 MyBatis 自带的连接池PooledDataSource 的详细实现;后面紧接着介绍了 Transaction 模块的功能。然后分析了 binding 模块如何将 Mapper 接口与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache 接口以及多个实现类的具体实现它们是Mybatis中一级缓存和二级缓存的基础。

@ -0,0 +1,484 @@
StatementHandler接口是MyBatis的核心接口之一它完成了MyBatis中最核心的工作也是Executor 接口实现的基础。
StatementHandler接口中的功能很多例如创建Statement对象为SQL语句绑定实参执行select、insert、update、delete等多种类型的SQL语句批量执行SQL语句将结果集映射成结果对象。
```java
public interface StatementHandler {
// 从连接中获取一个Statement
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// 绑定statement执行时所需的实参
void parameterize(Statement statement)
throws SQLException;
// 批量执行SQL语句
void batch(Statement statement)
throws SQLException;
// 执行update/insert/delete语句
int update(Statement statement)
throws SQLException;
// 执行select语句
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
// 获取参数处理器
ParameterHandler getParameterHandler();
}
```
## RoutingStatementHandler
RoutingStatementHandler使用了策略模式RoutingStatementHandler是策略类而SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler则是实现了具体算法的实现类RoutingStatementHandler对象会根据MappedStatement对象的StatementType属性值选择使用相应的策略去执行。
```java
public class RoutingStatementHandler implements StatementHandler {
// 持有的真正实现StatementHandler接口功能的对象
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// RoutingStatementHandler的作用就是根据ms的配置生成一个相对应的StatementHandler对象
// 并设置到持有的delegate属性中本对象的所有方法都是通过调用delegate的相应方法实现的
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
@Override
public void batch(Statement statement) throws SQLException {
delegate.batch(statement);
}
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
return delegate.queryCursor(statement);
}
@Override
public BoundSql getBoundSql() {
return delegate.getBoundSql();
}
@Override
public ParameterHandler getParameterHandler() {
return delegate.getParameterHandler();
}
}
```
## BaseStatementHandler
看它以Base开头就可以猜到 它是一个实现了StatementHandler接口的抽象类这个类只提供了一些参数绑定相关的方法并没有实现操作数据库的方法。
```java
public abstract class BaseStatementHandler implements StatementHandler {
// 持有的这些属性都是通过构造方法完成初始化的typeHandlerRegistry、
// objectFactory、parameterHandler等则是通过configuration属性获得的
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
// parameterHandler的功能主要是为SQL语句绑定实参也就是使用传入的实参
// 替换SQL语句中的占位符"?"
protected final ParameterHandler parameterHandler;
// 用来执行SQL语句的执行器
protected final Executor executor;
protected final MappedStatement mappedStatement;
// 记录了用户设置的offset和limit用于在结果集中定位
// 映射的起始位置和结束位置
protected final RowBounds rowBounds;
protected BoundSql boundSql;
// BaseStatementHandler的构造方法主要用于属性的初始化
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
// 其中调用了KeyGenerator的processBefore()方法
// 用于初始化SQL语句的主键
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}
@Override
public BoundSql getBoundSql() {
return boundSql;
}
@Override
public ParameterHandler getParameterHandler() {
return parameterHandler;
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 这是一个抽象方法用于初始化java.sql.Statement对象
statement = instantiateStatement(connection);
// 为Statement对象设置超时时间及fetchSize
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
Integer queryTimeout = null;
if (mappedStatement.getTimeout() != null) {
queryTimeout = mappedStatement.getTimeout();
} else if (configuration.getDefaultStatementTimeout() != null) {
queryTimeout = configuration.getDefaultStatementTimeout();
}
if (queryTimeout != null) {
stmt.setQueryTimeout(queryTimeout);
}
StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
}
protected void setFetchSize(Statement stmt) throws SQLException {
Integer fetchSize = mappedStatement.getFetchSize();
if (fetchSize != null) {
stmt.setFetchSize(fetchSize);
return;
}
Integer defaultFetchSize = configuration.getDefaultFetchSize();
if (defaultFetchSize != null) {
stmt.setFetchSize(defaultFetchSize);
}
}
protected void closeStatement(Statement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
//ignore
}
}
}
```
BaseStatementHandler主要实现了StatementHandler接口中的prepare()方法BaseStatementHandler依赖两个重要的组件ParameterHandler和ResultSetHandler。
## ParameterHandler系列组件
我们要执行的SQL语句中可能包含占位符"?",而每个"?"都对应了BoundSql中parameterMappings集合中的一个元素在该ParameterMapping对象中记录了对应的参数名称以及该参数的相关属性。ParameterHandler接口定义了一个非常重要的方法setParameters()该方法主要负责调用PreparedStatement的set()系列方法为SQL语句绑定实参。MyBatis只为ParameterHandler接口提供了唯一一个实现类DefaultParameterHandler。
```java
public interface ParameterHandler {
// 获取用户传入的实参对象
Object getParameterObject();
// 本方法主要负责调用PreparedStatement.set*()方法为SQL语句绑定实参。
void setParameters(PreparedStatement ps)
throws SQLException;
}
public class DefaultParameterHandler implements ParameterHandler {
// 管理mybatis中所有的TypeHandler对象
private final TypeHandlerRegistry typeHandlerRegistry;
// 其中记录了SQL节点相应的配置信息
private final MappedStatement mappedStatement;
// 用户传入的实参对象
private final Object parameterObject;
// 其中记录了要执行的SQL语句及参数信息
private final BoundSql boundSql;
private final Configuration configuration;
// 构造方法主要为持有的属性 进行初始化
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
// 为PreparedStatement对象要执行的SQL语句中的占位符 设置对应的参数值
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 获取参数列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 过滤掉存储过程中的输出参数
if (parameterMapping.getMode() != ParameterMode.OUT) {
// 记录绑定的实参
Object value;
// 获取参数对应的属性名
String propertyName = parameterMapping.getProperty();
// 根据属性名 获取 实参值
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
// 整个实参为空
} else if (parameterObject == null) {
value = null;
// 如果实参可以直接通过TypeHandler转换成JdbcType
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
// 获取对象中相应的属性值 或查找Map对象中的值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取当前parameterMapping中的TypeHandler对象 及JdbcType对象
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// TypeHandler的setParameter()方法会调用PreparedStatement对象的
// set*()系列方法为SQL语句绑定相应的实参
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
```
为SQL语句绑定完实参之后就可以调用Statement对象 相应的execute方法将SQL语句交给数据库执行了。
## SimpleStatementHandler
SimpleStatementHandler继承了BaseStatementHandler抽象类。其底层使用java.sql.Statement来完成数据库的相关操作所以SQL语句中不存在占位符所以SimpleStatementHandler的parameterize()方法是空实现。SimpleStatementHandler的instantiateStatement()方法直接通过JDBC Connection创建Statement对象。
```java
public class SimpleStatementHandler extends BaseStatementHandler {
// 构造方法主要用于属性的初始化
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
// 直接通过Connection创建Statement对象
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// 如果结果集类型是DEFAULT默认的则直接用connection创建Statement对象
return connection.createStatement();
} else {
// 否则,设置结果集类型,设置结果集 只读
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
// 上面创建的Statement对象会被本方法用于完成数据库查询操作
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 获取SQL语句
String sql = boundSql.getSql();
// 发送请求 执行SQL语句
statement.execute(sql);
// 从statement中获取结果集并进行映射处理
return resultSetHandler.handleResultSets(statement);
}
// 下面的batch()及queryCursor()方法的实现与上面的query()方法非常类似
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.addBatch(sql);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleCursorResultSets(statement);
}
// 本方法用于执行insert、delete、update等类型的SQL语句并且会根据配置的
// KeyGenerator获取数据库生成的主键
@Override
public int update(Statement statement) throws SQLException {
// 获取SQL语句 及parameterObject
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
// 获取配置的KeyGenerator 数据库主键生成器
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
// 执行SQL语句
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
// 获取更新的条数
rows = statement.getUpdateCount();
// 将数据库生成的主键添加到parameterObject中
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
// 执行SQL语句
statement.execute(sql);
// 获取更新的条数
rows = statement.getUpdateCount();
// 执行<selectKey>节点中配置的SQL语句将从数据库获取到的主键 添加到parameterObject中
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
@Override
public void parameterize(Statement statement) {
// N/A
}
}
```
## PreparedStatementHandler
PreparedStatementHandler底层依赖于java.sql.PreparedStatement来完成数据库的相关操作。其中的parameterize()方法中会调用前面介绍的ParameterHandler的setParameters()方法 完成 SQL语句的参数绑定。instantiateStatement()方法直接调用JDBC Connection的prepareStatement()方法创建PreparedStatement对象。
```java
public class PreparedStatementHandler extends BaseStatementHandler {
// 构造方法主要用于属性的初始化
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 获取SQL语句
String sql = boundSql.getSql();
// 根据mappedStatement持有的KeyGenerator的类型进行不同的处理
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
// 获取主键列
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
// 返回数据库生成的主键
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
// 在insert语句执行完后会将keyColumnNames指定的列返回
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// 如果结果集类型是DEFAULT默认的则直接通过connection获取PreparedStatement对象
return connection.prepareStatement(sql);
} else {
// 否则,设置结果集类型,设置结果集为只读
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
// 因为是PrepareStatement对象所以需要处理占位符"?"
// 使用了前面介绍的ParameterHandler组件完成
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
// 下面的这些方法,除了多了一步 将Statement对象强转成PreparedStatement对象
// 其它的几乎与SimpleStatementHandler一样
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.addBatch();
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleCursorResultSets(ps);
}
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
}
```
另外StatementHandler接口还有一个CallableStatementHandler的实现。其底层依赖于java.sql.CallableStatement调用指定的存储过程其parameterize()方法也会调用ParameterHandler的setParameters()方法完成SQL语句的参数绑定并指定输出参数的索引位置和JDBC类型。其余方法与前面介绍的ResultSetHandler实现类似唯一区别是会调用ResultSetHandler的handleOutputParameters()方法 处理输出参数。
看到这里我们可以发现StatementHandler组件依赖ParameterHandler组件 和 ResultSetHandler组件 完成了MyBatis的核心功能它控制着参数绑定、SQL语句执行、结果集映射等一系列核心流程。

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Loading…
Cancel
Save