Mybatis根据给出的mapper接口与对应的xml文件,利用jdk的动态代理来实现相应的逻辑。再调用具体的sqlsession,通过相应的excutor执行相关的sql逻辑。
关于Mapper代理,就从Configuration的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); }
无非扫描包,到每个类调用具体的addMapper。
public void addMappers(String packageName, Class<?> superType) { //查找包下所有是superType的类 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); } }
我们再来看addMapper的逻辑
public <T> void addMapper(Class<T> type) { //mapper必须是接口!才会添加 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)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { //如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之? if (!loadCompleted) { knownMappers.remove(type); } } } }
mapper必须是接口,先判断有无重复添加,再根据类型创建相应的MapperProxyFactroy,并添加至knowMappers(存放mapper工厂的map),再之后创建MapperAnnotationbuilder对类中的相关注解进行解析。
若这个过程中出错,直接移除相应的MapperProxyFactroy。
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
根据mapper中的注解进行配置,并且如果未加载相关xml资源的话在这里会重新加载,并且parseStatement处理mapper中的每一个方法(无非是配置相应参数加载到assistant)。
到这里为止~准备工作算是差不多了,剩下就是用了。
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无非先得到对应的代理工厂,再返回newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
protected T newInstance(MapperProxy<T> mapperProxy) { //用JDK自带的动态代理生成映射器 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
从上面代码中可以看出,通过jdk的动态代理,代理是MapperProxy类的对象,实现的是mapper接口,也就是说真正被代理的是mapperProxy。
jdk的动态代理,得到代理对象后调用的方法,都是通过invoke();
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法 //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行 if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //这里优化了,去缓存中找MapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); //执行 return mapperMethod.execute(sqlSession, args); } //去缓存中找MapperMethod private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { //找不到才去new mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
思路很清晰,这里有个缓存methodCache的一个小的优化,只进行一次解析,空间换时间。
再进入MapperMethod的构造方法。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, method); }
其实就是对方法的数据的一个封装,execute方法:
//执行 public Object execute(SqlSession sqlSession, Object[] args) { Object result; //可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法 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()) { //如果结果是map 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; }
根据command里的配置信息执行sqlSession不同的方法,convertArgsToSqlCommandParam是对传入参数的一个处理,rowCountResult无非是对返回值的一个合法性处理。
具体执行是通过sqlSession,已经相应的executor,执行bandSql。