Mybatis源码分析
@Author:zxw
@School:吉首大学
1.源码启动剖析
先来看看基础的代码
// 获取SqlsessionFactory工厂 SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); // 获取sqlsession SqlSession sqlSession = build.openSession(); // 获取我们的Mapper对象 NewMapper mapper = sqlSession.getMapper(NewMapper.class); New byId = mapper.findById(); New byId2 = mapper.findById(); System.out.println(byId == byId2);
1.1 SqlSessionFactory构建
我们先来看看SqlSessionFactory对象里有什么,其实只有一个Configuration对象,Configuration对象就是我们的核心了,里面有我们配置文件的信息以及缓存相关等等。
// build()方法构造DefaultSqlSessionFactory对象 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 这个主要用来解析我们的mybatis-config.xml配置文件里的东西 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 两层方法,先进行parser.parse(); // 通过parser.parse()获取Configuration对象 // 随后将Configuration传入build方法构造DefaultSqlSessionFactory对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { // 作用不明,里面有TreadLocalMap,推测和当前线程有关 ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
接下来看看parse.parse()方法里面做了什么
public Configuration parse() { // 根据异常,应该用来判断是否注册过 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // evalNode就是用来解析mybatis-config.xml配置文件中的configuration标签 // mybatis的配置文件中的配置都是在configuration标签下,因此这步获取了所有的标签 // 然后进行解析 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
获取完节点后要开始解析配置文件了,既然是解析配置文件,那么就应该是拿到我们在配置文件中写的所有配置
// 通过源码我们就能知道配置文件中能够配置的标签有哪些,就不要每次上网查资料了 // 此代码位于XMLConfigBuilder类中103行 private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 加载别名<properties> propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); // 加载别名<typeAliases> typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 此处加载我们配置的数据库环境 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 此处重点内容,加载我们的xxxMapper.xml文件中的内容 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
让我们继续往下看看加载xxxMapper.xml的代码里是如何实现的
// 此代码位于XMLConfigBuilder类中359行 private void mapperElement(XNode parent) throws Exception { if (parent != null) { // 遍历<mappers>下的所有<mapper> for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { // 一般别人会问你mapper里面有几种加载方式?通过源码查看有3种,resources,url,class // 3种方式的加载优先级如何? // rousece -> url > class 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,这个对象主要存了我们单个mapper里的一些信息,example:Alias,type,parser XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); // 这一步非常重要,设计缓存相关概念,我们后面在绕回来看,先在这里插个眼 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."); } } } } }
自此,我们的Configuration对象就构建完成了,剩下的就是传入DefaultSqlSessionFactory中而已
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
1.2 SqlSession对象
SqlSession对象主要是我们用来操作mapper的对象
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 数据库环境 final Environment environment = configuration.getEnvironment(); // 事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 如果未指定事务处理器,mybatis会使用默认的 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 这个Executor对象我感觉功能上很牛逼,但是目前未知作用,应该是底层调用的时候会使用到 final Executor executor = configuration.newExecutor(tx, execType); // 直接构建就完事了 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
自此构建已经结束,省下的就是实际操作的部分了
2.Mybatis一级缓存原理
2.1 JDK动态代理
现在开始讲mybatis的一级缓存内容,在mybaits操作数据库中,使用的是jdk动态代理方式,使用代理对象对数据库进行操作,我们先来看看代理对象何时生成的
NewMapper mapper = sqlSession.getMapper(NewMapper.class);
我们有一句这样的代码,是用来获取我们的mapper对象,然后通过mapper对象操作接口的方法进行调用,我们看看这段代码底层发生了什么。
通过一路的追踪,终于在MapperRegistry类中发现相关代码,当然具体的实现不再这个类,在MapperProxyFactory中。
个人理解:MapperRegistry就是用来存储我们的mapper类和代理类之间的关系
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 从代理工厂中获取MapperProxyFactory 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); } }
// 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<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
在mapperProxyFacotry中又有一个map,用来建立我们具体的方法和方法操作的关系
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
可能有人有疑问,我们第一次调用mapperFactoryProxy为什么得到的值不是空的?答案就在我们先前插眼的地方,我们来看看插眼的地方具体做了什么。
mapperParser.parse();
public void parse() { // 这里应该只是判断我们的mapper是否已加载 if (!configuration.isResourceLoaded(resource)) { // 第一次的时候未加载,进行存到Set集合中 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); // 这里会加载你在<mapper>中配置的类的路径,通过classForName加载你的Mapper类,见下 bindMapperForNamespace(); } // 加载resultMap parsePendingResultMaps(); // 加载Cache parsePendingCacheRefs(); // 加载statement parsePendingStatements(); }
private void bindMapperForNamespace() { // 获取你配置的路径名称 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 加载你的类 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } // 如果类加载成功 if (boundType != null) { // 判断是否有mapper if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); // 此处就是我们之前提到的MapperRegistry对象了,所以我们明明第一次getMapper()却有数据的原因就在这 // 这个注入的方法我找了一个小时才找到,菜B默默流下了泪水 configuration.addMapper(boundType); } } } }
2.2 代理对象执行过程
代理对象就是我们的MethodProxy对象了,让我们看看它的属性有哪些
private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; // 接口类 private final Map<Method, MapperMethod> methodCache; // 这里就是我们的缓存重点了
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 判断是不是Object类 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) { // 会从mapper属性中获取mapperMethod // 我们现在是第一次调用,所以为null MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { // 创建这个对象的时候会解析xxxMapper.xml中的<select>标签等等 mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
// 代理对象执行方法 public Object execute(SqlSession sqlSession, Object[] args) { // 获取该方法的标签类型,example: select -> insert -> update -> delete Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: 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); // 大概上就是拿到我们的sql语句进行解析,编译,执行的过程 result = sqlSession.selectOne(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; }
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 获取MappedStatement对象,在我们生成mapper的时候会构建一次这个对象 MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 生成我们BoundSql对象,里面包含我们的sql语句以及参数 BoundSql boundSql = ms.getBoundSql(parameter); // 缓存key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
@SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; // 从缓存获取数据,HashMap,PerprtualCache对象 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; // 如果有数据库,执行 if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 代表无缓存,第一次执行 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
自此缓存分析结束,还有一些地方没有分析完全,可以自行研究,比如sql语句执行的方式和时机等等
来源:oschina
链接:https://my.oschina.net/u/4182062/blog/3192549