在前一篇文章Mybatis源码解析《一》中,已经简单了捋了一下mybatis核心文件和mapper配置文件的一个基本的解析流程,这是理解mybatis的基本,和spring中的配置文件的加载解析是一样的道理。
既然说完了文件的加载,那么接下来便是关于mybatis的核心流程了,SqlSession的创建、SQL语句的操作、session的commit和关闭。在正式开始之前先回顾下上一篇文章中的测试代码。
public class MybatisTest {
@Test
public void test() throws Exception {
User user = new User();
user.setAddress("北京市海淀区");
user.setBirthday(new Date(2000-10-01));
user.setSex("男");
user.setUsername("李清源");
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("insertUser", user);
sqlSession.commit();
sqlSession.close();
}
}
本篇文章将从SqlSession sqlSession = sqlSessionFactory.openSession()这行代码开始,那么就不多说了,开始mybatis源码的旅行了。
从代码中可以得知,sqlSesssion是通过sqlSessionFactory去打开session的,那就看下代码具体是如何体现的:
DefaultSqlSessionFactory.java
public SqlSession openSession() {
// 通过数据源打开session,第一个参数是通过configuration获取默认的执行器类型(这个configuration贯穿整个mybatis的执行),对于它的作用,后面看
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
这段代码中调用openSessionFromDataSource方法,方法中传入默认执行器类型、null、和false,这几个参数的意义分别是:默认执行器类型、事务隔离级别、是否自动提交数据库。继续看这个调用方法的代码:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 通过configuration获取开发环境(这个在核心配置文件中在environments有个environment标签)
final Environment environment = configuration.getEnvironment();
// 通过environment获取事务工厂(在我上面那段配置文件中写的是JDBC)
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 通过环境中获取的数据源、事务的隔离级别、是否自动提交来创建一个事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// configuration通过事务和执行器类型来创建一个执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 最后通过configuration(配置对象)、执行器、是否自动提交创建一个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();
}
}
这段代码中还算比较清晰的,在这段代码中,它的基本流程有如下几步:
通过sqlSessionFactory去打开sqlSession后,便可以通过sqlSession来操作SQL语句了,但是这里所要考虑的是通过这个操作是怎么操作数据的,那就带着这个疑问来看代码中是怎么实现的。
public int insert(String statement, Object parameter) {
// 这里传入的两个参数是sqlSession.insert("insertUser", user)这行代码所传的第一个参数"insertUser",这个参数是SQL命令的id编号,也就是mapper配置文件中所对应的,
// 第二个参数是user对象
return update(statement, parameter);
}
上面提到了通过insert这个方法是怎么最后实现数据库的操作的,说到这里,从上面代码中来看,来把问题细分一下。在注释里说了"insertUser"这个参数的值是mapper文件中,所对应的标签id,因此在这里首先就要思考下这个id和SQL语句是如何进行定位的?当定位好之后对于所传入的user对象中的数据应该如何与SQL命令进行绑定呢?最后在数据与SQL命令绑定好之后,这样的SQL命令是如何输送的呢?
带着上面的这样的几个问题,来继续看代码中是如何实现的。首先来看下上面代码中所调用的update(statement, parameter)方法:
public int update(String statement, Object parameter) {
try {
// 这里算是首次出现dirty,这个意思是脏的意思,是为了把SQLSession设置为true,这是个关注点
dirty = true;
// 通过所传入的参数statement,由configuration对象来获取mappedStatement并赋值给MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 然后通过所获取到的mappedStatement和wrapCollection(parameter)锁处理后的object对象交由执行器执行
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
上面的代码中大体就三步:
这三步中的第二步锁获取到的结果最后所赋值给ms的这个对象的结果如下图:
从上图中,可以清晰的看出通过所传入的参数,然后又这个参数定位这个SQL的位置,然后再找到这个SQL配置文件中的真正SQL语句。
Configuration.java
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
// 这个方法是做各种校验的,但是按照现在的测试代码来看,是基本上都不走的
buildAllStatements();
}
// 通过id在缓存中获取Statement相关数据
return mappedStatements.get(id);
}
public V get(Object key) {
V value = super.get(key);
if (value == null) {
throw new IllegalArgumentException(name + " does not contain value for " + key);
}
if (value instanceof Ambiguity) {
throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
+ " (try using the full name including the namespace, or rename one of the entries)");
}
return value;
}
上面的代码,就是configuration.getMappedStatement(statement)的整个执行流程,其实就是把之前mapper文件所解析缓存的内容,此时再取出来。
在得到mappedStatement对象后,按照一开始的几个问题来看,这里的SQL定位已经完成了,那么现在要做的就是SQL模板的赋值了。那继续看executor.update(ms, wrapCollection(parameter))方法中的具体操作。在看update方法之前先看下wrapCollection这个方法中做了什么。
private Object wrapCollection(final Object object) {
// 判断所传入的对象是否是集合,是的话就走当前的逻辑
if (object instanceof Collection) {
StrictMap
因为前面所传入的参数,既不是集合也不是数组,所以这里返回的还是原对象。那就来看update方法。
BaseExecutor.java
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
// 核心入口
return doUpdate(ms, parameter);
}
这段代码很简单,但是还是没有到达我们的核心代码中,那只有继续看核心入口doUpdate(ms, parameter)方法的具体实现了。
SimpleExecutor.java
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
// 总算看到点希望了,在JDBC里面所有的SQL都是依赖Statement去输送的
Statement stmt = null;
try {
// 从前面所传入的mappedStatement中来获取configuration配置对象
Configuration configuration = ms.getConfiguration();
// 通过this(SimpleExecutor)、ms(sql)、parameter(传入的对象)、行绑定等来创建一个StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 这里是把所传入的对象的数据和SQL模板进行一个绑定合成一句完整的SQL
stmt = prepareStatement(handler, ms.getStatementLog());
// 最后执行SQL
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
这段代码中主要的两个作用就是把所传入的对象的数据和SQL模板进行一个绑定合成一句完整的SQL和执行SQL操作数据库,那先看下prepareStatement(handler, ms.getStatementLog())方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取链接信息
Connection connection = getConnection(statementLog);
// 准备stmt
stmt = handler.prepare(connection, transaction.getTimeout());
// 最后参数绑定整合
handler.parameterize(stmt);
return stmt;
}
先看下准备stmt的过程,这里代码很简单。
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 实例化statement
statement = instantiateStatement(connection);
// 设置statement超时时间
setStatementTimeout(statement, transactionTimeout);
// 设置fetch的size
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);
}
}
接下来看下最后参数的整合绑定,核心代码如下:
DefaultParameterHandler.java
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List 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);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
这里的代码相对比较复杂,感兴趣的同学可以自己debug进来研究一下,这里就是最后数据的绑定SQL模板的过程了。在看完第二个问题后,那来看看最后一个问题,就是SQL的执行了。
PreparedStatementHandler.java
public int update(Statement statement) throws SQLException {
// 把statement赋给PreparedStatement
PreparedStatement ps = (PreparedStatement) statement;
// 最后做执行操作
ps.execute();
// 把执行结果返回
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
写着写着发现篇幅又很长了,看来后面的操作的代码解析只能放到下篇文章去做解析了,这有点超出我的所预想的,那本篇文章就先到这里了,感谢大家的阅读,如有错误请大家指出,谢谢!