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动态代理。