深入理解执行器的设计理念
JDBC执行过程
-
获取连接:Connection
从配置文件读取连接所需的配置参数:url,driver,username,password
-
预编译SQL:PrepareStatement
设置参数
-
执行SQL
获的ResultSet对象
-
读取结果
结果封装为Java Bean
三种Statement(依次继承)
1.简单Statement
- 基本功能:执行静态SQL
- 传输相关:发送与接收
- 批处理:addBatch(),一次性向数据库发送多条SQL或参数,批处理操作,将多个SQL合并在一起,最后调用executeBatch 一起发送至数据库执行
- 设置从数据库每次读取的数量单位:setFetchSize() 该举措是为了防止一次性从数据库加载数据过多,导致内存溢出。
2.预处理PrepareStatement
- 扩展了Statement,提供SQL参数预编译处理,防止SQL注入,针对不同的参数类型提供不同的setxxx()方法
- SQL防注入:与Statement发送多条静态SQL(参数已写入SQL,不会转义)不同,PrepareStatement是向数据库发送SQL语句加参数组,参数会在数据库进行转义
3.与存储过程相关的CallableStatement
- 设置出参,读取入参
Mybatis执行过程(SqlSession-->Executor-->StatementHandler-->DB)
1.SqlSession接口
-
基本API:增删改查 insert()、delete()、update()、select();select()方法有多个重载,分别有不同的参数,但总共只有4种参数,返回的结果类型有Object,Map,List,Cursor游标,void
-
String statement: @param statement Unique identifier matching the statement to use. //statement唯一标识符 -
Object parameter //sql参数 -
RowBounds rowBounds: @param rowBounds RowBound instance to limit the query results.//限制返回的结果条数,用于分页 -
ResultHandler handler: @param handler ResultHandler that will handle each retrieved row. //结果处理器 -
SqlSession的select()方法运用了门面模式,为不同需求提供多种选择,而不需要关心实现细节.
-
-
辅助API:提交、回滚、关闭会话连接以及清除会话缓存。(只会回滚更新、删除和新增操作)
-
void clearCache();//清除本地会话缓存 void commit(); void commit(boolean force);//是否强制提交 void rollback(); void rollback(boolean force);//是否强制回滚 List<BatchResult> flushStatements();//刷新批处理的Statement。进行批处理时,加载了所有Statement后需要调用此方法刷新后批处理才会生效。类似于io流的flush方法。
-
2.Executor执行器根接口
-
基本API:改、查、缓存维护
-
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;//查询方法,其中的BoundSql对象是用于预编译的动态sql对象,动态处理参数映射关系 int update(MappedStatement ms, Object parameter) throws SQLException;//修改,包括了更新,删除和新增 CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);//创建缓存,CachingExecutor重写了该方法,实际为二级缓存 void setExecutorWrapper(Executor executor);//CachingExecutor重写了该方法,执行完二级缓存的逻辑后委托(delegate)给其它执行器
-
-
辅助API:提交、关闭执行器,批处理刷新
-
Executor的实现类:
- BaseExecutor:
- BaseExecutor是抽象类,除了继承实现Executor的基础方法外,还另外定义了一些doXXX()的模板方法供子类实现,do开头的方法一般都是真正干活的,所以可以猜测,在BatchExecutor,ReuseExecutor和SimpleExecutor中的doXX方法应该是真正类执行具体操作的。
- 有一个localCache属性,这个属性其实就是一级缓存所在。这个属性是PerpetualCache(永久缓存)类型的,而PerpetualCache类内部维护了一个HashMap类型的属性cache,所以这就是最终二级缓存存在的形式。
- 在构造方法中会new一个id为LocalCache的PerpetualCache对象,赋值给localCache,因此一级缓存的作用域是SqlSession,而且是默认开启的,因为每一次创建sqlSession时都会创建Executor。
- 当执行update、commit、rollback方法时会调用clearLocalCache()方法, 清除缓存。
- BatchExecutor
- 批处理执行器,批量修改操作等。不一定会用到JDBC Statement的addBatch功能,只有当请求的sql完全相同时才会重用一个Statement。否则是按照请求的顺序去创建新的Statement,保证sql执行的顺序。
- ReuseExecutor
- 可重用执行器,在这个类中有一个属性叫statementMap,这个map用sql为key,statement为value 。这样下次执行相同的SQL时就不用再创建新的statement。
- SimpleExecutor
- 简单执行器,每次执行都会创建一个新的PreparedStatement。
- BaseExecutor:
3.StatementHandler声明处理器接口
-
参数处理与结果处理,真正执行query和update的地方
-
五种实现类
- BaseStatementHandler 实现了一些公共方法,其它4种StatementHandler继承于BaseStatementHandler
- SimpleStatementHandler 简单的StatementHandler,用于处理Statement
- PrepareStatementHandler 预编译StatementHandler,用于处理PrepareStatement
- CallableStatementHandler 存储过程StatementHandler,用于处理CallableStatement
- RoutingStatementHandler 路由处理 ,根据Mapper文件或注解配置的 StatementType 路由到对应的其它3种StatementHandler,可以看成工厂模式的实现(传入statementType,返回对应的StatementHandler)
Executor执行器体系
-
顶层接口Executor
-
直接实现类CachingExecutor,在这个地方处理完二级缓存的逻辑,根据缓存配置,选择使用或不使用二级缓存,该类的构造方法传入的是从SqlSession传过来的Executor,赋值给delegate(Executor),然后调用setExecutorWrapper方法设置下一个Executor,执行下一环节,引用BaseExecutor。
-
直接实现类BaseExecutor,公共方法的实现,设置一级缓存,获取数据库连接,执行查询或更新方法。上层调用的query方法实际调用的是BaseExecutor的doQuery方法,update调用的实际是doUpdate方法。
-
子类SimpleExecutor:简单执行器,每次执行都会调用自身私有的prepareStatement方法创建新的预处理器PrepareStatement
-
@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); } }
-
-
子类ReuseExecutor:可重用执行器,会在一个Map中存放prepareStatement,相同的sql使用Map中的同一个prepareStatement
-
private final Map<String, Statement> statementMap = new HashMap<>(); private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); //相同sql则使用同一个prepareStatement if (hasStatementFor(sql)) { stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; } //判断是否存在相同sql private boolean hasStatementFor(String sql) { try { return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed(); } catch (SQLException e) { return false; } } //get操作 private Statement getStatement(String s) { return statementMap.get(s); } //put操作 private void putStatement(String sql, Statement stmt) { statementMap.put(sql, stmt); } }
-
-
BatchExecutor:批处理执行器,批量提交修改,必须先执行flushStatements()方法,刷新Statements才会生效
-
-
总结
在Mybatis整个执行过程中,最开始的SqlSession建立数据库连接,提供相关的API,但是它本身并没有实现这些功能,而是在生成session的时候根据配置创建相应的Executor,然后将请求转交给Executor执行器,执行器再调用StatementHandler去真正处理请求。
可以将SqlSession看成是星级餐厅中的服务员角色,Executor相当于大堂经理,StatementHandler相当于制作菜品的主厨,服务员给客户提供菜单,然后将菜单交给大堂经理,由大堂经理告诉主厨客户需要的菜品,最后是由主厨来制作菜品。