前言
所有系统的设计和实现,核心都在于如何解耦,如果解耦不清晰最后直接导致的就是再继续迭代功能时,会让整个系统的实现越来越臃肿,稳定性越来越差。而关于解耦的实践在各类框架的源码中都有非常不错的设计实现,所以阅读这部分源码,就是在吸收成功的经验。把解耦的思想逐步运用到实际的业务开发中,才会让我们写出更加优秀的代码结构。
设计
从我们对 ORM 框架渐进式的开发过程上,可以分出的执行动作包括,解析配置、代理对象、映射方法等,直至我们前面章节对数据源的包装和使用,只不过我们把数据源的操作硬捆绑到了 DefaultSqlSession 的执行方法上了。
那么现在为了解耦这块的处理,则需要单独提出一块执行器的服务功能,之后将执行器的功能随着 DefaultSqlSession 创建时传入执行器功能,之后具体的方法调用就可以调用执行器来处理了,从而解耦这部分功能模块。
执行器的定义和实现
java
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
<E> List<E> query(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql);
Transaction getTransaction();
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
void close(boolean forceRollback);
}
在执行器中定义的接口包括事务相关的处理方法和执行SQL查询的操作,随着后续功能的迭代还会继续补充其他的方法。
BaseExecutor 抽象基类
java
public abstract class BaseExecutor implements Executor {
protected Configuration configuration;
protected Transaction transaction;
protected Executor wrapper;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.configuration = configuration;
this.transaction = transaction;
this.wrapper = this;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
return doQuery(ms, parameter, resultHandler, boundSql);
}
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql);
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new RuntimeException("Cannot commit, transaction is already closed");
}
if (required) {
transaction.commit();
}
}
}
在抽象基类中封装了执行器的全部接口,这样具体的子类继承抽象类后,就不用在处理这些共性的方法。与此同时在 query 查询方法中,封装一些必要的流程处理,如果检测关闭等,在 Mybatis 源码中还有一些缓存的操作,这里暂时剔除掉,以核心流程为主。
SimpleExecutor 简单执行器实现
java
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
protected <E> List<E> doQuery(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, resultHandler, boundSql);
Connection connection = transaction.getConnection();
Statement stmt = handler.prepare(connection);
handler.parameterize(stmt);
return handler.query(stmt, resultHandler);
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
}
简单执行器 SimpleExecutor 继承抽象基类,实现抽象方法 doQuery,在这个方法中包装数据源的获取、语句处理器的创建,以及对 Statement 的实例化和相关参数设置。最后执行 SQL 的处理和结果的返回操作。
关于 StatementHandler 语句处理器的实现,接下来介绍
语句处理器
StatementHandler
java
public interface StatementHandler {
/** 准备语句 */
Statement prepare(Connection connection) throws SQLException;
/** 参数化 */
void parameterize(Statement statement) throws SQLException;
/** 执行查询 */
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
}
BaseStatementHandler 抽象基类
java
public abstract class BaseStatementHandler implements StatementHandler {
protected final Configuration configuration;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final Object parameterObject;
protected final ResultSetHandler resultSetHandler;
protected BoundSql boundSql;
public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.boundSql = boundSql;
// 参数和结果集
this.parameterObject = parameterObject;
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, boundSql);
}
@Override
public Statement prepare(Connection connection) throws SQLException {
Statement statement = null;
try {
// 实例化 Statement
statement = instantiateStatement(connection);
// 参数设置,可以被抽取,提供配置
statement.setQueryTimeout(350);
statement.setFetchSize(10000);
return statement;
} catch (Exception e) {
throw new RuntimeException("Error preparing statement. Cause: " + e, e);
}
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
}
PreparedStatementHandler 预处理语句处理器
java
public class PreparedStatementHandler extends BaseStatementHandler{
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
return connection.prepareStatement(sql);
}
@Override
public void parameterize(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.setLong(1, Long.parseLong(((Object[]) parameterObject)[0].toString()));
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
}
在预处理语句处理器中包括 instantiateStatement 预处理 SQL、parameterize 设置参数,以及 query 查询的执行的操作。
执行器创建和使用
执行器开发完成以后,则需要在串联到 DefaultSqlSession 中进行使用,那么这个串联过程就需要在 创建 DefaultSqlSession 的时候,构建出执行器并作为参数传递进去。那么这块就涉及到 DefaultSqlSessionFactory#openSession 的处理。
开启执行器
java
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
TransactionFactory transactionFactory = environment.getTransactionFactory();
tx = transactionFactory.newTransaction(configuration.getEnvironment().getDataSource(), TransactionIsolationLevel.READ_COMMITTED, false);
// 创建执行器
final Executor executor = configuration.newExecutor(tx);
// 创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
try {
assert tx != null;
tx.close();
} catch (SQLException ignore) {
}
throw new RuntimeException("Error opening session. Cause: " + e);
}
}
}
使用执行器
java
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
MappedStatement ms = configuration.getMappedStatement(statement);
List<T> list = executor.query(ms, parameter, Executor.NO_RESULT_HANDLER, ms.getBoundSql());
return list.get(0);
}
}
经过上面执行器的所有实现完成后,接下来就是解耦后的调用了。在 DefaultSqlSession#selectOne 中获取 MappedStatement 映射语句类后,则传递给执行器进行处理,那么现在这个类经过设计思想的解耦后,就变得更加赶紧整洁了,也就是易于维护和扩展了。
配置数据源
xml
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
配置Mapper
xml
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
SELECT id, userId, userName, userHead
FROM user
where id = #{id}
</select>
单元测试
java
@Test
public void test() throws IOException {
// 1. 从SqlSessionFactory中获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
// 2. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 3. 测试验证
User user = userDao.queryUserInfoById(1L);
logger.info("测试结果:{}", JSON.toJSONString(user));
}
总结
1:从 DefaultSqlSession#selectOne 对数据源的处理解耦到执行器中进行操作。而执行器中又包括了对 JDBC 处理的拆解,链接、准备语句、封装参数、处理结果,所有的这些过程经过解耦后的类和方法,就都可以在以后的功能迭代中非常方便的完成扩展了。
2:本章节也为我们后续扩展参数的处理、结果集的封装预留出了扩展点,以及对于不同的语句处理器选择的问题,都需要在后续进行完善和补充。目前我们串联出来的是最核心的骨架结构,随着后续的渐进式开发陆续迭代完善。
好了到这里就结束了手写mybatis之SQL执行器的定义和实现的学习,大家一定要跟着动手操作起来。需要源码的 可si我获取;