本章将以简单SQL查询为例,在前两章初始化基础上讲解Mybatis运行时流程。然后会针对Mybatis运行期核心实现进行详细阐述
本章以下面的简单查询案例进行分析:
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectByPrimaryKey(1);
System.out.println(user.getUserName());
}
Mybatis SQL执行流程
Mybatis执行SQL完整流程如下:
- 初始化Mybatis,获得SqlSession对象
- 通过SqlSession获得 UserMapper接口 代理类
- 执行UserMapper中查询方法 selectByPrimaryKey方法
- 返回查询结果
Step1: 初始化Mybatis,获得SqlSession对象
关于Mybatis初始化流程,笔者已经在《深入理解Mybatis原理》一栏中的前两章详细讲解,感兴趣的同学可以跳转阅读。
《深入理解Mybatis原理》 01-Mybatis初始化机制、《深入理解Mybatis原理》 02-Mybatis数据源与连接池
Step2: 通过SqlSession获得 UserMapper接口 代理类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
从sqlSession.getMapper(UserMapper.class)入口方法进入,首先会检查MapperRegister中有没有UserMapper代理类,若没有则进行创建:
//1. 首先会创建一个MapperProxy<UserMapper>对象
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
//MappperProxy类(省略部分代理)
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
//2.通过JDK动态代理返回 UserMapper 代理类
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Step3: 执行UserMapper中查询方法 selectByPrimaryKey方法
由于Step2中 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);返回的是一个代理类。
所以 User user = mapper.selectByPrimaryKey(1); 查询语句会执行 代理类的 invoke方法。
在invoke方法中,会首先从methodCache中获取查询语句方法。若没有则会为此次method创建MapperMethod。
并将新创建的MapperMethod缓存到mrthodCache中
//MapperProxy invoke代理方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断method所在类是否为Java Object类
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {//判断methos是的为所在类中的默认方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
接下来会调用MapperMethod的execute(sqlSession,args)方法准备底层sql查询。
根据 select/update/delete/insert不同类型sql到相应的sqlSession中执行相应的 select/update/delete/insert方法
//根据 sql类型,执行XXXSqlSession中相应的执行方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
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 if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}
接下来SqlSession会调用XXXExecutor进入真正sql查询的开始阶段
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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包括:
CachingExecutor、BaseExecutor
以DefultSqlSession为例:sqlSession首先会调用 CachingExecutor 进行sql方法执行
为什么会首先调用CachingExecutor 呢?
因为Mybatis支持缓存机制,所以会首先调用 CachingExecutor判断将要执行的sql所在的mapper中是否配置缓存(cache节点),若缓存可以命中则直接返回结果
若没有配置cache缓存则调用 BaseExecutor 进一步查询。(BaseExecutor中有localCach对查询进行缓存,commit之前此缓存是有效的)
BaseExceutor最终会调用 SimpleExecutor (以简单查询为例)进行底层原生JDBC 查询并返回查询结果
@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());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
以上就是本文 《深入理解Mybatis原理》 03-Mybatis SQL执行流程 的全部内容,
上述内容如有不妥之处,还请读者指出,共同探讨,共同进步!
@author : [email protected]