博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis 源码解析(一):初始化和动态代理
阅读量:6639 次
发布时间:2019-06-25

本文共 16486 字,大约阅读时间需要 54 分钟。

简介

MyBatis 是 Java 开发中非常流行的 ORM 框架,其封装了 JDBC 并且解决了 Java 对象与输入参数和结果集的映射,同时又能够让用户方便地手写 SQL 语句。MyBatis 的行为类似于以下几行代码:

Class.forName("com.mysql.jdbc.Driver");Connection conn = DriverManager.getConnection(url, usr, password);PraparedStatement st = conn.prepareStatement(sql);st.setInt(0, 1);st.execute();ResultSet rs = st.getResultSet();while (rs.next()) {    String result = rs.getString(colname);}

上面是 JDBC 的使用流程,MyBatis 其实就是对上面的代码进行分解包装。本文将对 MyBatis 的代码进行分析,探究其中的逻辑。

基本用法

首先从 MyBatis 的基本用法开始,下面是 MyBatis 官网的入门示例:

String resource = "org/mybatis/example/mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

其中 mabatis-config.xml 是 MyBatis 的核心配置文件,其中包括数据源、事务管理器、别名以及 SQL 对应的 Mapper 文件等,如下所示:

有了 SqlSessionFactory 后就可以创建 SqlSession 来调用 select 以及 update 等方法请求数据了:

try {  BlogMapper mapper = session.getMapper(BlogMapper.class);  Blog blog = mapper.selectBlog(101);} finally {  session.close();}

配置文件解析

我们按照上面的代码流程开始分析源码,首先是配置文件的解析:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) SqlSessionFactoryBuilder 显然是为了构建 SqlSessionFactory,而且是从配置文件的输入流构建,代码如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {    try {      // 创建 XMLConfigBuilder      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);      // parse.parse() 进行解析      return build(parser.parse());    } catch (Exception e) {      throw ExceptionFactory.wrapException("Error building SqlSession.", e);    } finally {      ErrorContext.instance().reset();      try {        inputStream.close();      } catch (IOException e) {        // Intentionally ignore. Prefer previous error.      }    }  }

首先是创建了一个 XMLConfigBuilder 对象,它是用来解析 Config 文件的。XMLConfigBuilder 继承自 BaseBuilderBaseBuilder 中有个 Configuration 类型的变量,这个类需要重点关注,Config 文件中解析出来的所有信息都保存在这个变量中。

创建了 XMLConfigBuilder 后调用了其 parse 方法:

public Configuration parse() {    if (parsed) {      throw new BuilderException("Each XMLConfigBuilder can only be used once.");    }    parsed = true;    // 在这个函数中解析    parseConfiguration(parser.evalNode("/configuration"));    return configuration;  }

这里主要逻辑在 parseConfiguration 中:

private void parseConfiguration(XNode root) {    try {      // 解析 properties      propertiesElement(root.evalNode("properties")); //issue #117 read properties first      // 解析 type alias      typeAliasesElement(root.evalNode("typeAliases"));      pluginElement(root.evalNode("plugins"));      objectFactoryElement(root.evalNode("objectFactory"));      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));            // 解析 setting      settingsElement(root.evalNode("settings"));      // 解析 environment       environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631      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);    }  }

这里解析了 config 文件中所有的标签,包括 propertiessettings 以及 mappers 等,下面挑几个看一下。

settings

settings 是对 MyBatis 的一些配置项,包括缓存的开启以及是否使用驼峰转换(mapUnderscoreToCamelCase)等,代码如下:

private void settingsElement(XNode context) throws Exception {   if (context != null) {   // 将配置项保存到 Properties 中     Properties props = context.getChildrenAsProperties();     // Check that all settings are known to the configuration class     MetaClass metaConfig = MetaClass.forClass(Configuration.class);     for (Object key : props.keySet()) {       if (!metaConfig.hasSetter(String.valueOf(key))) {         throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");       }     }     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));     // 默认开启缓存     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));           configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));     configuration.setLogPrefix(props.getProperty("logPrefix"));     configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));   } }

可以看出,settings 的子节点保存在 Properties 中,然后校验是否有不合法的子节点,最后提取出其中的属性保存到 Configuration 中,上面提到这个类专门用于保存 Config 文件解析出的信息。

从上面也可以看到 MyBatis 的一些默认属性,例如一级缓存如果没有配置,那么默认是开启的。

environments

environments 包含了数据源(dataSource) 和事务管理器(transactionManager) 的配置,代码如下:

private void environmentsElement(XNode context) throws Exception {    if (context != null) {      if (environment == null) {        environment = context.getStringAttribute("default");      }      for (XNode child : context.getChildren()) {        String id = child.getStringAttribute("id");        if (isSpecifiedEnvironment(id)) {          // 解析 transactionManager          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));          // 解析 dataSource          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));          DataSource dataSource = dsFactory.getDataSource();          Environment.Builder environmentBuilder = new Environment.Builder(id)              .transactionFactory(txFactory)              .dataSource(dataSource);          // 设置 environment 到 configuration          configuration.setEnvironment(environmentBuilder.build());        }      }    }  }private TransactionFactory transactionManagerElement(XNode context) throws Exception {    if (context != null) {      String type = context.getStringAttribute("type");      Properties props = context.getChildrenAsProperties();      // 通过反射实例化      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();      factory.setProperties(props);      return factory;    }    throw new BuilderException("Environment declaration requires a TransactionFactory.");  }  private DataSourceFactory dataSourceElement(XNode context) throws Exception {    if (context != null) {      String type = context.getStringAttribute("type");      Properties props = context.getChildrenAsProperties();      // 通过反射实例化      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();      factory.setProperties(props);      return factory;    }    throw new BuilderException("Environment declaration requires a DataSourceFactory.");  }

其中主要是两部分,第一部分解析 transactionManager,第二部分解析 dataSource。从 transactionManagerElementdataSourceElement 中可以看出通过对应 Class 文件的 newInstance 实例化出对应的工厂对象。最终解析出的 transactionManagerdataSource 依然是设置到 Configuration 中。

mappers

mappers 对应了具体的 SQL Mapper 文件,也是我们要分析的重点。

mappers 标签可以多种子标签,上面的示例中是 mapper 配合 resource

我们下面看一下此种形式在源码中的解析:

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 {          String resource = child.getStringAttribute("resource");          String url = child.getStringAttribute("url");          String mapperClass = child.getStringAttribute("class");          // 这个分支解析 resource 形式的标签          if (resource != null && url == null && mapperClass == null) {            ErrorContext.instance().resource(resource);            InputStream inputStream = Resources.getResourceAsStream(resource);            // 创建 XMLMapperBuilder             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."); } } } } }

resource 标签解析的对应分支是 (resource != null && url == null && mapperClass == null),其中创建了一个 XMLMapperBuilder 对象然后调用 parse 方法进行解析:

public void parse() {    if (!configuration.isResourceLoaded(resource)) {      // 解析 mapper 下面的标签,包括 namespace、cache、parameterMap、resultMap 等      configurationElement(parser.evalNode("/mapper"));      configuration.addLoadedResource(resource);      bindMapperForNamespace();    }    parsePendingResultMaps();    parsePendingChacheRefs();    parsePendingStatements();  } private void configurationElement(XNode context) {    try {      // namespace 对应 Mapper 对应接口的全名(包名 + 类名)      String namespace = context.getStringAttribute("namespace");      builderAssistant.setCurrentNamespace(namespace);      cacheRefElement(context.evalNode("cache-ref"));      cacheElement(context.evalNode("cache"));      // 解析生成 ParameterMap      parameterMapElement(context.evalNodes("/mapper/parameterMap"));      // 解析生成 ResultMap      resultMapElements(context.evalNodes("/mapper/resultMap"));      sqlElement(context.evalNodes("/mapper/sql"));      // 每一个 sql 语句生成一个 MappedStatement      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));    } catch (Exception e) {      throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);    }  }

configurationElement 用于解析具体的子标签,如 namespacecacheparameterMapresultMap 以及 select|insert|update|delete 等。

namespace 对应了 Mapper 接口类的包名 + 类名,通过 namespace 可以唯一定位一个 Class 文件,解析的 namespace 保存在 builderAssistant 中,后面会用到。

parameterMapresultMap 解析会生成 ParameterMapResultMap 对象。每个 SQL 语句解析会生成 MappedStatement

在上面的 parse 方法中,解析完标签后调用了 bindMapperForNamespace,这个实现了加载 namespace 对应的 Class,并且为每个 Class 创建了代理类工厂对象(MapperProxyFactory)。

MapperProxyFactory

MapperProxyFactory 用于为 Mapper 接口类创建代理对象,代理对象指的是

BlogMapper mapper = session.getMapper(BlogMapper.class) 生成的对象。

下面从 bindMapperForNamespace 开始:

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) { 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); // 添加 mapper 和 MapperProxyFactory configuration.addMapper(boundType); } } } }

其中先从 builderAssistant 取出 namespace,然后加载对应的 Class(boundType = Resources.classForName(namespace))。最后调用 configuration.addMapper(boundType) 添加到 configuration 中。 configuration.addMapper(boundType) 很关键,看代码:

public 
void addMapper(Class
type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 添加到 Map
, MapperProxyFactory
> 中 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); } } } }

关键的一行是 knownMappers.put(type, new MapperProxyFactory<T>(type)),其中 knownMappers 的类型是 Map<Class<?>, MapperProxyFactory<?>>,即 key 是 Class,value 是 MapperProxyFactory。这里的 MapperProxyFactory 即是动态代理对象的工厂,下面是其 newInstance 方法的代码:

protected T newInstance(MapperProxy
mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy
mapperProxy = new MapperProxy
(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }

从中可以看出,这里用的是 Java 的动态代理,Proxy.newProxyInstance 方法生成指定接口的代理对象,这个方法的第三个参数是用于方法拦截的对象,这里是 MapperProxy 的实例。

由此可以知道,具体的执行 SQL 语句的操作是由这个类拦截并且执行的,看看这个类的 invoke 方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    if (Object.class.equals(method.getDeclaringClass())) {      return method.invoke(this, args);    }    final MapperMethod mapperMethod = cachedMapperMethod(method);    return mapperMethod.execute(sqlSession, args);  }

如果是 Object 类中声明的方法,则直接执行,否则调用 MapperMethodexecute,其中便是 JDBC 相关的逻辑了。限于篇幅,具体内容留到下一篇文章再看。

在分析完配置文件的解析后,再回到 XMLConfigBuilder 中:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {    try {      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);      // 构建 SqlSessionFactory      return build(parser.parse());    } catch (Exception e) {      throw ExceptionFactory.wrapException("Error building SqlSession.", e);    } finally {      ErrorContext.instance().reset();      try {        inputStream.close();      } catch (IOException e) {        // Intentionally ignore. Prefer previous error.      }    }  }

parser.parse() 执行完后,生成一个 Configuration 对象,最后调用 build 构建 SqlSessionFactory,代码如下:

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

可以看到,最终创建的是 DefaultSqlSessionFactory,这个类内部持有 Configuration,并且提供了多个重载的 openSession 方法用于创建 SqlSession

到这里,初始化部分就结束了。

总结

MyBatis 的初始化流程主要是解析配置文件,将相关信息保存在 Configuration 中,同时对每个 namespace 代表的 Class 生成代理对象工厂。最后,利用 Configuration 生成了一个 DefaultSqlSessionFactory,通过这个对象可以创建 SqlSession 执行 SQL 请求,相关内容将在下一篇()分析。

如果我的文章对您有帮助,不妨点个赞支持一下(^_^)

转载地址:http://kxovo.baihongyu.com/

你可能感兴趣的文章
map的用法
查看>>
安卓 WebView加载本地图片时居中显示
查看>>
UITableView 优化总结
查看>>
信号量同步线程
查看>>
NUC1333 Knight Moves【DFS】
查看>>
B00014 C++实现的AC自动机
查看>>
687C: The values you can make
查看>>
HDU2502 月之数(解法三)
查看>>
设计模式-命令模式
查看>>
C#的几个基本概念
查看>>
JavaScript对象的几种创建方式
查看>>
Linux进程间通信——使用信号量
查看>>
xpath提取多个html标签text
查看>>
android中webservce获取soapObject数据的解析问题
查看>>
[120_移动开发Android]004_android开发之单元测试
查看>>
Java加密算法(二)——对称加密算法DES&AES
查看>>
最少换乘
查看>>
centos 7 安装MySql
查看>>
LeetCode: Adding two numbers (by list)
查看>>
Hibernate查询 内连接和外连接区别
查看>>