案例
先看下一次完成的Mybatis查询过程示例代码:
1 2 3 4 5 6 7 8 9 String resource = "mybatis-config.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = session.getMapper(UserMapper.class); User user = userMapper.findUserById(27 ); session.close();
本文先重点分析,以下代码背后的原理
1 2 UserMapper userMapper = session.getMapper(UserMapper.class); User user = userMapper.findUserById(27 );
思考一个问题,为什么userMapper没有接口实现类也能成功执行findUserById()?
创建UserMapper代理对象
首先我们看下getMapper()是怎样获取代理对象的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 DefaultSqlSession.java public <T> T getMapper (Class<T> type) { return configuration.<T>getMapper(type, this ); } ------>>>>>> Configuration.java public <T> T getMapper (Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } ------>>>>>> 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); } } ------>>>>>> MapperProxyFactory.java public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance (MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
大致过程,先获取MapperProxyFactory,然后使用JDK动态代理方式构建出代理对象。在MapperRegistry.java中我们可以知道,mapperProxyFactory由knownMappers.get(type)得到,那么knownMappers中的数据什么时候维护进去的呢?我们看下下面的MapperRegistry.java完整的代码。
MapperRegistry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class MapperRegistry { private Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public MapperRegistry (Configuration config) { this .config = config; } @SuppressWarnings("unchecked") 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> boolean hasMapper (Class<T> type) { return knownMappers.containsKey(type); } public <T> void addMapper (Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory<T>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true ; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } public Collection<Class<?>> getMappers() { return Collections.unmodifiableCollection(knownMappers.keySet()); } ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } } public void addMappers (String packageName) { addMappers(packageName, Object.class); } }
MapperRegistry 类是注册Mapper接口与获取代理类实例的工具类。从上述代码可以看到,addMapper(Class type)方法中维护了knownMappers。而XMLConfigBuilder.mapperElement(XNode) 是在读取配置文件时[XMLConfigBuilder 118行]调用了该方法addMapper(Class type),即在加载配置文件时候为每一个Mapper类(UserMapper)维护了对应的MapperProxyFactory。
MapperProxyFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class MapperProxyFactory <T > { private final Class<T> mapperInterface; private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory (Class<T> mapperInterface) { this .mapperInterface = mapperInterface; } public Class<T> getMapperInterface () { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache () { return methodCache; } @SuppressWarnings("unchecked") 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); } }
从上述代码可以看到在newInstance(MapperProxy mapperProxy)中,利用mapperProxy作为InvocationHandler来创建代理对象。
MapperProxy
MapperProxy的源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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; } public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } 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; } }
从上述代码可以看到,invoke()方法中的逻辑是执行相应的 SQL 语句并将结果集返回,而没有相应的去执行接口实现类方法的代码。 这就是为什么mybatis的mapper没有实现类的原因?
MapperMethod
MapperMethod是MapperProxyFactory类在创建MapperProxy对象时传进来的。我们看下源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod (Class<?> mapperInterface, Method method, Configuration config) { this .command = new SqlCommand(config, mapperInterface, method); this .method = new MethodSignature(config, method); } 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; } private Object rowCountResult (int rowCount) { final Object result; if (method.returnsVoid()) { result = null ; } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { result = rowCount; } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { result = (long ) rowCount; } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { result = (rowCount > 0 ); } else { throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); } return result; } private void executeWithResultHandler (SqlSession sqlSession, Object[] args) { MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); if (void .class.equals(ms.getResultMaps().get(0 ).getType())) { throw new BindingException("method " + command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation," + " or a resultType attribute in XML so a ResultHandler can be used as a parameter." ); } Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args)); } else { sqlSession.select(command.getName(), param, method.extractResultHandler(args)); } } private <E> Object executeForMany (SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; } private <E> Object convertToDeclaredCollection (Configuration config, List<E> list) { Object collection = config.getObjectFactory().create(method.getReturnType()); MetaObject metaObject = config.newMetaObject(collection); metaObject.addAll(list); return collection; } @SuppressWarnings("unchecked") private <E> E[] convertToArray(List<E> list) { E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size()); array = list.toArray(array); return array; } private <K, V> Map<K, V> executeForMap (SqlSession sqlSession, Object[] args) { Map<K, V> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds); } else { result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey()); } return result; } public static class ParamMap <V > extends HashMap <String , V > { private static final long serialVersionUID = -2212268410512043556L ; @Override public V get (Object key) { if (!super .containsKey(key)) { throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet()); } return super .get(key); } }
在核心方法execute(SqlSession sqlSession, Object[] args)中,最后还是根据类型去选择到底执行sqlSession中的哪个方法。 SqlCommand来封装底层的增删改查操作。我们看下SqlCommand源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand (Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException { String statementName = mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null ; if (configuration.hasStatement(statementName)) { ms = configuration.getMappedStatement(statementName); } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); if (configuration.hasStatement(parentStatementName)) { ms = configuration.getMappedStatement(parentStatementName); } } if (ms == null ) { throw new BindingException("Invalid bound statement (not found): " + statementName); } name = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } public String getName () { return name; } public SqlCommandType getType () { return type; } }
我们都会知道节点上有id属性值。那么MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。在SqlCommand中,要找到MappedStatement类就必须通过接口类全限定名+id(方法名)来获得。 以selectOne()为例,看下底层是怎么执行? 1、selectOne()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public <T> T selectOne (String statement, Object parameter) { 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 ; } }
2、selectList()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public <E> List<E> selectList (String statement, Object parameter) { return this .selectList(statement, parameter, RowBounds.DEFAULT); } @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是一个非常重要的接口:
其中BaseExecutor是一个抽象实现类,这里也使用了模板设计模式,这里将一些共性抽取到了BaseExecutor中。 进入了org.apache.ibatis.executor.BaseExecutor#query
BoundSql是对SQL语句的封装,ms(MapperStatment)由前面接口类全限定名+接口方法从Configuration对象中获取。 继续看下一步的query方法
执行querFormData()
localCache会先putObject()一下,这行代码的意义是声明一个占位符,当发送一次查询数据的请求时,设置该占位符告诉其他请求正在查询数据库,请其他请求先阻塞或休眠。当这次请求查询到数据之后,将真正的数据放到占位符的位置,缓存数据。如果其他请求与该次请求查询的数据时一样的,直接从一级缓存中拿数据减少了查询请求对数据库的压力 (org.apache.ibatis.executor.BaseExecutor.DeferredLoad#load org.apache.ibatis.executor.BaseExecutor.DeferredLoad#canLoad),接下来会执行doQuery()方法,doQuery()方法是BaseExecutor中的一个模板方法:
这里会有一个拦截器链去执行Plugins的拦截:
当sqlsessionFactory获取sqlsession时,产生的ParameterHandler、ResultSetHandler、StatementHandler、Executor都是由org.apache.ibatis.session.Configuration 类的方法 newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor产生的代理对象,而这些代理对象是经过适用规则的plugin层层代理的 ,最后会返回一个StatementHandler。
在prepareStatement()方法中,获取了Connection:
接下来会执行handler.query(stmt, resultHandler):
返回结果处理
执行完成后会由ResultSetHandler处理结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public List<Object> handleResultSets (Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results" ).object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0 ; 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(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); }
由handleResultSet()方法逐层深入最后来到getRowValue()方法。
执行org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createResultObject(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap, org.apache.ibatis.executor.loader.ResultLoaderMap, java.lang.String)方法,首先会循环遍历为ResultMapping属性赋值。如果是嵌套查询而且配置了延迟加载,其中这里的createProxy()方法会生成一个具有延迟加载功能的代理对象(这里采用CGLIB代理生成!!!) 。
这里返回的是一个所有属性都为空的结果:
需要在前文getRowValue()中,给各个属性赋值:
大体的执行过程如下:
参考资料