一.整体执行过程
整体执行过程大致如上图所示。具体的步骤可以拆分成一下内容
1.首先解析配置,得到Configuration
2.创建SqlSession回话,用于和数据库完成交互。
3.SqlSession中创建不同的Executor执行程序。
4.Executor中创建StatementHandler来调用jdbc程序。
5.Executor中指定ParameterHandler来将java类型转化成jdbc类型,然后查询数据库,数据库返回的数据通过ResultSetHandler把jdbc类型转化成java类型。
二.具体执行过程分析
整体的执行过程的时序图如上图所示。
下面一起来分析整个执行过程
1.创建mybatis的环境
本次验证过程中,使用的mybatis的编程式方式调用
mybatis-config.xml文件配置如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="SLF4J"/>
<!--<setting name="cacheEnabled" value="true" />-->
</settings>
<!--<plugins>-->
<!--<plugin interceptor="com.gupaoedu.mybatis.plugins.TestPlugin">-->
<!--</plugin>-->
<!--</plugins>-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://172.16.159.211:3306/user"/>
<property name="username" value="root"/>
<property name="password" value="******"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="TestMapper.xml"/>
</mappers>
</configuration>
pom依赖文件
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.2</version>
</dependency>
</dependencies>
TestMapper接口
public interface TestMapper {
User selectByPrimary(Integer id);
}
TestMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace= "com.mybaties.demo.TestMapper">
<!--<cache-->
<!--eviction="FIFO"-->
<!--flushInterval="60000"-->
<!--size="512"-->
<!--readOnly="true"/>-->
<select id="selectByPrimary" resultType="com.mybaties.demo.User">
select * from user where id = #{id}
</select>
</mapper>
测试类
/**
* 通过接口形式调用
*/
@Test
public void Test() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("/Applications/study/java/freamework-study/mybaties/src/main/java/com/mybaties/demo/mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(fis);
SqlSession sqlSession = sqlSessionFactory.openSession();
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
User user = mapper.selectByPrimary(2);
System.out.println(user.toString());
}
2.开始调试代码分析
我们知道mabtis的调用方法是通过接口调用的,但是接口是不能被实例化的,那具体的调用是怎么的呢?
TestMapper mapper = sqlSession.getMapper(TestMapper.class);
在程序调用上一端代码的时候,进入了MapperProxy的invoke方法,而且返回的是MapperProxy的代理对象,所以我们可以知道mybatis是 通过代理模式来调用接口中的方法的。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
继续跟踪
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
...
case UPDATE:
...
case DELETE:
...
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
case FLUSH:
...
}
由于我们调用的是select方法 ,那么 在将会调用sqlSession.selectOne的方法,selectOne方法的具体实现在selectList
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
...
}
return var5;
}
sqlSession找到对应的MappedStatement对象,然后委派给 executor来完成调用
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//拼接sql
BoundSql boundSql = ms.getBoundSql(parameter);
//拼装缓存key
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
...
List list;
try {
++this.queryStack;
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//查询数据库
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
...
return list;
}
}
queryFromDatabase调用doQuery方法
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var10;
try {
this.flushStatements();
Configuration configuration = ms.getConfiguration();
//创建StatementHandler,在构造方法中初始化了ParameterHandler,ResultSetHandler
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
Connection connection = this.getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, this.transaction.getTimeout());
//将请求参数放入到查询sql
handler.parameterize(stmt);
var10 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var10;
}
调用jdbc程序查询数据库,并将jdbc类型转成java类型,返回查询结果。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
3.总结
总体来说,mybatis的程序体系并不复杂。SqlSession代表一次完整的数据库回话,Executor代表一次查询动作,Executor通过委派StatementHandler来与数据库交互。StatementHandler又含有ParameterHandler和ResultSetHandler两个处理器,这两个处理器 完成jdbc类型和java类型之间的相互转换。