Mybatis的SQL执行流程
1、指定statementId和参数
public static void main(String[] args) throws Exception {
// 指定全局配置文件
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.selectOne("com.hyy.mybatis.demo.mapper.ActivityPoolMapper.selectByActivityType", 2);
}
SqlSession的实现类DefaultSqlSession中的selectOne(statement, parameter)方法:
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
从代码可以看出,selectOne()实际上调用的是selectList()方法,其中如果查询出的记录数大于1,则会抛出Expected one result (or null) to be returned by selectOne(), but found: n异常,相信大家都遇到过这个异常。
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Mybatis的Mapper.xml文件中的每一个select|insert|delete|update在Mybatis初始化时都被封装成一个MappedStatement对象存放在Configiration中的Map<String, MappedStatement> mappedStatements中,其中Key为*Mapper.xml中的select|insert|delete|update的id属性值和namespace + select|insert|delete|update的id属性值,Value为MappedStatement。
其中namespace + statementId可以唯一确定MappedStatement,那么如果存在多个相同的statementId会怎样呢?新建一份Mapper.xml,里面同样有一个statementId为selectAll的<select标签>。mappedStatements中Value的值变化如下:
从前两张图中可以发现,MappedStatements中Key为selectAll的Value值变成了Configuration类中的内部类Ambiguity。我们来看一下Configuration中,往HashMap中put的代码:
public V put(String key, V value) {
// 判断全限定名(namespace + statementId)是否存在
if (containsKey(key))
throw new IllegalArgumentException(name + " already contains value for " + key);
if (key.contains(".")) {
// 获取statementId
final String shortKey = getShortName(key);
// HashMap中不存在则直接将MappedStatement存入
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
// 如果存在,则新建一个Ambiguity对象后存入
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
return super.put(key, value);
}
然后控制台会报如下错误:
因此在不同的namespace中,可以存在id相同的select|insert|delete|update,但是只能使用全限定名来访问。
在获取到MappedStatement后,就会调用Excutor的query方法。该方法的主要作用是动态创建SQL语句、为查询创建缓存,以提高性能,执行SQL语句、以及对返回的数据进行封装。
CachingExecutor类中的query方法:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据传入的参数动态构建SQL语句
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
BaseExecutor中的query方法:
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());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 从数据库中查询数据
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
SimpleExecutor中的doQuery方法:
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);
}
}
SimpleStatementHandler中的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
// 将查询结果组装成List
return resultSetHandler.<E>handleResultSets(statement);
}
是不是看到这么多代码就烦?我也是!我们还是画个时序图来整理一下流程吧。
2、通过Mapper执行
public class MybatisTest {
public static void main(String[] args) throws Exception {
// 指定全局配置文件
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
ActivityPoolMapper mapper = sqlSession.getMapper(ActivityPoolMapper.class);
/*List<ActivityPool> activityPools = mapper.selectByActivityTypes(Arrays.asList(2, 21));*/
/*ActivityPool activityPool = mapper.selectByTypeAndSubType(11, 1);*/
ActivityPool activityPool = mapper.selectByActivityType(2);
}
}
首先来看一下Mapper实例的获取流程:
DefaultSqlSession类:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
从代码中可以看出,Mapper类都是存储在Configuration中。
Configuration类:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
在Configruation中使用了MapperRegistry类来注册所有Mapper实例。
MapperRegistry类:
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);
}
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
可以看到,getMapper()方法返回的是MapperProxy,即Mapper的代理对象。因此,我们在调用Mapper类中的方法时,实际上就是调用的MapperProxy类的invoke()方法。
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);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
在invoke()方法中,新建了一个MapperMethod实例,并放入methodCache中。
接下来就是调用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 {
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;
}
这里if-else if-else就不多介绍了,讲一下MapperMethod.convertArgsToSqlCommandParam()方法:
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
代码中的params的类型为SortedMap<Integer, String>,其中Key为参数的index,Value为index或者@Param注解中的值。
List<ActivityPool> selectByActivityTypes(List<Integer> activityTypes);
ActivityPool selectByTypeAndSubType(@Param("activityType") int activityType, @Param("activitySubType") int activitySubType);
hasNamedParameters的作用是判断是否有@Param注解:
private boolean hasNamedParams(Method method) {
boolean hasNamedParams = false;
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
for (Object aParamAnno : paramAnno) {
if (aParamAnno instanceof Param) {
hasNamedParams = true;
break;
}
}
}
return hasNamedParams;
}
随后的过程跟1中的一样