mybatis是一个ORM框架,使用起来很简单。mybatis和spring集成后,仅需要注入TestMapper:
@Resource
private TestMapper testMapper;
就可以直接使用。Test test = testMapper.selectByPrimaryKey(1);
为了更好地理解mybatis的执行流程,这里使用编程式的mybatis进行debug。
public class Demo {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Test test = testMapper.selectByPrimaryKey(1);
System.out.println("查询结果:" + test.toString());
}
}
一、源码
1.读取配置文件,生成SqlSession对象。这个接口层,SqlSession提供select/insert/update/delete方法,在旧版本中使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。
2.通过SqlSession对象获取TestMapper 接口对象。
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
调用链:DefaultSqlSession->Configuration->MapperRegistry->MapperProxyFactory->MapperProxy.
当 Test test = testMapper.selectByPrimaryKey(1)时,真正实行的是MapperProxy的invoke方法。这里使用了动态代理。
2.1 DefaultSqlSession.java
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
2.2 Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
2.3 MapperRegistry.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
2.4 MapperProxyFactory.java
动态生成一个MapperProxy代理对象。当执行Mapper接口层的方法时,就会调用到MapperProxy的invoke方法。
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
2.5 MapperProxy.java
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);
}
............
}
3. 交给MapperMethod执行。mapperMethod.execute(sqlSession, args);
3.1 MapperMethod.java: execute里面其实就是CRUD操作。
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;
}
3.2 又回到SqlSession执行。跟踪sqlSession.selectOne(command.getName(), param);
DefaultSqlSession.java
@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();
}
}
3.3 一层层的调用。
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());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
3.4 PreparedStatementHandler.java
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
到此, 一次sql的执行流程就完了。
二、重要的对象
SqlSession四大对象(拦截器用到)
1)Execute:调度执行StatementHandler、ParmmeterHandler、ResultHandler执行相应的SQL语句;
2)StatementHandler:使用数据库中Statement(PrepareStatement)执行操作,即底层是封装好了的prepareStatement;
3)ParammeterHandler:处理SQL参数;
4)ResultHandler:结果集ResultSet封装处理返回。
附录
1 加载配置文件,获取Mapper接口代理对象。
2 调用Mapper接口,执行流程图:
虽然网上有很多流程图,而且很多画的比自己好很多。但是为了加深理解,自己又笨拙的花了很长时间debug代码,画了一下执行流程图。
虽然mybatis源码比起spring要简单很多,但是如果对设计模式不了解,debug的过程中还是容易晕车。
跳来提去,不知道跑哪去了。。。要了解模板模式,装饰器模式,代理模式等等。
自己花时间debug一下代码,多跟踪几次,加深理解。
参考文档:
官方文档:http://www.mybatis.org/mybatis-3/zh/index.html
深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)
https://blog.csdn.net/qq_38974634/article/details/81123251