Merge pull request #6 from doocs/master

MERGE
pull/9/head
huifer 5 years ago committed by GitHub
commit 0b069c19d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -43,7 +43,7 @@
- [ResultSetHandler](docs/Mybatis/核心处理层/3、ResultSetHandler.md)
- [StatementHandler](docs/Mybatis/核心处理层/4、StatementHandler.md)
- [Executor组件](docs/Mybatis/核心处理层/5、Executor组件.md)
- [SqlSession组件](docs/Mybatis/核心处理层/6、SqlSession组件.md)
## Netty
### IO
- [把被说烂的BIO、NIO、AIO再从头到尾扯一遍](docs/Netty/IO/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md)

@ -338,7 +338,9 @@ public class PoolState {
#### 1.3.3 PooledDataSource
PooledDataSource管理的数据库连接对象 是由其持有的UnpooledDataSource对象创建的并由PoolState管理所有连接的状态。
PooledDataSource的getConnection()方法会首先调用popConnection()方法获取PooledConnection对象然后通过PooledConnection的getProxyConnection()方法获取数据库连接的代理对象。popConnection()方法是PooledDataSource的核心逻辑之一其整体的逻辑关系如下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191205213903828.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
![avatar](/images/mybatis/数据库连接池流程图.png)
```java
public class PooledDataSource implements DataSource {

@ -72,9 +72,13 @@ public abstract class BaseExecutor implements Executor {
```
### 1.1 一级缓存简介
常见的应用系统中,数据库是比较珍贵的资源,很容易成为整个系统的瓶颈。在设计和维护系统时,会进行多方面的权衡,并且利用多种优化手段,减少对数据库的直接访问。
使用缓存是一种比较有效的优化手段,使用缓存可以减少应用系统与数据库的网络交互、减少数据库访问次数、降低数据库的负担、降低重复创建和销毁对象等一系列开销,从而提高整个系统的性能。
MyBatis提供的缓存功能分别为一级缓存和二级缓存。BaseExecutor主要实现了一级缓存的相关内容。一级缓存是会话级缓存在MyBatis中每创建一个SqlSession对象就表示开启一次数据库会话。在一次会话中应用程序可能会在短时间内(一个事务内),反复执行完全相同的查询语句,如果不对数据进行缓存,那么每一次查询都会执行一次数据库查询操作,而多次完全相同的、时间间隔较短的查询语句得到的结果集极有可能完全相同,这会造成数据库资源的浪费。
为了避免上述问题MyBatis会在Executor对象中建立一个简单的一级缓存将每次查询的结果集缓存起来。在执行查询操作时会先查询一级缓存如果存在完全一样的查询情况则直接从一级缓存中取出相应的结果对象并返回给用户减少数据库访问次数从而减小了数据库的压力。
一级缓存的生命周期与SqlSession相同其实也就与SqISession中封装的Executor 对象的生命周期相同。当调用Executor对象的close()方法时断开连接该Executor 对象对应的一级缓存就会被废弃掉。一级缓存中对象的存活时间受很多方面的影响例如在调用Executor的update()方法时,也会先请空一级缓存。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置。
### 1.2 一级缓存的管理
BaseExecutor的query()方法会首先创建CacheKey对象并根据该CacheKey对象查找一级缓存如果缓存命中则返回缓存中记录的结果对象如果缓存未命中则查询数据库得到结果集之后将结果集映射成结果对象并保存到一级缓存中同时返回结果对象。
@ -178,6 +182,7 @@ public abstract class BaseExecutor implements Executor {
}
```
从上面的代码中可以看到BaseExecutor的query()方法会根据flushCache属性和localCacheScope配置 决定是否清空一级缓存。
另外BaseExecutor的update()方法在调用doUpdate()方法之前也会清除一级缓存。update()方法负责执行insert、update、delete三类SQL 语句它是调用doUpdate()方法实现的。
```java
@Override
@ -348,7 +353,9 @@ public class SimpleExecutor extends BaseExecutor {
```
## 3 ReuseExecutor
在传统的JDBC编程中复用Statement对象是常用的一种优化手段该优化手段可以减少SQL预编译的开销以及创建和销毁Statement对象的开销从而提高性能Reuse复用
ReuseExecutor提供了Statement复用的功能ReuseExecutor中通过statementMap 字段缓存使用过的Statement对象key是SQL语句value是SQL对应的Statement 对象。
ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与SimpleExecutor中对应方法的实现一样区别在于其中调用的prepareStatement()方法SimpleExecutor每次都会通过JDBC的Connection对象创建新的Statement对象而ReuseExecutor则会先尝试重用StaternentMap中缓存的Statement对象。
```java
// 本map用于缓存使用过的Statement以提升本框架的性能
@ -398,20 +405,35 @@ ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与SimpleEx
```
#### 拓展内容SQL预编译
**1、数据库预编译起源**
1数据库SQL语句编译特性
数据库接收到sql语句之后需要词法和语义解析以优化sql语句制定执行计划。这需要花费一些时间。但是很多情况我们的同一条sql语句可能会反复执行或者每次执行的时候只有个别的值不同比如query的where子句值不同update的set子句值不同insert的values值不同
2减少编译的方法
如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等则效率就明显不行了。为了解决上面的问题于是就有了预编译预编译语句就是将这类语句中的值用占位符替代可以视为将sql语句模板化或者说参数化。一次编译、多次运行省去了解析优化等过程。
3缓存预编译
预编译语句被DB的编译器编译后的执行代码被缓存下来那么下次调用时只要是相同的预编译语句就不需要重复编译只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。
(4) 预编译的实现方法
4 预编译的实现方法
预编译是通过PreparedStatement和占位符来实现的。
**2.预编译作用**
1减少编译次数 提升性能
预编译之后的 sql 多数情况下可以直接执行DBMS数据库管理系统不需要再次编译。越复杂的sql往往编译的复杂度就越大。
2防止SQL注入
使用预编译后面注入的参数将不会再次触发SQL编译。也就是说对于后面注入的参数系统将不会认为它会是一个SQL命令而默认其是一个参数参数中的or或and等SQL注入常用技俩就不是SQL语法保留字了。
**3.mybatis是如何实现预编译的**
mybatis默认情况下将对所有的 sql 进行预编译。mybatis底层使用PreparedStatement过程是先将带有占位符即”?”的sql模板发送至数据库服务器由服务器对此无参数的sql进行编译后将编译结果缓存然后直接执行带有真实参数的sql。核心是通过 “#{ }” 实现的。在预编译之前,#{ } 被解析为一个预编译语句PreparedStatement的占位符 ?。
```sql
// sqlMap 中如下的 sql 语句
@ -420,8 +442,152 @@ select * from user where name = #{name};
select * from user where name = ?;
```
## 4 BatchExecutor
应用系统在执行一条SQL语句时会将SQL语句以及相关参数通过网络发送到数据库系统。对于频繁操作数据库的应用系统来说如果执行一条SQL语句就向数据库发送一次请求很多时间会浪费在网络通信上。使用批量处理的优化方式可以在客户端缓存多条SQL语句并在合适的时机将多条SQL语句打包发送给数据库执行从而减少网络方面的开销提升系统的性能。
需要注意的是在批量执行多条SQL 语句时每次向数据库发送的SQL语句条数
是有上限的若超出上限数据库会拒绝执行这些SQL语句井抛出异常所以批量发送SQL语句的时机很重要。
mybatis的BatchExecutor实现了批处理多条SQL 语句的功能。
```java
public class BatchExecutor extends BaseExecutor {
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
// 缓存多个Statement对象其中每个Statement对象中都可以缓存多条
// 结构相同 但参数不同的sql语句
private final List<Statement> statementList = new ArrayList<Statement>();
// 记录批处理的结果BatchResult中通过updateCounts字段
// 记录每个Statement对象 执行批处理的结果
private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
// 记录当前执行的sql语句
private String currentSql;
// 记录当前执行的MappedStatement对象
private MappedStatement currentStatement;
/**
* JDBC中的批处理只支持insert、update、delete等类型的SQL语句不支持select类型的
* SQL语句所以doUpdate()方法是BatchExecutor中最重要的一个方法。
* 本方法在添加一条SQL语句时首先会将currentSql字段记录的SQL语句以及currentStatement字段
* 记录的MappedStatement对象与当前添加的SQL以及MappedStatement对象进行比较
* 如果相同则添加到同一个Statement对象中等待执行如果不同则创建新的Statement对象
* 井将其缓存到statementList集合中等待执行
*/
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
// 获取configuration配置对象
final Configuration configuration = ms.getConfiguration();
// 实例化一个StatementHandler并返回
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
// 获取需要执行的sql语句
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
// 判断要执行的sql语句结构 及 MappedStatement对象 是否与上次的相同
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// 相同则添加到同一个Statement对象中等待执行
// 首先获取statementList集合中最后一个Statement对象
int last = statementList.size() - 1;
stmt = statementList.get(last);
// 重新设置事务超时时间
applyTransactionTimeout(stmt);
// 绑定实参,处理占位符?
handler.parameterize(stmt);
// 查找对应的BatchResult对象并记录用户传入的实参
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
// 不同则创建新的Statement对象井将其缓存到statementList集合中等待执行
Connection connection = getConnection(ms.getStatementLog());
// 创建新的Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 绑定实参,处理占位符?
handler.parameterize(stmt);
// 记录本次的sql语句 及 Statement对象
currentSql = sql;
currentStatement = ms;
// 将新创建的Statement对象添加到statementList集合
statementList.add(stmt);
// 添加新的BatchResult对象
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 底层通过调用java.sql.Statement的addBatch()方法添加sql语句
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
/**
* 上面的doUpdate()方法负责添加待执行的sql语句
* 而doFlushStatements()方法则将上面添加的sql语句进行批量处理
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
// 用于存储批处理结果的集合
List<BatchResult> results = new ArrayList<BatchResult>();
// 如果要回滚 则返回一个空集合
if (isRollback) {
return Collections.emptyList();
}
// 批处理statementList集合中的所以Statement对象
for (int i = 0, n = statementList.size(); i < n; i++) {
// 获取Statement对象 和其对应的 BatchResult对象
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
// 调用Statement对象的executeBatch()方法批量执行其中记录的sql语句
// 将执行返回的int[]数组set进batchResult的updateCounts字段
// 其中的每一个int值都代表了对应的sql语句 影响的记录条数
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
// 获取配置的KeyGenerator对象
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
// 获取数据库生成的主键 并设置到parameterObjects中
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) {
// 对于其它类型的KeyGenerator则调用其processAfter进行处理
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
closeStatement(stmt);
} catch (BatchUpdateException e) {
StringBuilder message = new StringBuilder();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append(" ")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
// 添加处理完的BatchResult对象到要返回的List<BatchResult>集合中
results.add(batchResult);
}
return results;
} finally {
// 关闭所有的Statement对象
for (Statement stmt : statementList) {
closeStatement(stmt);
}
// 清空currentSql、statementList、batchResultList对象
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
}
```
通过了解JDBC的批处理功能 我们可以知道Statement中可以添加不同语句结构的SQL但是每添加一个新结构的SQL语句都会触发一次编译操作。而PreparedStatement中只能添加同一语句结构的SQL语句只会触发一次编译操作但是可以通过绑定多组不同的实参实现批处理。通过上面对doUpdate()方法的分析可知BatchExecutor会将连续添加的、相同语句结构的SQL语句添加到同一个Statement/PreparedStatement对象中这样可以有效地减少编译操作的次数。
BatchExecutor中doQuery()和doQueryCursor()方法的实现与前面介绍的SimpleExecutor类似主要区别就是BatchExecutor中的这两个方法在最开始都会先调用flushStatements()方法执行缓存的SQL语句以保证 从数据库中查询到的数据是最新的。
## 5 CachingExecutor
CachingExecutor中为Executor对象增加了二级缓存相关功能而mybatis的二级缓存在实际使用中往往利大于弊被redis等产品所替代所以这里不做分析。

@ -0,0 +1,489 @@
SqlSession是MyBatis核心接口之一也是MyBatis接口层的主要组成部分对外提供MyBatis常用的API。mybatis提供了两个SqlSession接口的实现分别为DefaultSqlSession、SqlSessionManager其中最常用的是DefaultSqlSession。另外跟前面分析过的源码mybatis的源码一样mybatis也为SqlSession提供了相应的工厂接口SqlSessionFactory及实现该接口的实现DefaultSqlSessionFactorySqlSessionManager同时实现了SqlSession和SqlSessionFactory接口
## 1 SqlSession
在SqlSession中定义了常用的数据库操作以及事务的相关操作为了方便用户使用每种类型的操作都提供了多种重载。
```java
public interface SqlSession extends Closeable {
// 泛型方法参数是要执行查询的sql语句返回值为查询的结果对象
<T> T selectOne(String statement);
// 第二个参数表示 需要用户传入的实参,即 sql语句绑定的实参
<T> T selectOne(String statement, Object parameter);
// 查询结果有多条记录,会封装成 结果对象列表 并返回
<E> List<E> selectList(String statement);
// 参数 + 多记录结果集
<E> List<E> selectList(String statement, Object parameter);
// 参数RowBounds主要用于逻辑分页逻辑分页会将所有的结果都查询到
// 然后根据RowBounds中提供的offset和limit值来获取最后的结果
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
// mapKey表示将结果集中的哪一列如 主键列或编码列作为Map的key
// value则为列值 对应的那条记录
<K, V> Map<K, V> selectMap(String statement, String mapKey);
// 多了个parameter参数其它与上面相同
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
// 多了个RowBounds参数其它与上面相同
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
// 除了返回值是Cursor对象其它与selectList相同
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
// 查询出的结果集 将由传入的ResultHandler对象处理其它与selectList相同
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
// 执行insert语句
int insert(String statement);
int insert(String statement, Object parameter);
// 执行update语句
int update(String statement);
int update(String statement, Object parameter);
// 执行delete语句
int delete(String statement);
int delete(String statement, Object parameter);
// 提交事务
void commit();
void commit(boolean force);
// 回滚事务
void rollback();
void rollback(boolean force);
// 将对数据库的操作请求 刷到数据库
List<BatchResult> flushStatements();
// 关闭当前session
void close();
// 清空缓存
void clearCache();
// 获取Configuration对象
Configuration getConfiguration();
// 获取type对应的Mapper对象
<T> T getMapper(Class<T> type);
// 获取该SqlSession对应的数据库连接
Connection getConnection();
}
```
### 1.1 DefaultSqlSession
DefaultSqlSession是单独使用MyBatis进行开发时最常用的SqISession接口实现。其实现了SqISession接口中定义的方法及各方法的重载。select()系列方法、selectOne()系列方法、selectList()系列方法、selectMap()系列方法之间的调用关系如下图殊途同归它们最终都会调用Executor的query()方法。
![avatar](/images/mybatis/DefaultSqlSession方法调用栈.png)
上述重载方法最终都是通过调用Executor的query(MappedStatement, Object, RowBounds,ResultHandler)方法实现数据库查询操作的但各自对结果对象进行了相应的调整例如selectOne()方法是从结果对象集合中获取了第一个元素返回selectMap()方法会将List类型的结果集 转换成Map类型集合返回select()方法是将结果集交由用户指定的ResultHandler对象处理且没有返回值selectList()方法则是直接返回结果对象集合。
DefaultSqlSession的insert()方法、update()方法、delete()方法也有多个重载它们最后都是通过调用DefaultSqlSession的update(String, Object)方法实现的该重载首先会将dirty字段置为true然后再通过Executor的update()方法完成数据库修改操作。
DefaultSqlSession的commit()方法、rollback()方法以及close()方法都会调用Executor中相应的方法其中就会涉及清空缓存的操作之后就会将dirty字段设置为false。
上述的dirty字段主要在isCommitOrRollbackRequired()方法中与autoCommit字段以及用户传入的force参数共同决定是否提交/回滚事务。该方法的返回值将作为Executor的commit()方法和rollback()方法的参数。
```java
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
```
## 2 SqlSessionFactory
SqlSessionFactory负责创建SqlSession对象其中包含了多个openSession()方法的重载可以通过其参数指定事务的隔离级别、底层使用Executor的类型、以及是否自动提交事务等方面的配置。
```java
public interface SqlSessionFactory {
// 提供了openSession()方法的多种重载,根据相应的参数 可以指定事务的隔离级别、
// 底层使用的Executor类型、以及是否自动提交事务等配置
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
```
### 2.1 DefaultSqlSessionFactory
DefaultSqlSessionFactory是SqlSessionFactory接口的默认实现主要提供了两种创建DefaultSqlSession对象的方式一种方式是通过数据源获取数据库连接并创建Executor对象以及DefaultSqlSession对象另一种方式是用户提供数据库连接对象DefaultSqlSessionFactory根据该数据库连接对象获取autoCommit属性创建Executor对象以及DefaultSqlSession对象。
DefaultSqISessionFactory提供的所有openSession()方法重载都是基于上述两种方式创建DefaultSqlSession对象的。
```java
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取配置的Environment对象
final Environment environment = configuration.getEnvironment();
// 从environment中获取TransactionFactory对象如果没有就创建一个ManagedTransactionFactory实例并返回
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从事务工厂中获取一个事务对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据事务对象tx和配置的Executor类型execType创建Executor实例
// ExecutorType是个枚举类型有三个值 SIMPLE, REUSE, BATCH分别对应了
// SimpleExecutor、ReuseExecutor、BatchExecutor
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
// 根据当前连接对象获取autoCommit属性是否自动提交事务
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
autoCommit = true;
}
// 除了获取autoCommit属性的方式和上面不一样外下面的处理都与上面完全相同
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
}
```
### 2.2 SqlSessionManager
SqlSessionManager同时实现了SqlSession接口和SqlSessionFactory接口所以同时提供了SqlSessionFactory创建SqlSession对象以及SqlSession操纵数据库的功能。
SqlSessionManager与DefaultSqlSessionFactory的主要不同点SqlSessionManager 提供了两种模式第一种模式与DefaultSqlSessionFactory的行为相同同一线程每次通过SqlSessionManager对象访问数据库时都会创建新的SqlSession对象完成数据库操作。第二种模式是SqlSessionManager通过localSqlSession这ThreadLocal 变量记录与当前线程绑定的SqlSession对象供当前线程循环使用从而避免在同一线程多次创建SqlSession对象带来的性能损失。
SqlSessionManager的构造方法是唯一且私有的如果要创建SqlSessionManager对象需要调用其newInstance()方法但需要注意的是这不是单例模式因为每次调用newInstance()方法都返回了一个新的对象)。
SqlSessionManager的openSession()系列方法,都是通过直接调用其持有的
DefaultSqlSessionFactory实例来实现的。
```java
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
// 通过持有DefaultSqlSessionFactory对象 来产生SqlSession对象
private final SqlSessionFactory sqlSessionFactory;
// 用于记录一个与当前线程绑定的SqlSession对象
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
// localSqlSession中记录的SqlSession对象的代理对象JDK动态代理
// SqlSessionManager初始化时 生成本代理对象,可以看下 下面的构造函数
private final SqlSession sqlSessionProxy;
// 私有的构造函数也是SqlSessionManager唯一的构造函数
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
// 传入的这个SqlSessionFactory对象 往往是DefaultSqlSessionFactory的实例
this.sqlSessionFactory = sqlSessionFactory;
// JDK动态代理生成代理对象可以看得出SqlSessionInterceptor一定实现了
// InvocationHandler接口
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
// 通过newInstance()方法创建SqlSessionManager对象有多种重载
// 但最后都是new了一个DefaultSqlSessionFactory的实例
public static SqlSessionManager newInstance(Reader reader) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
}
public static SqlSessionManager newInstance(Reader reader, String environment) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));
}
public static SqlSessionManager newInstance(Reader reader, Properties properties) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, properties));
}
public static SqlSessionManager newInstance(InputStream inputStream) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));
}
public static SqlSessionManager newInstance(InputStream inputStream, String environment) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, environment, null));
}
public static SqlSessionManager newInstance(InputStream inputStream, Properties properties) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, properties));
}
public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionManager(sqlSessionFactory);
}
// openSession()系列方法都是通过当前SqlSessionManager对象持有的
// DefaultSqlSessionFactory实例的openSession()实现的
@Override
public SqlSession openSession() {
return sqlSessionFactory.openSession();
}
@Override
public SqlSession openSession(boolean autoCommit) {
return sqlSessionFactory.openSession(autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return sqlSessionFactory.openSession(connection);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return sqlSessionFactory.openSession(level);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return sqlSessionFactory.openSession(execType);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return sqlSessionFactory.openSession(execType, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return sqlSessionFactory.openSession(execType, level);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return sqlSessionFactory.openSession(execType, connection);
}
}
```
SqlSessionManager中实现的SqlSession接口方法例如select ()系列方法、update()系列方法等都是直接调用sqlSessionProxy代理对象对应的方法实现的。在创建该代理对象时使用的InvocationHandler对象是SqlSessionlnterceptor它是SqISessionManager的内部类。
```java
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() { }
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取 与当前线程绑定的SqlSession
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
// 如果有绑定的SqlSession对象
if (sqlSession != null) { // 模式二
try {
// 调用真正的sqlSession对象完成数据库操作
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 如果没有绑定的SqlSession对象
} else { // 模式一
// 创建一个新的SqlSession对象
final SqlSession autoSqlSession = openSession();
try {
// 通过反射调用该SqlSession对象的方法完成数据库操作
final Object result = method.invoke(autoSqlSession, args);
// 提交事务
autoSqlSession.commit();
return result;
} catch (Throwable t) {
// 出异常就回滚
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
} finally {
// 关闭该SqlSession对象
autoSqlSession.close();
}
}
}
}
```
通过对SqlSessionlnterceptor的分析可知第一种模式中新建的SqlSession在使用完成后会立即关闭。在第二种模式中与当前线程绑定的SqISession对象需要先通过SqlSessionManager的startManagedSession()方法进行设置,此方法也存在多种重载,但都彼此相似 且简单。
```java
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
public void startManagedSession(boolean autoCommit) {
this.localSqlSession.set(openSession(autoCommit));
}
public void startManagedSession(Connection connection) {
this.localSqlSession.set(openSession(connection));
}
public void startManagedSession(TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(level));
}
public void startManagedSession(ExecutorType execType) {
this.localSqlSession.set(openSession(execType));
}
public void startManagedSession(ExecutorType execType, boolean autoCommit) {
this.localSqlSession.set(openSession(execType, autoCommit));
}
public void startManagedSession(ExecutorType execType, TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(execType, level));
}
public void startManagedSession(ExecutorType execType, Connection connection) {
this.localSqlSession.set(openSession(execType, connection));
}
public boolean isManagedSessionStarted() {
return this.localSqlSession.get() != null;
}
```
当需要提交/回滚事务或关闭IocalSqlSession中记录的SqlSession对象时需要通过SqlSessionManager的commit()、rollback()以及close()方法完成其中会先检测当前线程是否绑定了SqlSession对象如果未绑定则抛出异常如果绑定了则调用该SqlSession对象的相应方法。
```java
@Override
public void clearCache() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot clear the cache. No managed session is started.");
}
sqlSession.clearCache();
}
@Override
public void commit() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot commit. No managed session is started.");
}
sqlSession.commit();
}
@Override
public void commit(boolean force) {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot commit. No managed session is started.");
}
sqlSession.commit(force);
}
@Override
public void rollback() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
sqlSession.rollback();
}
@Override
public void rollback(boolean force) {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
sqlSession.rollback(force);
}
@Override
public List<BatchResult> flushStatements() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot rollback. No managed session is started.");
}
return sqlSession.flushStatements();
}
@Override
public void close() {
final SqlSession sqlSession = localSqlSession.get();
if (sqlSession == null) {
throw new SqlSessionException("Error: Cannot close. No managed session is started.");
}
try {
sqlSession.close();
} finally {
localSqlSession.set(null);
}
}
```

@ -0,0 +1,6 @@
本文用于总结阅读过的框架中,所使用过的设计模式,结合实际生成中的源码,重新理解设计模式。

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Loading…
Cancel
Save