@ -0,0 +1,319 @@
|
|||||||
|
# mybatis 缓存
|
||||||
|
- Author: [HuiFer](https://github.com/huifer)
|
||||||
|
- Description: 该文介绍 mybatis Cache 源码
|
||||||
|
- `org.apache.ibatis.cache.Cache`
|
||||||
|
```java
|
||||||
|
public interface Cache {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 放入数据
|
||||||
|
*/
|
||||||
|
void putObject(Object key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据
|
||||||
|
*/
|
||||||
|
Object getObject(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除数据
|
||||||
|
*/
|
||||||
|
Object removeObject(Object key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空数据
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有多少缓存数据
|
||||||
|
*/
|
||||||
|
int getSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重入锁
|
||||||
|
* @return A ReadWriteLock
|
||||||
|
*/
|
||||||
|
default ReadWriteLock getReadWriteLock() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- BlockingCache: 阻塞的缓存
|
||||||
|
- FifoCache: 按对象进入缓存的顺序来移除它们。
|
||||||
|
- LruCache: 最近最少使用的:移除最长时间不被使用的对象。
|
||||||
|
- SoftCache: 软引用:移除基于垃圾回收器状态和软引用规则的对象
|
||||||
|
- WeakCache: 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
|
||||||
|
|
||||||
|
## BlockingCache
|
||||||
|
- BlockingCache 内部使用了`ReentrantLock`来进行加锁开锁这个操作.在插入缓存时上锁,插入缓存后释放.请求缓存值得时候同理
|
||||||
|
```java
|
||||||
|
public class BlockingCache implements Cache {
|
||||||
|
|
||||||
|
private final Cache delegate;
|
||||||
|
/**
|
||||||
|
* 线程安全的map
|
||||||
|
*/
|
||||||
|
private final ConcurrentHashMap<Object, ReentrantLock> locks;
|
||||||
|
private long timeout;
|
||||||
|
|
||||||
|
public BlockingCache(Cache delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.locks = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return delegate.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return delegate.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putObject(Object key, Object value) {
|
||||||
|
try {
|
||||||
|
delegate.putObject(key, value);
|
||||||
|
} finally {
|
||||||
|
releaseLock(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(Object key) {
|
||||||
|
acquireLock(key);
|
||||||
|
Object value = delegate.getObject(key);
|
||||||
|
if (value != null) {
|
||||||
|
// 释放锁
|
||||||
|
releaseLock(key);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object removeObject(Object key) {
|
||||||
|
// despite of its name, this method is called only to release locks
|
||||||
|
releaseLock(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
delegate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ReentrantLock getLockForKey(Object key) {
|
||||||
|
return locks.computeIfAbsent(key, k -> new ReentrantLock());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求锁
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
private void acquireLock(Object 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放锁
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
private void releaseLock(Object key) {
|
||||||
|
ReentrantLock lock = locks.get(key);
|
||||||
|
if (lock.isHeldByCurrentThread()) {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeout() {
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimeout(long timeout) {
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## FifoCache
|
||||||
|
- 存储结构是`java.util.LinkedList`
|
||||||
|
```java
|
||||||
|
public class FifoCache implements Cache {
|
||||||
|
|
||||||
|
private final Cache delegate;
|
||||||
|
/**
|
||||||
|
* 队列
|
||||||
|
*/
|
||||||
|
private final Deque<Object> keyList;
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
cycleKeyList(key);
|
||||||
|
delegate.putObject(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(Object key) {
|
||||||
|
return delegate.getObject(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object removeObject(Object key) {
|
||||||
|
return delegate.removeObject(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
delegate.clear();
|
||||||
|
keyList.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加 key 删除最开始的一个
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
private void cycleKeyList(Object key) {
|
||||||
|
keyList.addLast(key);
|
||||||
|
if (keyList.size() > size) {
|
||||||
|
Object oldestKey = keyList.removeFirst();
|
||||||
|
delegate.removeObject(oldestKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## LruCache
|
||||||
|
- 存储结构是`java.util.LinkedHashMap`
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* Lru (least recently used) cache decorator.
|
||||||
|
* LRU 緩存策略 最近最少使用的:移除最长时间不被使用的对象。
|
||||||
|
*
|
||||||
|
* @author Clinton Begin
|
||||||
|
*/
|
||||||
|
public class LruCache implements Cache {
|
||||||
|
|
||||||
|
private final Cache delegate;
|
||||||
|
/**
|
||||||
|
* {@link LinkedHashMap}
|
||||||
|
*/
|
||||||
|
private Map<Object, Object> keyMap;
|
||||||
|
private Object eldestKey;
|
||||||
|
|
||||||
|
public LruCache(Cache delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
setSize(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return delegate.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return delegate.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置大小
|
||||||
|
*
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
|
public void setSize(final int size) {
|
||||||
|
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
|
||||||
|
private static final long serialVersionUID = 4267176411845948333L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
|
||||||
|
// 数量超出预设值 执行
|
||||||
|
boolean tooBig = size() > size;
|
||||||
|
if (tooBig) {
|
||||||
|
// 获取被移除的key
|
||||||
|
eldestKey = eldest.getKey();
|
||||||
|
}
|
||||||
|
return tooBig;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putObject(Object key, Object value) {
|
||||||
|
delegate.putObject(key, value);
|
||||||
|
cycleKeyList(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject(Object key) {
|
||||||
|
keyMap.get(key); //touch
|
||||||
|
return delegate.getObject(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object removeObject(Object key) {
|
||||||
|
return delegate.removeObject(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
delegate.clear();
|
||||||
|
keyMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除最早的一个key
|
||||||
|
* @param key
|
||||||
|
*/
|
||||||
|
private void cycleKeyList(Object key) {
|
||||||
|
keyMap.put(key, key);
|
||||||
|
if (eldestKey != null) {
|
||||||
|
delegate.removeObject(eldestKey);
|
||||||
|
eldestKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,90 @@
|
|||||||
|
# Mybatis Alias
|
||||||
|
- Author: [HuiFer](https://github.com/huifer)
|
||||||
|
- Description: 该文介绍 mybatis Alias 源码
|
||||||
|
- 源码位置 :`org.apache.ibatis.type.Alias`
|
||||||
|
- 与 Alias 相关的一个方法`org.apache.ibatis.type.TypeAliasRegistry.registerAlias(java.lang.String, java.lang.Class<?>)`(别名注册)
|
||||||
|
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 别名注册,
|
||||||
|
* typeAliases 是一个map key=>别名,value=>字节码
|
||||||
|
*
|
||||||
|
* @param alias 别名名称
|
||||||
|
* @param value 别名的字节码
|
||||||
|
*/
|
||||||
|
public void registerAlias(String alias, Class<?> value) {
|
||||||
|
if (alias == null) {
|
||||||
|
throw new TypeException("The parameter alias cannot be null");
|
||||||
|
}
|
||||||
|
// issue #748
|
||||||
|
String key = alias.toLowerCase(Locale.ENGLISH);
|
||||||
|
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
|
||||||
|
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
|
||||||
|
}
|
||||||
|
typeAliases.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- registerAlias 操作的对象是一个`map`对象
|
||||||
|
```java
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 别名存放仓库
|
||||||
|
* 是一个map key=>别名,value=>字节码
|
||||||
|
*/
|
||||||
|
private final Map<String, Class<?>> typeAliases = new HashMap<>();
|
||||||
|
```
|
||||||
|
不难看出这个对象存放的内容是 别名 -> clazz.
|
||||||
|
- 相关注解`Alias`
|
||||||
|
```java
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface Alias {
|
||||||
|
/**
|
||||||
|
* Return the alias name.
|
||||||
|
*
|
||||||
|
* @return the alias name
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 看一下实现方式
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 加载{@link Alias} 注解的内容
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
public void registerAlias(Class<?> type) {
|
||||||
|
String alias = type.getSimpleName();
|
||||||
|
Alias aliasAnnotation = type.getAnnotation(Alias.class);
|
||||||
|
if (aliasAnnotation != null) {
|
||||||
|
// 获取 别名注解
|
||||||
|
alias = aliasAnnotation.value();
|
||||||
|
}
|
||||||
|
// 转换为 别名,clazz
|
||||||
|
registerAlias(alias, type);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
最后回到了`org.apache.ibatis.type.TypeAliasRegistry.registerAlias(java.lang.String, java.lang.Class<?>)`方法
|
||||||
|
我们可以简单编写一个测试类
|
||||||
|
```java
|
||||||
|
@Alias(value = "hc")
|
||||||
|
public class Hc {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对注解 {@link Alias} 的测试用例
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testAnnotation() {
|
||||||
|
TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
|
||||||
|
typeAliasRegistry.registerAlias(Hc.class);
|
||||||
|
assertEquals("org.apache.ibatis.type.Hc", typeAliasRegistry.resolveAlias("hc").getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
其他与`Alias`相关的测试类位于: `org.apache.ibatis.type.TypeAliasRegistryTest`
|
@ -0,0 +1,358 @@
|
|||||||
|
# Mybatis DataSource
|
||||||
|
- Author: [HuiFer](https://github.com/huifer)
|
||||||
|
- Description: 该文介绍 mybatis DataSource 源码
|
||||||
|
- `org.apache.ibatis.datasource.DataSourceFactory`
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 数据源工厂
|
||||||
|
* @author Clinton Begin
|
||||||
|
*/
|
||||||
|
public interface DataSourceFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 dataSource 属性
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
void setProperties(Properties props);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 dataSource
|
||||||
|
* @return {@link DataSource}
|
||||||
|
*/
|
||||||
|
DataSource getDataSource();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
类图如下
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- `setProperties`会将下列标签放入`datasource`中
|
||||||
|
|
||||||
|
```java
|
||||||
|
<dataSource type="POOLED">
|
||||||
|
<property name="driver" value="com.mysql.jdbc.Driver"/>
|
||||||
|
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false"/>
|
||||||
|
<property name="username" value="mybatis"/>
|
||||||
|
<property name="password" value="mybatis"/>
|
||||||
|
</dataSource>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 在`org.apache.ibatis.session.Configuration`中有配置下面三个信息
|
||||||
|
```java
|
||||||
|
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
|
||||||
|
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
|
||||||
|
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## JndiDataSourceFactory
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* @author Clinton Begin
|
||||||
|
*/
|
||||||
|
public class JndiDataSourceFactory implements DataSourceFactory {
|
||||||
|
|
||||||
|
public static final String INITIAL_CONTEXT = "initial_context";
|
||||||
|
public static final String DATA_SOURCE = "data_source";
|
||||||
|
public static final String ENV_PREFIX = "env.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 直接 java 数据源
|
||||||
|
*/
|
||||||
|
private DataSource dataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据源的配置信息
|
||||||
|
* @param allProps
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Properties getEnvProperties(Properties allProps) {
|
||||||
|
final String PREFIX = ENV_PREFIX;
|
||||||
|
Properties contextProperties = null;
|
||||||
|
for (Entry<Object, Object> entry : allProps.entrySet()) {
|
||||||
|
String key = (String) entry.getKey();
|
||||||
|
String value = (String) entry.getValue();
|
||||||
|
// 只获取前缀`env`
|
||||||
|
if (key.startsWith(PREFIX)) {
|
||||||
|
if (contextProperties == null) {
|
||||||
|
contextProperties = new Properties();
|
||||||
|
}
|
||||||
|
// 放入数据
|
||||||
|
contextProperties.put(key.substring(PREFIX.length()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contextProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置数据源属性
|
||||||
|
* @param properties
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setProperties(Properties properties) {
|
||||||
|
try {
|
||||||
|
InitialContext initCtx;
|
||||||
|
Properties env = getEnvProperties(properties);
|
||||||
|
if (env == null) {
|
||||||
|
initCtx = new InitialContext();
|
||||||
|
} else {
|
||||||
|
initCtx = new InitialContext(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties.containsKey(INITIAL_CONTEXT)
|
||||||
|
&& properties.containsKey(DATA_SOURCE)) {
|
||||||
|
// 如果包含`initial_context`和`data_source`
|
||||||
|
Context ctx = (Context) initCtx.lookup(properties.getProperty(INITIAL_CONTEXT));
|
||||||
|
dataSource = (DataSource) ctx.lookup(properties.getProperty(DATA_SOURCE));
|
||||||
|
} else if (properties.containsKey(DATA_SOURCE)) {
|
||||||
|
dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (NamingException e) {
|
||||||
|
throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PooledDataSource
|
||||||
|
|
||||||
|
```java
|
||||||
|
protected int poolMaximumActiveConnections = 10;
|
||||||
|
protected int poolMaximumIdleConnections = 5;
|
||||||
|
protected int poolMaximumCheckoutTime = 20000;
|
||||||
|
protected int poolTimeToWait = 20000;
|
||||||
|
protected int poolMaximumLocalBadConnectionTolerance = 3;
|
||||||
|
protected String poolPingQuery = "NO PING QUERY SET";
|
||||||
|
protected boolean poolPingEnabled;
|
||||||
|
protected int poolPingConnectionsNotUsedFor;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## PooledDataSourceFactory
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
|
||||||
|
|
||||||
|
|
||||||
|
public PooledDataSourceFactory() {
|
||||||
|
this.dataSource = new PooledDataSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
public PooledDataSource() {
|
||||||
|
dataSource = new UnpooledDataSource();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## UnpooledDataSourceFactory
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public void setProperties(Properties properties) {
|
||||||
|
Properties driverProperties = new Properties();
|
||||||
|
//metaDataSource 现在是一个dataSource
|
||||||
|
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
|
||||||
|
for (Object key : properties.keySet()) {
|
||||||
|
String propertyName = (String) key;
|
||||||
|
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
|
||||||
|
// 如果是 driver. 前缀开头
|
||||||
|
String value = properties.getProperty(propertyName);
|
||||||
|
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
|
||||||
|
} else if (metaDataSource.hasSetter(propertyName)) {
|
||||||
|
String value = (String) properties.get(propertyName);
|
||||||
|
Object convertedValue = convertValue(metaDataSource, propertyName, value);
|
||||||
|
// 通过 metaDataSource 来对 dataSource 进行设置属性
|
||||||
|
metaDataSource.setValue(propertyName, convertedValue);
|
||||||
|
} else {
|
||||||
|
throw new DataSourceException("Unknown DataSource property: " + propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (driverProperties.size() > 0) {
|
||||||
|
metaDataSource.setValue("driverProperties", driverProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## UnpooledDataSource
|
||||||
|
|
||||||
|
- `org.apache.ibatis.datasource.unpooled.UnpooledDataSource`主要定义数据库连接相关的一些属性,以及与数据库的链接对象创建
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 一些配置信息
|
||||||
|
private ClassLoader driverClassLoader;
|
||||||
|
private Properties driverProperties;
|
||||||
|
private String driver;
|
||||||
|
private String url;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private Boolean autoCommit;
|
||||||
|
private Integer defaultTransactionIsolationLevel;
|
||||||
|
private Integer defaultNetworkTimeout;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 初始化连接对象
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 加载链接驱动 如 mysql 链接驱动
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
private synchronized void initializeDriver() throws SQLException {
|
||||||
|
if (!registeredDrivers.containsKey(driver)) {
|
||||||
|
Class<?> driverType;
|
||||||
|
try {
|
||||||
|
if (driverClassLoader != null) {
|
||||||
|
driverType = Class.forName(driver, true, driverClassLoader);
|
||||||
|
} else {
|
||||||
|
driverType = Resources.classForName(driver);
|
||||||
|
}
|
||||||
|
// DriverManager requires the driver to be loaded via the system ClassLoader.
|
||||||
|
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
|
||||||
|
Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
|
||||||
|
DriverManager.registerDriver(new DriverProxy(driverInstance));
|
||||||
|
registeredDrivers.put(driver, driverInstance);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 设置连接对象的属性
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 设置连接对象 , 超时时间,是否自动提交事物
|
||||||
|
* @param conn
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
private void configureConnection(Connection conn) throws SQLException {
|
||||||
|
if (defaultNetworkTimeout != null) {
|
||||||
|
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
|
||||||
|
}
|
||||||
|
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
|
||||||
|
conn.setAutoCommit(autoCommit);
|
||||||
|
}
|
||||||
|
if (defaultTransactionIsolationLevel != null) {
|
||||||
|
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 获取连接对象
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 获取链接对象
|
||||||
|
* @param username
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
private Connection doGetConnection(String username, String password) throws SQLException {
|
||||||
|
Properties props = new Properties();
|
||||||
|
if (driverProperties != null) {
|
||||||
|
props.putAll(driverProperties);
|
||||||
|
}
|
||||||
|
if (username != null) {
|
||||||
|
props.setProperty("user", username);
|
||||||
|
}
|
||||||
|
if (password != null) {
|
||||||
|
props.setProperty("password", password);
|
||||||
|
}
|
||||||
|
return doGetConnection(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 解析流程
|
||||||
|
|
||||||
|
- 在xml解析的过程中会执行`DataSourceFactory`相关内容
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 解析 dataSourceElement 标签
|
||||||
|
* <dataSource type="POOLED">
|
||||||
|
* <property name="driver" value="com.mysql.jdbc.Driver"/>
|
||||||
|
* <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
|
||||||
|
* <property name="username" value="root"/>
|
||||||
|
* <property name="password" value="root"/>
|
||||||
|
* </dataSource>
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
|
||||||
|
if (context != null) {
|
||||||
|
String type = context.getStringAttribute("type");
|
||||||
|
Properties props = context.getChildrenAsProperties();
|
||||||
|
//org.apache.ibatis.session.Configuration.Configuration()
|
||||||
|
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
|
||||||
|
|
||||||
|
// PooledDataSourceFactory -> UnpooledDataSourceFactory
|
||||||
|
factory.setProperties(props);
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
从类图上或者代码中我们可以发现`PooledDataSourceFactory`是继承`UnpooledDataSourceFactory`那么方法应该也是`UnpooledDataSourceFactory`的。看看设置属性方法
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
方法直接走完
|
||||||
|
|
||||||
|

|
||||||
|
|
@ -0,0 +1,433 @@
|
|||||||
|
# Mybatis DyanmicSqlSourcce
|
||||||
|
- Author: [HuiFer](https://github.com/huifer)
|
||||||
|
|
||||||
|
- `org.apache.ibatis.scripting.xmltags.DynamicSqlSource`
|
||||||
|
- `org.apache.ibatis.scripting.xmltags.DynamicContext.DynamicContext`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```XML
|
||||||
|
<select id="list" resultType="com.huifer.mybatis.entity.HsSell">
|
||||||
|
select * from hs_sell
|
||||||
|
<trim prefix="WHERE" prefixOverrides="AND |OR">
|
||||||
|
<if test="ID != null">
|
||||||
|
and ID = #{ID,jdbcType=INTEGER}
|
||||||
|
</if>
|
||||||
|
|
||||||
|
</trim>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MixedSqlNode implements SqlNode {
|
||||||
|
private final List<SqlNode> contents;
|
||||||
|
|
||||||
|
public MixedSqlNode(List<SqlNode> contents) {
|
||||||
|
this.contents = contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(DynamicContext context) {
|
||||||
|
// 调用 salNode 对象本身的 apply 方法解析 sql
|
||||||
|
contents.forEach(node -> node.apply(context));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 根据mapper.xml文件中的代码流程 需要走
|
||||||
|
|
||||||
|
`org.apache.ibatis.scripting.xmltags.StaticTextSqlNode#apply`
|
||||||
|
|
||||||
|
`org.apache.ibatis.scripting.xmltags.TrimSqlNode#apply`
|
||||||
|
|
||||||
|
`org.apache.ibatis.scripting.xmltags.IfSqlNode#apply`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* @author Clinton Begin
|
||||||
|
*/
|
||||||
|
public class StaticTextSqlNode implements SqlNode {
|
||||||
|
private final String text;
|
||||||
|
|
||||||
|
public StaticTextSqlNode(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静态文本apply 方法
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean apply(DynamicContext context) {
|
||||||
|
context.appendSql(text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `org.apache.ibatis.scripting.xmltags.DynamicContext#appendSql`
|
||||||
|
|
||||||
|
```JAVA
|
||||||
|
public void appendSql(String sql) {
|
||||||
|
sqlBuilder.add(sql);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 解析`trim`标签
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 在解析`trim`的时候会往下解析下级标签
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public boolean apply(DynamicContext context) {
|
||||||
|
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
|
||||||
|
// 解析下级标签的入口
|
||||||
|
boolean result = contents.apply(filteredDynamicContext);
|
||||||
|
filteredDynamicContext.applyAll();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```JAVA
|
||||||
|
@Override
|
||||||
|
public boolean apply(DynamicContext context) {
|
||||||
|
if (evaluator.evaluateBoolean(test, context.getBindings())) {
|
||||||
|
contents.apply(context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- `evaluator.evaluateBoolean(test, context.getBindings())`方法
|
||||||
|
|
||||||
|
```JAVA
|
||||||
|
/**
|
||||||
|
* @param expression 判断语句,ID != null
|
||||||
|
* @param parameterObject 参数列表
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean evaluateBoolean(String expression, Object parameterObject) {
|
||||||
|
Object value = OgnlCache.getValue(expression, parameterObject);
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
return (Boolean) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
|
||||||
|
}
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```JAVA
|
||||||
|
/**
|
||||||
|
* 取值
|
||||||
|
* @param expression 判断语句,ID=NULL
|
||||||
|
* @param root 参数列表
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Object getValue(String expression, Object root) {
|
||||||
|
try {
|
||||||
|
Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
|
||||||
|
// 判断是否存在 expression 的判断内容 (判断ID是否存在)
|
||||||
|
return Ognl.getValue(parseExpression(expression), context, root);
|
||||||
|
} catch (OgnlException e) {
|
||||||
|
throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
存在返回`true`
|
||||||
|
|
||||||
|
执行完成就得到了一个sql
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
继续执行`org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql`方法
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 发送sql`org.apache.ibatis.executor.SimpleExecutor#doQuery`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 调用链路如下
|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)`
|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)`
|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.Executor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)`
|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)`
|
||||||
|
|
||||||
|
```java
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
|
||||||
|
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
|
||||||
|
if (closed) {
|
||||||
|
// 判断当前是否关闭
|
||||||
|
throw new ExecutorException("Executor was closed.");
|
||||||
|
}
|
||||||
|
if (queryStack == 0 && ms.isFlushCacheRequired()) {
|
||||||
|
// 查询堆栈==0 和 是否需要刷新缓存
|
||||||
|
// 清理本地缓存
|
||||||
|
clearLocalCache();
|
||||||
|
}
|
||||||
|
List<E> list;
|
||||||
|
try {
|
||||||
|
// 堆栈+1,防止重新清理缓存
|
||||||
|
queryStack++;
|
||||||
|
// 通过 缓存key 在本地缓存中获取
|
||||||
|
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
|
||||||
|
if (list != null) {
|
||||||
|
// 通过缓存 key 查到后处理 localOutputParameterCache
|
||||||
|
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
|
||||||
|
} else {
|
||||||
|
// 没有查询到从数据库查询
|
||||||
|
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// 堆栈-1
|
||||||
|
queryStack--;
|
||||||
|
}
|
||||||
|
if (queryStack == 0) {
|
||||||
|
for (DeferredLoad deferredLoad : deferredLoads) {
|
||||||
|
deferredLoad.load();
|
||||||
|
}
|
||||||
|
// 清空线程安全队列(延迟队列)
|
||||||
|
// issue #601
|
||||||
|
deferredLoads.clear();
|
||||||
|
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
|
||||||
|
// STATEMENT 清空本地缓存
|
||||||
|
// issue #482
|
||||||
|
clearLocalCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.BaseExecutor#queryFromDatabase`
|
||||||
|
|
||||||
|
```java
|
||||||
|
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
|
||||||
|
List<E> list;
|
||||||
|
localCache.putObject(key, EXECUTION_PLACEHOLDER);
|
||||||
|
try {
|
||||||
|
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
|
||||||
|
} finally {
|
||||||
|
localCache.removeObject(key);
|
||||||
|
}
|
||||||
|
localCache.putObject(key, list);
|
||||||
|
if (ms.getStatementType() == StatementType.CALLABLE) {
|
||||||
|
localOutputParameterCache.putObject(key, parameter);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.BaseExecutor#doQuery`
|
||||||
|
- `org.apache.ibatis.executor.SimpleExecutor#doQuery`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```java
|
||||||
|
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
|
||||||
|
Statement stmt;
|
||||||
|
// 数据库连接
|
||||||
|
Connection connection = getConnection(statementLog);
|
||||||
|
// stms 创建
|
||||||
|
// org.apache.ibatis.executor.statement.BaseStatementHandler.prepare
|
||||||
|
stmt = handler.prepare(connection, transaction.getTimeout());
|
||||||
|
// 参数放入
|
||||||
|
handler.parameterize(stmt);
|
||||||
|
return stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.statement.BaseStatementHandler#prepare`
|
||||||
|
- `org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
|
||||||
|
ErrorContext.instance().sql(boundSql.getSql());
|
||||||
|
Statement statement = null;
|
||||||
|
try {
|
||||||
|
statement = instantiateStatement(connection);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
protected Statement instantiateStatement(Connection connection) throws SQLException {
|
||||||
|
String sql = boundSql.getSql();
|
||||||
|
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
|
||||||
|
String[] keyColumnNames = mappedStatement.getKeyColumns();
|
||||||
|
if (keyColumnNames == null) {
|
||||||
|
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
|
||||||
|
} else {
|
||||||
|
return connection.prepareStatement(sql, keyColumnNames);
|
||||||
|
}
|
||||||
|
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
|
||||||
|
return connection.prepareStatement(sql);
|
||||||
|
} else {
|
||||||
|
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- 这个方法都去了`java.sql.Connection#prepareStatement(java.lang.String, java.lang.String[])`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 接下来需要考虑的问题是如何将`?`换成我们的参数`2`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.statement.StatementHandler#parameterize`
|
||||||
|
- `org.apache.ibatis.executor.statement.RoutingStatementHandler#parameterize`
|
||||||
|
- `org.apache.ibatis.executor.statement.StatementHandler#parameterize`
|
||||||
|
- `org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize`
|
||||||
|
- `org.apache.ibatis.executor.parameter.ParameterHandler`
|
||||||
|
- `org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
这样就拿到了`value`的值
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
准备工作就绪了发送就可以了
|
||||||
|
|
||||||
|
`doQuery`的工作完成了继续往下走
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Override
|
||||||
|
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
|
||||||
|
Statement stmt = null;
|
||||||
|
try {
|
||||||
|
Configuration configuration = ms.getConfiguration();
|
||||||
|
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
|
||||||
|
stmt = prepareStatement(handler, ms.getStatementLog());
|
||||||
|
return handler.query(stmt, resultHandler);
|
||||||
|
} finally {
|
||||||
|
closeStatement(stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- `org.apache.ibatis.executor.statement.RoutingStatementHandler#query`
|
||||||
|
- `org.apache.ibatis.executor.statement.PreparedStatementHandler#query`
|
||||||
|
- `org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets`
|
||||||
|
- `org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
处理后结果如上
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 处理查询结果
|
||||||
|
* @param stmt
|
||||||
|
* @return
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Object> handleResultSets(Statement stmt) throws SQLException {
|
||||||
|
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
|
||||||
|
|
||||||
|
final List<Object> multipleResults = new ArrayList<>();
|
||||||
|
|
||||||
|
int resultSetCount = 0;
|
||||||
|
ResultSetWrapper rsw = getFirstResultSet(stmt);
|
||||||
|
|
||||||
|
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
|
||||||
|
int resultMapCount = resultMaps.size();
|
||||||
|
validateResultMapsCount(rsw, resultMapCount);
|
||||||
|
while (rsw != null && resultMapCount > resultSetCount) {
|
||||||
|
ResultMap resultMap = resultMaps.get(resultSetCount);
|
||||||
|
handleResultSet(rsw, resultMap, multipleResults, null);
|
||||||
|
rsw = getNextResultSet(stmt);
|
||||||
|
cleanUpAfterHandlingResultSet();
|
||||||
|
resultSetCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] resultSets = mappedStatement.getResultSets();
|
||||||
|
if (resultSets != null) {
|
||||||
|
while (rsw != null && resultSetCount < resultSets.length) {
|
||||||
|
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
|
||||||
|
if (parentMapping != null) {
|
||||||
|
String nestedResultMapId = parentMapping.getNestedResultMapId();
|
||||||
|
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
|
||||||
|
handleResultSet(rsw, resultMap, null, parentMapping);
|
||||||
|
}
|
||||||
|
rsw = getNextResultSet(stmt);
|
||||||
|
cleanUpAfterHandlingResultSet();
|
||||||
|
resultSetCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询结果
|
||||||
|
return collapseSingleResultList(multipleResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 19 KiB |