在使用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(); }