在众多的ORM框架中,Mybatis如今愈来愈多的被互联网公司所使用;主要缘由仍是由于Mybatis使用简单,操做灵活;本系列准备经过提问的方式来从源码层来更加深刻的了解Mybatis。git
咱们最经常使用的使用Mybatis的方式是获取一个Mapper接口对象,而后经过接口的方法名映射到配置文件中的statement;大体的代码格式以下所示:github
public class BlogMain { public static void main(String[] args) throws IOException { String resource = "mybatis-config-sourceCode.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); try { BlogMapper mapper = session.getMapper(BlogMapper.class); // 常规方法 System.out.println(mapper.selectBlog(101)); // Object的方法 System.out.println(mapper.hashCode()); // public default方法 System.out.println(mapper.defaultValue()); // 父接口中的方法 System.out.println(mapper.selectParent(101)); } finally { session.close(); } }
以上除了使用常规的接口方法selectBlog,还使用了类型彻底不一样的方法分别是:Object内部方法,接口的默认方法,以及父类中的方法,固然Mybatis都能很好的处理,那咱们每次调用的接口的方法时,Mybatis是如何帮咱们执行sql的;接下来将进行分析,同时一并看一下是如何处理这些特殊方法的。sql
经过使用动态代理,生成一个代理类,而后经过Mapper里面的方法名称和配置文件中的statement名称作映射,而后根据statement类型分别执行sql。缓存
首先分析getMapper操做,而后再分析执行Mapper中的相关方法是如何调用相关sql的;session
如上代码中使用openSession建立的一个DefaultSqlSession类,此类中包含了执行了sql的增删改查等操做,另外还包含了getMapper方法:mybatis
private final Configuration configuration; @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
此处的Configuration是关键,也是Mybatis的一个核心类,能够先简单理解为就是咱们的配置文件mybatis-config.xml的一个映射类;继续往下走:app
protected final MapperRegistry mapperRegistry = new MapperRegistry(this); public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
这里引出了MapperRegistry,全部的Mapper都在此类中注册,经过key-value的形式存放,key对应xx.xx.xxMapper,而value存放的是Mapper的代理类,具体如类MapperRegistry代码所示:框架
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); 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); } }
能够看到每次getMapper的时候其实都是去knownMappers获取一个MapperProxyFactory类,至因而什么时候往knownMappers中添加数据的,是在解析mybatis-config.xml配置文件的时候,解析到mappers标签的时候,以下所示:ide
<mappers> <mapper resource="mapper/BlogMapper.xml" /> </mappers>
继续解析里面的BlogMapper.xml,会把BlogMapper.xml中的namespace做为key,以下所示:优化
<mapper namespace="com.mybatis.mapper.BlogMapper"> <select id="selectBlog" parameterType="long" resultType="blog"> select * from blog where id = #{id} </select> </mapper>
namespace是必填的,此值做为MapperRegistry中的knownMappers的key,而value就是此Mapper类的一个代理工厂类MapperProxyFactory,每次调用getMapper的时候都会newInstance一个实例,代码以下:
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); @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); }
能够发现经过jdk自带的代理类Proxy.newProxyInstance(...)建立了一个代理类,设置MapperProxy做为InvocationHandler,在实例化MapperProxy时同时传入了一个methodCache对象,此对象是一个Map,存放的就是每一个Mapper里面的方法,这里定义为MapperMethod;至此咱们了解了getMapper的大体流程,下面继续看执行方法;
由上分析可知,经过getMapper返回的是Mapper的一个动态代理类,而且指定了MapperProxy做为InvocationHandler,因此咱们每次调用方法时其实调用了MapperProxy中的invoke方法:
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); } 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; }
以上有两个判断分别是:是不是Object类中的方法,以及是不是默认方法;这两种状况也是我在上面实例中展现的缘由,是不须要映射xxMapper.xml中的statement的,能够直接执行返回结果;接下来就是非这两种状况的处理,这里使用的缓存作优化,也就是说我若是连续调用同一个Mapper下面同一个方法屡次,不会建立多个MapperMethod;为何须要缓存,主要是由于每次实例化MapperMethod须要初始化不少东西,以下所示:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }
主要是SqlCommand和MethodSignature这两个实例,这两个类大体意思是:SqlCommand保存了方法是何种操做类型包括增删改查,未知,刷新以及对应的xxMapper.xml中的statement的ID;MethodSignature保存了方法的签名包括返回类型等;此处咱们大体了解一下就行,后面的文章会继续进行详细介绍;有了上面初始化好的这些参数就能够执行调用MapperMethod的execute方法了:
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; }
根据SqlCommand的命令类型分别执行不一样的sql;执行时还须要对参数进行处理,执行完以后还须要对结果集进行处理,固然还有缓存结果集的处理;此处咱们大体了解一下就行,后面每一个点会单独进行提问介绍;好了执行完以后就能够返回结果了;
本文大体了解到经过getMapper获取了一个xxMapper接口的动态代理类,而且每次get操做都会获取一个新的对象,Mybatis并无对此类进行缓存,而是对xxMapper接口中的每一个方法(MapperMethod)进行缓存,这里的缓存方法是被每一个动态代理类对象所共享的,没有对代理类进行缓存主要是由于每一个类能够有本身的sqlSession;另一点是Mybatis处理了是不是Object类中的方法,以及是不是默认方法两种特殊的方法;最后根据方法名称映射的statement执行相关的sql。