MyBatis源码分析(上)

  相信大家平时工作中都有用过MyBatis这个ORM框架,但不知大家是否在工作之余,有研究过框架源码?本篇文章会带大家一起简单过一遍MyBatis的关键源码,没看过源码的同学可以快速了解整个流程,看过源码的同学则可以温故一下相关知识点。

 

  一个标准的MyBatis用法代码如下:

 1 String resource = "org/mybatis/example/mybatis-config.xml";
 2 
 3 InputStream inputStream = Resources.getResourceAsStream(resource);
 4 
 5 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 6 
 7 SqlSession session = sqlSessionFactory.openSession();
 8 
 9 try {
10   BlogMapper mapper = session.getMapper(BlogMapper.class);
11   Blog blog = mapper.selectBlog(101);
12 } finally {
13   session.close();
14 }

第1行和第3行代码就不用详细说了,就是获取项目中Mybatis的配置文件,并将之转换为流以供之后创建SqlSessionFactory使用。本篇文章会详细分析第5行和第7行代码,看看Mybatis在这里究竟干了些啥事。

 

SqlSessionFactory,将资源文件中所有元素对应的内容设置到Configuration对象中

顾名思义,这是一个工厂类,它主要的作用就是用来创建SqlSession这个对象。从第5行代码中可以看到其实是调用了 SqlSessionFactoryBuilder这个类的build方法,并传入了Mybatis的配置文件对应的流作为参数,最终返回了SqlSessionFacotory这个工厂对象。我们进入build方法看下做了哪些事。

 

 1  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 2     try {
 3       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 4       return build(parser.parse());
 5     } catch (Exception e) {
 6       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
 7     } finally {
 8       ErrorContext.instance().reset();
 9       try {
10         inputStream.close();
11       } catch (IOException e) {
12         // Intentionally ignore. Prefer previous error.
13       }
14     }
15   }

 

 

这里第3行代码我们可以看到其实时新建了一个  XMLConfigBuilder这个对象,同样进入这个构造方法看下做了哪些事。

1 private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
2     super(new Configuration());
3     ErrorContext.instance().resource("SQL Mapper Configuration");
4     this.configuration.setVariables(props);
5     this.parsed = false;
6     this.environment = environment;
7     this.parser = parser;
8   }

 

从源码跟踪下来实际调用的是这个方法,从第2行代码中可以看到先是创建了一个 Configuration对象,这个对象非常重要,它包含的其实就是Mybatis配置文件中用户自定义的所有属性。我们来看一下这个类长什么样子

  1 public class Configuration {
  2 
  3   protected Environment environment;
  4 
  5   protected boolean safeRowBoundsEnabled;
  6   protected boolean safeResultHandlerEnabled = true;
  7   protected boolean mapUnderscoreToCamelCase;
  8   protected boolean aggressiveLazyLoading;
  9   protected boolean multipleResultSetsEnabled = true;
 10   protected boolean useGeneratedKeys;
 11   protected boolean useColumnLabel = true;
 12   protected boolean cacheEnabled = true;
 13   protected boolean callSettersOnNulls;
 14   protected boolean useActualParamName = true;
 15   protected boolean returnInstanceForEmptyRow;
 16 
 17   protected String logPrefix;
 18   protected Class<? extends Log> logImpl;
 19   protected Class<? extends VFS> vfsImpl;
 20   protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
 21   protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
 22   protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
 23   protected Integer defaultStatementTimeout;
 24   protected Integer defaultFetchSize;
 25   protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
 26   protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
 27   protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
 28 
 29   protected Properties variables = new Properties();
 30   protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
 31   protected ObjectFactory objectFactory = new DefaultObjectFactory();
 32   protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
 33 
 34   protected boolean lazyLoadingEnabled = false;
 35   protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
 36 
 37   protected String databaseId;
 38   /**
 39    * Configuration factory class.
 40    * Used to create Configuration for loading deserialized unread properties.
 41    *
 42    * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
 43    */
 44   protected Class<?> configurationFactory;
 45 
 46   protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
 47   protected final InterceptorChain interceptorChain = new InterceptorChain();
 48   protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
 49   protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
 50   protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
 51 
 52   protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
 53       .conflictMessageProducer((savedValue, targetValue) ->
 54           ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
 55   protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
 56   protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
 57   protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
 58   protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
 59 
 60   protected final Set<String> loadedResources = new HashSet<>();
 61   protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
 62 
 63   protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
 64   protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
 65   protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
 66   protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
 67 
 68   /*
 69    * A map holds cache-ref relationship. The key is the namespace that
 70    * references a cache bound to another namespace and the value is the
 71    * namespace which the actual cache is bound to.
 72    */
 73   protected final Map<String, String> cacheRefMap = new HashMap<>();
 74 
 75   public Configuration(Environment environment) {
 76     this();
 77     this.environment = environment;
 78   }
 79 
 80   public Configuration() {
 81     typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
 82     typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
 83 
 84     typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
 85     typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
 86     typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
 87 
 88     typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
 89     typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
 90     typeAliasRegistry.registerAlias("LRU", LruCache.class);
 91     typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
 92     typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
 93 
 94     typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
 95 
 96     typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
 97     typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
 98 
 99     typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
100     typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
101     typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
102     typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
103     typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
104     typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
105     typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
106 
107     typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
108     typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
109 
110     languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
111     languageRegistry.register(RawLanguageDriver.class);
112   }
113 }
View Code

这里我截取了Configuration对象的一些属性和构造方法,来看一下其中比较关键的一些属性.

 

首先第3行的environment属性,对应的是Mybatis配置文件中  <environment></environment>的值,一般我们会根据不同的环境配置开发、测试、生产三个不同环境的配置文件,这个属性用来记录当前使用的是哪一个环境的配置文件。

然后第20行,可以看到localCache(本地缓存,即所谓的一级缓存),其默认的作用域被设置为SESSION级别。

第25行,可以看到有个ExecutorType对象,且其默认设置为了SIMPLE(对应的是SimpleExecutor.java 这个类)。这个对象其实就是执行数据库增删改查操作的入口,之后我们会分析它,这里先留个印象。

第46行,可以看到有个MapperRegistry对象,对应的是Mybatis配置文件中的 <mappers></mappers>下声明的所有mapper,存放的是所有已经加载过的Mapper资源文件。它会在创建SqlSessionFactory时,就把所有用户自定义的XML格式的Mapper文件及对应的元素放到自身的一个Map类型的属性 knownMappers 中。从名字也很好理解,就是这个Map中所有存在的Mapper我都认识了,后面就可以直接拿来用了;而这个Map中没有的Mapper我全部都不认识,那么如果之后有碰到这种Mapper就会抛出异常(后文可以看到这段逻辑的相关代码)。

第48行,可以看到有个TypeHandlerRegistry对象,这个对象会把所有Mybatis原生支持的数据类型全部加载一下(比如LONG,INT,STRING等等),如果用户有自定义typeHandler(如需要将JDK1.8 的 LocalDateTime 类型转换为数据库的 TimeStamp 类型,就需要手动引入 TypeHandler- JSR133这个引用),那么同样也会存储到TypeHandlerRegistry对象中。

第49行,可以看到有个TypeAliasRegistry对象,这个对象主要存储了Mybatis原生的别名以及用户自定义的别名,可以看到在80~112行的Configuration构造方法中,就设置了一些Mybatis本身需要的别名。

第52行,有个MappedStatement对象,这个对象存放的是xml格式的Mapper文件中具体的数据库增删改查语句,对应的是 <select></select> 、<insert></insert>、<update></update>、<delete></delete>中id属性值和具体的sql语句。之后执行sql语句时会从这个对象中来查找应该执行哪条sql语句。

第56行,有个resultMaps对象,它是一个Map格式的对象,存储了具体哪条sql语句返回的是哪个resultMap。

第57行,有个parameterMaps对象,它也是一个Map格式的对象,存储了执行具体哪条sql语句时传入参数对应的类型。

 

OK我们回到上面的  XMLConfigBuilder对象,创建完Configuration对象后,设置一些properties属性,然后将 parsed 变量设置为false(表示当前的XMLConfigBuilder还没有解析过Mybatis配置文件,一个XMLConfigBuilder对象只能解析一次文件,防止多次解析撑爆内存),再设置一下环境以及xPath XML解析器 parser对象,然后该方法就结束了。

 

我们回到最上方的build方法中,此时第3行的代码执行完毕,即Configuration对象已经预创建好,环境也已经准备好,xml解析器也准备好,下面就执行 第4行的代码,这里首先会执行  parser.parse() 这个方法,我们一下这个方法做了些什么事

1 public Configuration parse() {
2     if (parsed) {
3       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
4     }
5     parsed = true;
6     parseConfiguration(parser.evalNode("/configuration"));
7     return configuration;
8   }

 

可以看到这里首先判断parsed这个布尔类型变量的值,如果已经解析过配置文件则直接抛出异常,如果没有解析过就将该变量设置为true,然后执行第6行 parseConfiguration() 方法。这个方法就是使用xml解析器去解析配置文件中  <configuration></configuration>标签下的所有元素,然后设置到之前预创建的 Configuration对应的属性中。下面代码应该已经可以很清晰的展现这个过程了:

 1 private void parseConfiguration(XNode root) {
 2     try {
 3       //issue #117 read properties first
 4       propertiesElement(root.evalNode("properties"));
 5       Properties settings = settingsAsProperties(root.evalNode("settings"));
 6       loadCustomVfs(settings);
 7       loadCustomLogImpl(settings);
 8       typeAliasesElement(root.evalNode("typeAliases"));
 9       pluginElement(root.evalNode("plugins"));
10       objectFactoryElement(root.evalNode("objectFactory"));
11       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
12       reflectorFactoryElement(root.evalNode("reflectorFactory"));
13       settingsElement(settings);
14       // read it after objectFactory and objectWrapperFactory issue #631
15       environmentsElement(root.evalNode("environments"));
16       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
17       typeHandlerElement(root.evalNode("typeHandlers"));
18       mapperElement(root.evalNode("mappers"));
19     } catch (Exception e) {
20       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
21     }
22   }

 

我们这里以第18行的 mapperElement为例,之前已经说过这里会解析所有<mappers></mappers>下配置的mapper文件,并放入MapperRegistry对象的knownMappers属性中,来看下是不是这样

 1   private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) {
 5           String mapperPackage = child.getStringAttribute("name");
 6           configuration.addMappers(mapperPackage);
 7         } else {
 8           String resource = child.getStringAttribute("resource");
 9           String url = child.getStringAttribute("url");
10           String mapperClass = child.getStringAttribute("class");
11           if (resource != null && url == null && mapperClass == null) {
12             ErrorContext.instance().resource(resource);
13             InputStream inputStream = Resources.getResourceAsStream(resource);
14             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
15             mapperParser.parse();
16           } else if (resource == null && url != null && mapperClass == null) {
17             ErrorContext.instance().resource(url);
18             InputStream inputStream = Resources.getUrlAsStream(url);
19             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
20             mapperParser.parse();
21           } else if (resource == null && url == null && mapperClass != null) {
22             Class<?> mapperInterface = Resources.classForName(mapperClass);
23             configuration.addMapper(mapperInterface);
24           } else {
25             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
26           }
27         }
28       }
29     }
30   }

 

这里有很多的if-else代码块,其实就是在判断声明<mapper></mapper>时具体使用的是哪种方式 (package声明、resource声明、url声明和class声明),但不管使用哪种声明方式最后都会调用 configuration.addMapper()这个方法(可能有同学会疑惑第11行和第16行的else-if语句块最后调用的是  mapperParser.parse() 这个方法,其实这个方法最终调用的就是  configuration.addMapper() 方法,不相信的同学可以自己下载源码跟踪到最里面的  bindMapperFoeNamespace() 方法的第441行看下就知道了),这个方法实现如下

 1   public <T> void addMapper(Class<T> type) {
 2     if (type.isInterface()) {
 3       if (hasMapper(type)) {
 4         throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
 5       }
 6       boolean loadCompleted = false;
 7       try {
 8         knownMappers.put(type, new MapperProxyFactory<>(type));
 9         // It's important that the type is added before the parser is run
10         // otherwise the binding may automatically be attempted by the
11         // mapper parser. If the type is already known, it won't try.
12         MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
13         parser.parse();
14         loadCompleted = true;
15       } finally {
16         if (!loadCompleted) {
17           knownMappers.remove(type);
18         }
19       }
20     }
21   }

从第8行代码可以印证上面所述的逻辑,并且大家注意下这里往 knownMappers中存放的values是一个 MapProxy对象,为什么存放的是这个MapProxy对象在下篇文章会说明。

 

到这里,所有的前置工作已经全部做好,我们回到 SqlSessionFactoryBuilder这个类的build方法第4行,接着就会执行  build(Configuration configuration) 这个方法了,我们看下实现:

1  public SqlSessionFactory build(Configuration config) {
2     return new DefaultSqlSessionFactory(config);
3   }

 

可以看到这里将设置好所有参数的Configuration对象作为参数,调用了 DefaultSqlSessionFactory这个类的构造方法后直接返回这个对象作为SqlSessionFactory的实现类,至此 SqlSessionFactory对象就已经创建完成,接着就是走最开始的第7行代码创建SqlSession了。

 

SqlSession,包含了预加载完成的Configuration对象,以及一系列数据库操作方法

  现在我们已经得到了一个SqlSessionFactory的实现类(也就是DefaultSqlSessionFactory这个类),最开始第7行代码使用了  sqlSessionFactory.openSession() 这个方法来创建一个SqlSession,我们看下具体的实现

1  @Override
2   public SqlSession openSession() {
3     return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
4   }
 1  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 2     Transaction tx = null;
 3     try {
 4       final Environment environment = configuration.getEnvironment();
 5       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
 6       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
 7       final Executor executor = configuration.newExecutor(tx, execType);
 8       return new DefaultSqlSession(configuration, executor, autoCommit);
 9     } catch (Exception e) {
10       closeTransaction(tx); // may have fetched a connection so lets call close()
11       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
12     } finally {
13       ErrorContext.instance().reset();
14     }
15   }

 

 实际调用的是 openSessionFromDataSource()这个方法,设置了环境、事务以及使用的执行器,然后返回的是 DefaultSqlSession这个对象,即调用如下构造方法将这些参数设置到对应属性中:

1  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
2     this.configuration = configuration;
3     this.executor = executor;
4     this.dirty = false;
5     this.autoCommit = autoCommit;
6   }

 

好了,到这里我们已经成功获取到了SqlSession对象,MyBatis整个流程的前半部分已经结束。下篇文章会讲解后半部分的流程,主要是通过DefaultSqlSession获取对应的Mapper代理对象,然后在执行代理后的方法时,通过executor执行器执行对应sql语句,最后将结果通过ResultHandler包装返回的流程。

 

posted @ 2019-04-14 22:22  ohbfskfhl  阅读(148)  评论(0)    收藏  举报