SqlSession 是一个接口,它本身并不复杂。构建 SqlSessionFactory 就可以拿到 SqlSession。SqlSession 提供了查询,插入,更新,删除的方法,在旧版本 MyBatis 或 iBatis 中常常使用这些接口方法,而在新版的 MyBatis 中建议使用 Mapper。
SqlSession 内部实现相当复杂,是整个 MyBatis 最难理解的部分。
映射器的动态代理 Mapper 是通过动态代理来实现的。看看源码:
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 public class MapperProxyFactory <T > { private final Class<T> mapperInterface; private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory (Class<T> mapperInterface) { this .mapperInterface = mapperInterface; } public Class<T> getMapperInterface () { return mapperInterface; } public Map<Method, MapperMethodInvoker> 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<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
可以看到动态代理对接口的绑定,作用是生成动态代理对象,而代理的方法则被放到了 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 public class MapperProxy <T > implements InvocationHandler , Serializable { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private static class PlainMethodInvoker implements MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker (MapperMethod mapperMethod) { this .mapperMethod = mapperMethod; } @Override public Object invoke (Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } } }
一旦 mapper 是一个代理对象,它就会运行到 invoke 方法里面,invoke 首先判断是否一个类,显然这里 Mapper 是一个接口不是类,所以判定失败。那么就会生成 MapperMethod 对象,它是通过 cachedMapperMethod 方法对其初始化的,然后执行 execute 方法,把 sqlSession 和当前运行的参数传递进去。
看看 execute 方法的原码:
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 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, mapperInterface, method); } 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); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break ; case FLUSH: result = sqlSession.flushStatements(); break ; default : throw new BindingException("Unknown execution method for: " + command.getName()); } 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.selectList(command.getName(), param, rowBounds); } else { result = sqlSession.selectList(command.getName(), param); } if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } return result; } }
MapperMethod 采用命令模式运行,根据上下文跳转。executeForMany 本质是通过 sqlSession 对象去运行对象的 SQL。
MyBatis 只用 Mapper 接口便能运行 SQL,因为映射器 XML 文件的命名空间对应的便是 Mapper 接口的全路径,根据全路径和方法名便能够绑定起来,通过运态代理技术,让这个接口跑起来。而后采用命令模式,最后还是使用 SqlSession 接口的方法使得它能够执行查询,因为有了这层封装便可以使用的接口编程,使得操作数据库就更简单了。