Merge pull request #5 from doocs/master

ReuseExecutor源码分析,拓展内容:SQL预编译
pull/7/head
huifer 5 years ago committed by GitHub
commit 1ecbb63763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -347,8 +347,78 @@ 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以提升本框架的性能
// key SQL语句value 该SQL语句对应的Statement
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
// 获取要执行的sql语句
String sql = boundSql.getSql();
// 如果之前执行过该sql则从缓存中取出对应的Statement对象
// 不再创建新的Statement减少系统开销
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
// 修改超时时间
applyTransactionTimeout(stmt);
} else {
// 获取数据库连接
Connection connection = getConnection(statementLog);
// 从连接中获取Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 将sql语句 和 其对应的Statement对象缓存起来
putStatement(sql, stmt);
}
// 处理占位符
handler.parameterize(stmt);
return stmt;
}
/**
* 当事务提交或回滚、连接关闭时都需要关闭这些缓存的Statement对象。前面分析的BaseExecutor的
* commit()、rollback()和close()方法中都会调用doFlushStatements()方法,
* 所以在该方法中关闭Statement对象的逻辑非常合适
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
// 遍历Statement对象集合并依次关闭
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
// 清除对Statement对象的缓存
statementMap.clear();
// 返回一个空集合
return Collections.emptyList();
}
```
#### 拓展内容SQL预编译
**1、数据库预编译起源**
1数据库SQL语句编译特性
数据库接收到sql语句之后需要词法和语义解析以优化sql语句制定执行计划。这需要花费一些时间。但是很多情况我们的同一条sql语句可能会反复执行或者每次执行的时候只有个别的值不同比如query的where子句值不同update的set子句值不同insert的values值不同
2减少编译的方法
如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等则效率就明显不行了。为了解决上面的问题于是就有了预编译预编译语句就是将这类语句中的值用占位符替代可以视为将sql语句模板化或者说参数化。一次编译、多次运行省去了解析优化等过程。
3缓存预编译
预编译语句被DB的编译器编译后的执行代码被缓存下来那么下次调用时只要是相同的预编译语句就不需要重复编译只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。
(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 语句
select * from user where name = #{name};
// 解析成为预编译语句
select * from user where name = ?;
```
## 4 BatchExecutor

Loading…
Cancel
Save