mybatis执行流程的源码分析
文章目录
一、通过SqlSessionFactoryBuilder类创建SqlSessionFactory。
//配置文件
InputStream configFile = new FileInputStream(
projectFile+"\\src\\main\\java\\com\\gupaoedu\\mybatis\\demo\\mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configFile);
二、再通过SqlSessionFactory取获取sqlSession。
sqlSessionFactory.openSession();
三、具体的执行流程。
众所周知mapper是一个接口,如果需要执行sql需要拿到xml配置文件或者annotation注解的sql语句,那么这里肯定用到了动态代理。在sql语句在创建SqlSessionFactory时,将配置文件或者注解配置到注册中心( 类Configuration)里去,在mapper执行sql语句时,通过动态代理获取。下面四、五、六点分别讲注册、动态代理执行、返回结果映射。
四、注册mapper到注册中心( 类Configuration),下面以xml方式举例。
类 SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());//重要的是这句代码,通过XMLConfigBuilder的parse方法将会扫描mybatis-config.xml里<mappers></mappers>的需要注册的*Mapper.xml文件。
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
下面接着看类 XMLConfigBuilder
/*
*被调用parse方法,parse方法再调用parseConfiguration方法。
*/
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/*
*其中mapperElement(root.evalNode("mappers"));
*这句话就是获取<mappers></mappers>的需要注册的*Mapper.xml文件。
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));//获取<mappers></mappers>的需要注册的*Mapper.xml文件。
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
下面是真正起作用的mapperElement 方法。调用 configuration.addMappers(mapperPackage);
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);//将mapper注册到configuration
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);//将mapper注册到configuration
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
configuration.addMappers实际调用的是类MapperRegistry里的addMappers方法,具体细节不再赘述。
public void addMappers(String packageName, Class<?> superType) {
mapperRegistry.addMappers(packageName, superType);
}
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
五、通过动态代理执行mapper。
TestMapper为一个mapper接口。getMapper实际拿到为代理模式产生的一个动态代理对象。
TestMapper testMapper = getSqlSession().getMapper(TestMapper.class);
SqlSession有个两个实现类。SqlSessionManager(线程安全)和DefaultSqlSession(线程不安全),猜想mybatis用的策略模式将其区分。整合spring后用的是类 SqlSessionTemplate 获取。
具体线程安全问题参见文章 《MyBatis 中 SqlSession 是线程安全的吗?》 和 《Mybatis 不是有 SqlSessionManager 了吗?为什么又提供了SqlSessionTemplate?》
这三个SqlSession的实现类都是通过类Configuration获取mapper的代理类。
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
最终还是要通过MapperRegistry的getMapper方法。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//之前addMapper时,已将放置不同类型的mapper的mapperProxyFactory放入到集合knownMappers中。
//key为:mapper的class类型。value为:MapperProxyFactory对象。
//MapperProxyFactory看字面意思就知道其为mapper动态代理对象的工厂类。
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//此处为获取mapper动态代理对象的真正方法。
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
接着往下看MapperProxyFactory
@SuppressWarnings("unchecked")
/**
*这里创建了一个jdk动态代理,实际处理类为MapperProxy。
*/
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);
}
再往下来到真正的实际执行处理类MapperProxy。看到这里的invoke方法,跟传统的代理模式不一样,传统的代理模式应该执行被代理类的方法,由于mybatis的sql语句的方式为xml配置文件或者注解方式。所以mybatis的代理是一个阉割版的代理方式。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
*传统的代理模式,这里应该执行被代理方法。而mybatis这里自己封装了一个MapperMethod去执行sql。
*示例:method.invoke(被代理类,方法参数);
*/
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);//这里是执行的sql的实际方法。
}
下面再看sql具体的执行方法,以select举例。先看MapperMethod的执行方法execute。
/**
*根据策略模式,选择不同增删改查对应的策略对象。最终还是选择相对应sqlSession里的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);
//这里执行SqlSession的update方法。
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的update方法,以其实现类DefaultSqlSession举例。
mybatis的sql执行都封装到了Executor里。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//MappedStatement维护了一条<select|update|delete|insert>节点的封。
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();
}
}
接着再往下看Excutor如何执行。这里以其实现类BaseExecutor举例。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor是一个抽象类,显然用的是模板模式,这里的query是一个模板方法。真正去执行的是其子类,这里用子类SimpleExecutor去分析。中间打断点过程略过,来到实际执行的doQuery方法。
@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());
//这里用封装好的声明处理类StatementHandler来执行。
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
这里拿一个StatementHandler子孙类StatementHandler举例。这里没什么好解释的,就是jdbc的statement执行,如果不了解可以去查看jdbc的使用方法。最终将返回结果用ResultSetHandler进行处理映射,下面第6点详细讲。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
//调用jdbc的statement的执行
statement.execute(sql);
//这里用ResultSetHandler将返回结果进行处理映射。
return resultSetHandler.<E>handleResultSets(statement);
}
六、通过ResultSetHandler对结果进行处理。
接口ResultSetHandler的唯一实现类DefaultResultSetHandler。
public interface ResultSetHandler {
//将返回结果解析成List集合,E对象为泛型定义的实际返回类型。
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
//将返回结果解析成Cursor对象,为Mybatis 3.4.0 新增的功能,使用游标提升性能。
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
//处理存储过程。
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
ResultSetHandler的唯一实现类DefaultResultSetHandler。这里只针对第5点中用到的handleResultSets进行分析,不影响整个流程的梳理。
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//ResultSet结果集集合,对于普通查询只能返回一个ResultSet,存储过程可能会返回多个。
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
//ResultSet装饰类
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
/**
*handleResultSet为实际的处理方法。实现细节不再分析。
*主要执行过程为:
*1.处理ResultSet返回的每一行Row,里面会循环处理全部的结果集。
*2.将处理后的结果,添加到multipleResults中。
*/
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
以上为编程式mybatis执行流程的源码分析。Thanks!