在使用ibatis执行数据库访问时,会调用形如
getSqlMapClientTemplate().queryForObject("getCityByCityId", cityId);
这样的代码。这样的形式要求调用方选择需要使用的函数(queryForObject、queryForList、update),还需要告诉这个函数具体执行哪一个statement(上文中是“getCityByCityId”),在这个过程中如果有一个地方选择错误或者拼写错误,不仅没有办法达到自己的期望值,可能还会出现异常,并且这种错误只有在运行时才能够发现。
mybatis对此进行了改进,只要先声明一个接口,就可以利用IDE的自动完成功能帮助选择对应的函数,简化开发的同时增加了代码的安全性:
SqlSession session = sqlSessionFactory.openSession(); try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog =mapper.selectBlog(101
); } finally { session.close(); }
这个功能就是在利用java的动态代理在binding包中实现的。对动态代理不熟悉的读者可以查看(http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html)。
一、binding包整体介绍
这个包中包含有四个类:
- BindingException 该包中的异常类
- MapperProxy 实现了InvocationHandler接口的动态代理类
- MapperMethod 代理类中真正执行数据库操作的类
- MapperRegistry mybatis中mapper的注册类及对外提供生成代理类接口的类
二、MapperMethod
MapperProxy关联到了这个类,MapperRegistry又关联到了MapperProxy,因而这个类是这个包中的基础类,我们首先介绍这个类的主要功能以及该类主要解决了那些问题。
这个类包含了许多属性和多个函数,我们首先来了解下它的构造函数。
//declaringInterface 已定义的mapper接口 //method 接口中具体的一个函数 //sqlSession 已打开的一个sqlSession public MapperMethod(Class<?> declaringInterface, Method method, SqlSession sqlSession) { paramNames = new ArrayList<String>(); paramPositions = new ArrayList<Integer>(); this.sqlSession = sqlSession; this.method = method; this.config = sqlSession.getConfiguration();//当前的配置 this.hasNamedParameters = false; this.declaringInterface = declaringInterface; setupFields();//确定这个方法在mapper中的全配置名:declaringInterface.getName() + "." + method.getName(); setupMethodSignature();//下文进行详细介绍 setupCommandType();//设置命令类型,就是确定这个method是执行的那类操作:insert、delete、update、select validateStatement(); }
在构造函数中进行了基本属性的设置和验证,这里面稍微复杂的操作是setupMethodSignature,我们来看其具体的功能:
//在具体实现时利用了Java的反射机制去获取method的各项属性 private void setupMethodSignature() { //首先判断方法的返回类型,这里主要判断三种:是否有返回值、返回类型是list还是map if( method.getReturnType().equals(Void.TYPE)){ returnsVoid = true; } //isAssignableFrom用来判定两个类是否存在父子关系;instanceof用来判断一个对象是不是一个类的实例 if (List.class.isAssignableFrom(method.getReturnType())) { returnsList = true; } if (Map.class.isAssignableFrom(method.getReturnType())) { //如果返回类型是map类型的,查看该method是否有MapKey注解。如果有这个注解,将这个注解的值作为map的key final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class); if (mapKeyAnnotation != null) { mapKey = mapKeyAnnotation.value(); returnsMap = true; } } //确定函数的参数 final Class<?>[] argTypes = method.getParameterTypes(); for (int i = 0; i < argTypes.length; i++) { //是否有为RowBounds类型的参数,如果有设置第几个参数为这种类型的 if (RowBounds.class.isAssignableFrom(argTypes[i])) { if (rowBoundsIndex == null) { rowBoundsIndex = i; } else { throw new BindingException(method.getName() + " cannot have multiple RowBounds parameters"); } } else if (ResultHandler.class.isAssignableFrom(argTypes[i])) {//是否有为ResultHandler类型的参数,如果有设置第几个参数为这种类型的 if (resultHandlerIndex == null) { resultHandlerIndex = i; } else { throw new BindingException(method.getName() + " cannot have multiple ResultHandler parameters"); } } else { String paramName = String.valueOf(paramPositions.size()); //如果有Param注解,修改参数名;如果没有,参数名就是其位置 paramName = getParamNameFromAnnotation(i, paramName); paramNames.add(paramName); paramPositions.add(i); } } }
前面介绍了MapperMethod类初始化相关的源代码,在初始化后就是向外提供的函数了,这个类向外提供服务主要是通过如下的函数进行:
public Object execute(Object[] args) { Object result = null; //根据初始化时确定的命令类型,选择对应的操作 if (SqlCommandType.INSERT == type) { Object param = getParam(args); result = sqlSession.insert(commandName, param); } else if (SqlCommandType.UPDATE == type) { Object param = getParam(args); result = sqlSession.update(commandName, param); } else if (SqlCommandType.DELETE == type) { Object param = getParam(args); result = sqlSession.delete(commandName, param); } else if (SqlCommandType.SELECT == type) {//相比较而言,查询稍微有些复杂,不同的返回结果类型有不同的处理方法 if (returnsVoid && resultHandlerIndex != null) { executeWithResultHandler(args); } else if (returnsList) { result = executeForList(args); } else if (returnsMap) { result = executeForMap(args); } else { Object param = getParam(args); result = sqlSession.selectOne(commandName, param); } } else { throw new BindingException("Unknown execution method for: " + commandName); } return result; }
这个函数整体上还是调用sqlSession的各个函数进行相应的操作,在执行的过程中用到了初始化时的各个参数。
三、MapperProxy
这个类继承了InvocationHandler接口,我们主要看这个类中的两个方法。一是生成具体代理类的函数newMapperProxy,另一个就是实现InvocationHandler接口的invoke。我们先看newMapperProxy方法。
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) { //先初始化生成代理类所需的参数 ClassLoader classLoader = mapperInterface.getClassLoader(); Class<?>[] interfaces = new Class[]{mapperInterface}; MapperProxy proxy = new MapperProxy(sqlSession);//具体要代理的类 //利用Java的Proxy类生成具体的代理类 return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy); }
我们再来看invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行 if (!OBJECT_METHODS.contains(method.getName())) { final Class<?> declaringInterface = findDeclaringInterface(proxy, method); //生成MapperMethod对象 final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession); //执行MapperMethod对象的execute方法 final Object result = mapperMethod.execute(args); //对处理结果进行校验 if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) { throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } return null; }
四、MapperRegistry
这个类会在Configuration类作为一个属性存在,在Configuration类初始化时进行初始化:
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
从类名就可以知道这个类主要用来mapper的注册,我们就首先来看addMapper函数:
public void addMapper(Class<?> type) { //因为Java的动态代理只能实现接口,因而在注册mapper时也只能注册接口 if (type.isInterface()) { //如果已经注册过了,则抛出异常,而不是覆盖 if (knownMappers.contains(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //先将这个mapper添加到mybatis中,如果加载过程中出现异常需要再将这个mapper从mybatis中删除 knownMappers.add(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 { if (!loadCompleted) { knownMappers.remove(type); } } } }
下面我们来看下其向外提供生成代理对象的函数:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //如果不存在这个mapper,则直接抛出异常 if (!knownMappers.contains(type)) throw new BindingException("Type " + type + " is not known to the MapperRegistry."); try { //返回代理类 return MapperProxy.newMapperProxy(type, sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
五、代理类生成的顺序图
我们在开篇时提到了如下的代码:
SqlSession session = sqlSessionFactory.openSession(); try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog =mapper.selectBlog(101
); } finally { session.close(); }