MyBatis 源码分析-04 - MyBatis Statement 的生成流程
限于篇幅, **本文主要分析内容是 SimpleExecutor.prepareStatement(handler, ms.getStatementLog())这个方法. **
Version :
MyBatis : 3.5.5
项目地址 :MyBatis-source-learn
- PS: 若文章字体偏大或者偏小,建议通过 ctrl键+鼠标滑轮 进行修改,以提升阅读效果.(带来不便,请谅解!)
流程:
传统JDBC连接数据的方式:
1. 获取DataSource 2. 获取连接 Connection 3. 生成Statement 4. 执行Statement 5. 处理结果集ResultSet
6. 关闭ResultSet 7. 关闭Statement 8. 关闭Connection 9.返回结果
Mybatis 执行步骤也是类似的:
1. 通过Transaction获取Connection(管理Connection) 2. Executor处理方法参数生成statement(管理Statement) 3. ResultSetGHandler 处理ResultSet(开启关闭)
测试代码:
ps :其他代码请自己去MyBatis-source-learn查看
测试类:
- CreateSqlSession.java
public class CreateSqlSession {
public static void main(String[] args) {
// 1. 连接数据库,配置基本参数
DataSource dataSource = getDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
// 2. 处理XXXMapper, 生成SqlSource(即处理xml 以及注解生成的SQL语句 与PreparedStatement绑定)
configuration.addMapper(BlogMapper.class);
// 3. 获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 获取mapper,执行方法
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
for (int i = 0; i < 10; i++) {
Blog blog = mapper.selectBlog(1);
System.out.println("i: "+blog);
}
}
**PS: MyBatis 默认查询 顺序是 二级缓存 (CachedExecutor.cache) -> 一级缓存(BaseExecutor.localCache) -> 数据库. **
人话描述总流程:
生成statementHandelr(resultSethandler,)
- StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
生成Statement
stmt = prepareStatement(handler, ms.getStatementLog());//对statement 进行处理,绑定参数到sql中
获取Connection : getConnection(statementLog)
生成Statement , 绑定 方法参数
实例化Statement: stmt = handler.prepare(connection, transaction.getTimeout());
结果: stmt = com.mysql.cj.jdbc.ClientPreparedStatement: select * from Blog where id = ** NOT SPECIFIED **
处理方法参数 : handler.parameterize(stmt);// 处理参数 绑定 sql - java type
结果: stmt= com.mysql.cj.jdbc.ClientPreparedStatement: select * from Blog where id = 1
源码分析:
- SimpleExecutor.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());//对statement 进行处理,绑定参数到sql中
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
1. 生成PreParedStatementHandler
- Configuration.java
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 初始化statement (prepareStatement.callableStatement) ,获取代理对象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);//初始化statement
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);// 添加拦截器, 拦截statement
return statementHandler;
}
PreparedStatementHandler类图
2. 创建Statement
SimpleExecutor.java
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);//利用事务获取connection
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);// 处理参数 绑定 sql - java type
return stmt;
}
SimpleExecutor的类图

获取Connection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();//事务管理connection
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
生成Statement
- BaseStatementHandler.java
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);// 获取statement (preparedStatement, callablestatment)
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);
}
}
instantiateStatement(connection)
- PreParedStatementHandler.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);
}
}
执行结果:
处理方法参数:
- PreparedStatementHandler.java
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
DefaultParameterHandler.java
PS : 可以跳过, 只是对方法参数进行绑定, 然后调用Statement.setXXX(value)方式填充statement
@Override//// TODO: 2021/3/16 给statement 加方法 参数
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {//
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);//preparedStatement.setInt(),setBlob
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
执行结果:
总结:
整体流程:这!
Mybatis 默认查询方法的执行流程是: 二级缓存 -> 一级缓存 -> DB
一级缓存 LocalCache: localCacheScope= session( default), 建议修改为 statement ,session 存在脏读现象
二级缓存 Cache: cacheEnabled=true(default), 对应xxxMapper.xml 添加
即可使用,使用二级缓存,可以有效避免在单表查询时出现的脏读现象,但是在多表查询时,极大可能出现脏数据,存在缺陷.
参考:
- https://mybatis.org/mybatis-3/zh