目录
一、缘起
二、示例代码
1、mybatis单独测试操作代码
2、spring与mybatis的整合操作代码
3、 总结
三、mybatis执行流程
1、mybatis的架构设计图
2、mybatis的执行流程图
3、mybatis执行流程时序图
4、Mybatis的核心组件
5、mybatis执行流程的分析
前三篇的文章主要介绍了Mybatis配置以及在spring的中整合中核心组件SqlSessionFactory对象的创建过程(其中包括了相关子节点的加载,Mapper.xml的加载)。这篇博文主要用来介绍针对Mybatis的curd操作的整个执行流程。因为mybatis单机是对jdbc操作的封装,笔者的分析只到jdbc操作流程(算是mybatis源码的浅尝辄止)。
@Test
public void testMapper() throws IOException {
String resource="mybatis-config.xml";
//以流的方式获取recource(mybatis的环境配置文件)
InputStream inputStream= Resources.getResourceAsStream(resource);
//创建会话工厂
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂得到SqlSession
SqlSession sqlSession= sqlSessionFactory.openSession();
//从创建的SqlSession对象中获取Mapper对象 (这个是核心,因为只有拿到该对象的Mapper对象
//(其实是Mapper接口的代理MapperProxy对象实例))
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
int bookId = 1000;
BookExample bookExample = new BookExample();
bookExample.createCriteria().andIdEqualTo(bookId);
List books = bookMapper.selectByExample(bookExample);
}
@Autowired
private BookMapper bookDao;
@Test
public void testQueryById() throws Exception {
int bookId = 1000;
BookExample bookExample = new BookExample();
bookExample.createCriteria().andIdEqualTo(bookId);
List books = bookDao.selectByExample(bookExample);
}
如上两种方式前者使用代码显示的从SqlSession对象中获取Mapper代理对象,后者则是在spring容器启动后将Mapper对象创建放入Spring容器中进行托管。这里有个疑问在于我们编写的XxxMapper为接口,但是在使用过程中初始化了其相应的Mapper对象。这里我们就来分析一下。
3.1、MapperProxy对象
这里不卖关子了,其实Mybatis为我们动态的生成了XxxMapper对象的的实际调用对象为MapperProxy对象,熟悉JDK动态代理的同学都知道接口中所有的实现都是调用实现了InvocationHandler对象的invoke()方法,我们所有对XxxMapper接口中方法的调用最终都会调用invoke()方法。该对象生成过程之间略有差异,前者是使用DefaultSqlSession的getMapper方法来生成,而后者则是使用SqlSessionTemplate对象的getMapper()方法生成。这里我们来对该MapperProxy对象初始化来做分析。
public T getMapper(Class type) {
//通过Configuration对象获取其中的mapper
return this.getConfiguration().getMapper(type, this);
}
configuration对象是我们mybatis的所有配置的对象,通过调用其getMapper()方法来获取MapperProxy,但是实际操作是被委托给了MapperRegistry对象
public T getMapper(Class type, SqlSession sqlSession) {
//通过configuration对象中的MapperRegistry对象调用其中的getMapper方法
return mapperRegistry.getMapper(type, sqlSession);
}
mapperRegistry根据对应的XxxMapper类class信息以及对应的mybatis会话sqlSession对象来获取dao层代理对象。还使用到了我们初始化Mapper.xml对象的时候将其以 XxxMapper className为key,MapperProxyFactory为value的形式存放在Map类型的结构对象knownMappers中,所以此处获取到对应的MapperProxyFactory对象(该对象中包含了XxxMapper接口类信息),通过Jdk动态代理生成XxxMapper接口的动态dialing对象
public T getMapper(Class type, SqlSession sqlSession) {
//从map容器中获取MapperProxyFactory对象信息
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//调用map中获取的MapperProxyFactory对象 实例化Mapper动态代理对想
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//JDK动态代理实例化dao接口对象
protected T newInstance(MapperProxy mapperProxy) {
//jdk动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//采购员别废话吧MapperProxy对象
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
//dao接口实例化
return newInstance(mapperProxy);
}
(嘻嘻,图是盗了csdn一位大牛的图 ,盗图地址:https://blog.csdn.net/luanlouis/article/details/40422941)
以相关的执行流程梳理出来的一个重要的java对象
二中讲述了Mapper接口的生成过程中提到了MapperProxy对象,所有Mapper接口的调用都最终会到达MapperProxy 的invoke()方法,这里也是我们进行mybatis执行过程分析的入口。
//所有dao层接口的调用最终会到达该方法中进行处理
// proxy为动态代理对象 Method为Dao接口层最终调用的具体方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//根据method从缓存中获取对应的MapperMethod对象,
//如果没有创建MapperMethod 并将其放入缓存中并返回
final MapperMethod mapperMethod = cachedMapperMethod(method);
//最终方法的调用交于MapperMethod对象
return mapperMethod.execute(sqlSession, args);
}
从上面的代码可以看出获取MapperMethod对象,该对象通过execute()来执行真正的数据库调用。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
总结:上述代码主要是针对具体的sql方法 insert|delete|update|select来进行不同的分支操作,分支操作主要有两点:1、将对应的参数转换为sql参数,2、根据不同的curd操作分别调用SqlSession对象的对应方法(insert(),update(),delete()等...)。这里以查询为例子调用DefauleSqlSession的selectList()方法
//以查询为例,查询集合列表
//1、statement 标识XxxMapper.xml文件的某一个curd方法的id 例如
//select id="selectByExample" parameterType="bookExample" resultMap="BaseResultMap">
//中的statement为 全限定类名+selectByExample构成了一个statement
//2、parameter 参数
//3、RowBounds 分页相关的对象
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据statement (statement的唯一标识)
MappedStatement ms = configuration.getMappedStatement(statement);
//最终调用Executor执行器组件来实现对应的sql查询操作
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在上述代码中出现了新的对象Executor,该对象是mybatis的执行器我们实际进行的增删改查都是通过这个Executor来进行的,
主要有两个实现类:BaseExecutor和CachingExecutor,CachingExecutor用于二级缓存,而BaseExecutor则用于一级缓存及
基础的操作BaseExecutor对象下面也有三个子类分别是:
flushStatements
来清除缓存。flushStatements
来清除Map。可以在mybatis的配置文章中进行设置使用什么类型的Executor 默认是SimpleExecutor
有关Executor对象的相关只是参考:mybatis的Executor相关说明
//调用执行器的query方法来执行数据库的底层操作。其中最后一个参数为ResultHandler对查询出来的
//结果集合进行处理的对象
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取包含动态sql的BoundSql对象
BoundSql boundSql = ms.getBoundSql(parameter);
//根据Statamenr、参数,RowBounds(分页对象),boundSql(动态sql) 创建一级缓存所使用的key
//在后面对查询出来的结果进行缓存的时候使用
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//查询操作
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public List 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());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//当queryStack ==0 并且配置了flushCache 为true 则每次调用的时候刷新缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List list;
try {
//每调用一次自增
queryStack++;
//从一级缓存中获取对应的结果信息,如果有则返回,否则从数据库查询
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
//这里的处理主要针对存储过程调用的处理
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库中进行查询操作
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//结束字典,暂时不清楚queryStack的作用
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
//如果配置了本地缓存的范围为Statement 而非SqlSession 也需要情况缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
抛出缓存的问题不表,我们关注的一下执行器的 queryFromDatabase()方法,从数据库中获取数据结果
//通过数据库进行查询
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
//使用占位形式保存数据到一级缓存中
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//调用Executor对象的doQuery方法 在BaseExecutor的doQuery()为抽象方法,
//需要使用子类的实现调用,这里默认使用SimpleExecutor对象
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//移除占位符(感觉此处多此一举)
localCache.removeObject(key);
}
//将结果存放在一级缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
//对于存储过程,存放其参数信息在localOutputParameterCache中
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
查询SimpleExecutor的doQuery()方法
//SimpleExecutor子类执行
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取mybatis的配置信息对象Configuration
Configuration configuration = ms.getConfiguration();
//使用configuration实例化一个StatementHandler对象 该对象主要是用来构建Statement对象的
//StatementHandler的作用就是先通过prepare方法构建一个Statement对象,然后再调用其他方法对
//Statement对象进行处理
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//使用ResultHandler构造Statement对象stmt
stmt = prepareStatement(handler, ms.getStatementLog());
//调用Statement query()方法
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
调用StatementHandler对象 子类 PreparedStatementHandler对象,我们最常用的也是预编译的sql statement处理,所以调用其query
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//jdbc statement的execute() 这里获取到的结果对象Resultset 被存储了供下面代码进行
//结果映射处理的时候使用
ps.execute();
//使用ResultsetHandler 来处理结果映射
return resultSetHandler. handleResultSets(ps);
}
结果映射的处理实现分析,调用DefaultResultSetHandler对象来对相应的结果进行解析处理
ResultSetHandler负责处理两件事:
(1)handleResultSets(Statement stmt) 处理Statement执行后产生的结果集,生成结果列表
(2) handleOutputParameters(CallableStatement cs) 处理存储过程执行后的输出参数
这里我们只探究普通sql的执行过程,不探究存储过程的研究
@Override
public List