MyBatis執行Sql的一個過程中是怎麼樣的呢,涉及到了那些類呢,讓咱們不妨來一一過一遍。MyBatis的用法能夠採用純MyBatis、或者使用Spring集成的用法。java
原生MyBatis對外暴露的接口層是SqlSession,全部的Sql操做都是靠它來引導完成的,咱們先看下SqlSession是怎麼建立出來的,看下面這段僞代碼:sql
// mybatis 配置文件 String resource = "mybatis-config.xml"; Reader reader = Resources.getResourceAsReader(resource); // 建立SqlSessionFactory,SqlSession的工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession sqlSession = sqlSessionFactory.openSession();
一、經過建立SqlSessionFactoryBuilder來生成SqlSessionFactory;mybatis
二、經過SqlSessionFactory工廠建立SqlSession;app
從開發者的角度來看下怎麼使用MyBatis來執行一次Sql:ide
就只要兩步和普通方法的調用同樣,MyBatis將Sql的執行隱藏了起來。ui
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class); Book book = bookMapper.findByName("Learn Java");
咱們看下底層MyBatis是怎麼實現的吧:this
一、獲取的是Mapper接口的代理實現,基於JDK動態代理spa
// SqlSession public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } // Configuration public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } // 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 { // 每次get都從新建立 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } // MapperProxyFactory 使用JDK動態代理,生成代理對象 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<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
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 { knownMappers.put(type, new MapperProxyFactory<>(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); } } } }
二、MapperProxy的invoker方法.net
@Override 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) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }
關鍵點是MapperMethod對象的execute,MapperMethod和Method是一一映射的關係代理
三、MapperMethod的execute方法,來路由,調用SqlSession的相關方法
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { ... sqlSession.insert(command.getName(), param) break; } case UPDATE: { ... sqlSession.update(command.getName(), param) break; } case DELETE: { ... sqlSession.delete(command.getName(), param) break; } case SELECT: ... sqlSession.selectXXX(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; }
四、SqlSession的增刪改查方法
PS:Mapper接口的方法都會造成一個ID,來和MapperStatement一一映射
String statementId = mapperInterface.getName() + "." + methodName;
@Autowired private BookMapper bookMapper; @Test public void test(){ Assert.notNull(bookMapper); System.out.println(bookMapper.findByName("Learn Java")); }
從開發者的角度來看下怎麼使用MyBatis來執行一次Sql:
這裏和原生MyBatis的使用方式的區別是,這裏的Mapper接口的實現是Spring實現的,是Spring的bean,其餘的用法不變。
讓咱們看Spring是如何實現的?
原生MyBatis和Spring集成MyBatis,在執行Sql的過程當中,惟一的區別是Mapper接口實現的獲取方式不一樣。
原生的須要本身從SqlSession中獲取Mapper接口,而且須要管理這個Mapper接口(爲了不每次都重試獲取生成)。Spring集成的方式,由Spring的IoC容器負責管理Mapper接口,在項目啓動的時候,Spring已經將全部的Mapper接口的代理實現都註冊到了容器裏,而且都是單例的,使用到了MapperFactoryBean。
共同點是,獲取的都是Mapper接口的代理實現,都用到了JDK動態代理。