diff --git a/README.md b/README.md index 59713f0..077d221 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,12 @@ ### 编码规范 ### 设计模式 + +## 贡献者 +感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。 + + + + + + \ No newline at end of file diff --git a/docs/Mybatis/基础支持层/4、缓存模块.md b/docs/Mybatis/基础支持层/4、缓存模块.md index e69de29..12e1e73 100644 --- a/docs/Mybatis/基础支持层/4、缓存模块.md +++ b/docs/Mybatis/基础支持层/4、缓存模块.md @@ -0,0 +1,617 @@ +MyBatis中的缓存分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是Cache接口的实现。MyBatis缓存模块的设计 使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。 +## 1 Cache组件 +MyBatis中缓存模块相关的代码位于org.apache.ibatis.cache包下,其中Cache接口是缓存模块中最核心的接口,它定义了所有缓存的基本行为。 +```java +public interface Cache { + + /** + * 获取当前缓存的Id + */ + String getId(); + + /** + * 存入缓存的key和value,key一般为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 +PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用HashMap记录缓存项,也是通过该HashMap对象的方法实现的Cache接口中定义的相应方法。 +```java +public class PerpetualCache implements Cache { + + // Cache对象的唯一标识 + private final String id; + + // 其所有的缓存功能实现,都是基于JDK的HashMap提供的方法 + private Map 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 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 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 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 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 + // 记录的顺序是accessOrder,即,LinkedHashMap.get()方法会改变其中元素的顺序 + keyMap = new LinkedHashMap(size, .75F, true) { + private static final long serialVersionUID = 4267176411845948333L; + + // 当调用LinkedHashMap.put()方法时,该方法会被调用 + @Override + protected boolean removeEldestEntry(Map.Entry 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 hardLinksToAvoidGarbageCollection; + // 引用队列,用于记录已经被GC的缓存项所对应的SoftEntry对象 + private final ReferenceQueue 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 { + private final Object key; + + SoftEntry(Object key, Object value, ReferenceQueue 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 softReference = (SoftReference) 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 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中一级缓存和二级缓存的基础。 \ No newline at end of file diff --git a/docs/Mybatis/核心处理层/4、StatementHandler.md b/docs/Mybatis/核心处理层/4、StatementHandler.md index e69de29..3559e43 100644 --- a/docs/Mybatis/核心处理层/4、StatementHandler.md +++ b/docs/Mybatis/核心处理层/4、StatementHandler.md @@ -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语句 + List query(Statement statement, ResultHandler resultHandler) + throws SQLException; + + Cursor 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 List query(Statement statement, ResultHandler resultHandler) throws SQLException { + return delegate.query(statement, resultHandler); + } + + @Override + public Cursor 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 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 List 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 Cursor 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(); + // 执行节点中配置的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 List 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 Cursor 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语句执行、结果集映射等一系列核心流程。 diff --git a/images/mybatis/Cache组件.png b/images/mybatis/Cache组件.png new file mode 100644 index 0000000..aacd553 Binary files /dev/null and b/images/mybatis/Cache组件.png differ diff --git a/images/mybatis/image-20191223100956713.png b/images/mybatis/image-20191223100956713.png index d470d86..1582cca 100644 Binary files a/images/mybatis/image-20191223100956713.png and b/images/mybatis/image-20191223100956713.png differ