本文主要介紹Mybatis經過動態代理避免對sqlSession直接調用,而是經過MapperProxy代理技術生成了具體dao接口的Mapper實例,裏面封裝了對sqlSession的調用;Mybatis預留了Interceptor接口,用戶能夠擴展該接口,實現自定義插件;Mybatis與Spring結合主要經過Spring 的FactoryBean技術實現;
java
把Mybatis源碼概覽(一)中的helloWorld例子的註釋代碼打開 以下git
BlogDao mapper = session.getMapper(BlogDao.class); List<Blog> blogs= mapper.selectAll();
這樣經過session.getMapper獲取某個Dao接口的Mapper代理實例,這樣後面查詢就不須要直接對sqlSession來操做,在具體應用中會省略掉不少代碼。具體生產Mapper代理原理咱們能夠Debug一步一步分析。github
第一加載解析Mybatis配置文件時 跟進代碼,下面這個片斷是解析Mybatis幾大屬性 其中最後有個mappersspring
//XMLConfigBuilder類中 private void parseConfiguration(XNode root) { try { Properties settings = settingsAsPropertiess(root.evalNode("settings")); //issue #117 read properties first propertiesElement(root.evalNode("properties")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectionFactoryElement(root.evalNode("reflectionFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //解析具體的mapper映射,轉到下斷代碼 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
//XMLConfigBuilder類中 private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //解析經過xml resource引用其餘mapper文件的方式 //<mapper resource="mapper/BlogMapper.xml"/> String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //獲取到mapper映射文件 而後進行解析到configuration, //這樣之後能夠直接經過configuration取到該mapper XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //執行解析,此步事後,能夠在configuration中看到MapperRegistry屬性裏面已經有實例, //並且其knownMappers已經有值,這一步主要調用MapperRegistry類中的addMapper方法 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
//MapperRegistry類 //解析到mapper對應的dao接口 添加進去,併爲該接口實例一個MapperProxyFacotry //(裏面就是經過JDK Proxy動態生產一個具體接口實例) public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //爲每一個mapper接口添加一個MapperProxyFacotry 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 { if (!loadCompleted) { knownMappers.remove(type); } } } }
這裏詳細介紹下MapperMethod,MapperProxy,MapperProxyFactory,MapperRegistry這四個類之間的關係:
sql
MapperRegistry能夠理解爲一個map ,維護了Mapper接口與具體的MapperProxyFactory的關係,registry通常直接放在congfiguration中,能夠直接用來查詢;得到到具體MapperProxyFactory以後,MapperProxyFactory主要是在newInstance中調用Proxy.newInstance來生產對應Mapper接口的具體MapperProxy,MapperProxy實現了InvocationHandler接口,這個MapperProxy爲了提升效率,裏面爲該Mapper對應的每個方法維護了一個對應的MapperMethod,這樣實現了對MapperMethod的重複利用;MapperProxy在invoke方法中會根據調用的mapper方法名找一個對應的MapperMethod來執行具體的調用sqlSession發起SQL請求;若是沒找到就new一個 ,並放到map緩存起來;緩存
這裏主要是經過動態代理技術把MapperMethod對sqlSession的執行操做封裝到Mapper代理中session
下面主要看下MapperProxy和MapperMethod源碼mybatis
MapperProxy類app
//實現了InvocationHandler,並把每一個mapper方法對應的MapperMethod緩存起來 public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 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) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
MapperMethod類ide
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; //---------省略代碼-----// //能夠看到最後仍是轉換到對sqlSession的調用 public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判斷具體SQL類型 執行增刪改查操做 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()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else if (SqlCommandType.FLUSH == command.getType()) { result = sqlSession.flushStatements(); } 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; } //-------省略代碼--------// }
上面代理生產的準備工做完成後,咱們在執行mapper接口具體方法時會到configuration->mapperRegistry中查找MapperProxy,返回具體的mapperProxy實例
//MapperRegistry類 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); } }
至此經過動態代理技術返回代理對象,實現經過mapper接口直接調用就完成了。
Mybatis預留了Interceptor接口來實現Plugin技術,這裏只簡單介紹一下
首先實現一個Interceptor接口類,而後在配置xml中配置plugin。這樣就能夠根據Interceptor攔截咱們須要的執行過程,好比能夠動態修改MappedStatement,實現對SQL,SQL參數的修改。
這裏介紹一個比較好用的分頁插件 就是利用該原理實現:Mybatis分頁插件 github https://github.com/pagehelper/Mybatis-PageHelper
通常擴展spring 都會用到spring schema技術,並實現相應的handler 解析類,解析相應的擴展配置,這裏很少介紹。
Mybatis-Spring jar中最重要的幾個類就是MapperFactoryBean,SqlSessionFactoryBean,SqlSessionTemplate。
先看看SqlSessionTemplate實現了SqlSession接口,對sqlSession作了一層包裝。這個類構造時候須要SqlSessionFactory參數,主要仍是用來生成SqlSession的,可是這裏生產的是對SqlSession作了代理的,實現了對方法調用的異常處理,事務,session關閉等處理。
看下面SqlSessionTemplate關鍵代碼
//其構造方法,須要傳入sqlSessionFactory來生產sqlSession public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; //生成代理 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } //內部類 實現invocationHandler接口 private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //utils 類的靜態方法 SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { //實現具體調用 Object result = method.invoke(sqlSession, args); //提交事務 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { //異常處理 Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { //關閉session if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
SqlSessionFactoryBean主要是實現了spring FactoryBean,裏面屬性配置包含了datasource(數據源),configLocation(mybatis配置信息,好比plugin等),mapperLocations(咱們寫的sql mapper映射xml地址);有了這些咱們就能夠經過SqlSessionFactoryBean 爲咱們提供SqlSessionFactory實例。咱們就能夠把SqlSessionFactory交給SqlSessionTemplate爲咱們生成session代理。
MapperFactoryBean類主要實現了spring FactoryBean接口,經過getObject爲咱們提供Mapper代理,避免對sqlSession的手工操做。MapperFactoryBean擴展了抽象類SqlSessionDaoSupport,裏面有對SqlSessionTemplate的封裝,爲咱們提供session代理。
MapperFactoryBean中的關鍵代碼
@Override public T getObject() throws Exception { //藉助sqlSessiontTemplate生成的session代理,查找出對應咱們dao接口對應的mapper實例 //這樣咱們在spring中只須要定義相應的dao接口方法參數 便可,不用手工一一來操做sqlsession return getSqlSession().getMapper(this.mapperInterface); }
到這mybatis-spring大概分析完畢,裏面還有不少細枝末葉,這裏就不介紹了。