[java学习笔记]MyBatis源码学习笔记(二)

Mapper映射器的配置

就是 标签了,用来进行 sql 文件映射。也就是说我们需要告诉 MyBatis 到哪里去找到这些语句。 Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
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");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            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.");
          }
        }
      }
    }
  }

显然我们从源码里面发现了,这四种映射文件获取的方式显然是有优先级的,package方式最为优先

Element : mappers
Content Model : (mapper*, package*)

根据dtd约束: mapper子标签必须在package标签前面。实际应用中,package标签使用的比较少,这里就不贴源码对package进行分析了(需要注意的是,如果两个子标签同时存在,前面解析完mapper标签后,存在相同的接口名,会抛出异常)

  • 第一个 if 表示 resource 值不为 null,且url 值和 mapperClass 值都为null。

  • 第二个else if 表示 url 值不为 null,且 resource 值和 mapperClass 值都为null。

  • 第三个 else if 表示 mapperClass 值不为 null,且 resource 值和 url 值都为null。

  • 第四个 else 表示如果三个都为null或者都不为null,或者有两个不为null,都会抛出异常。


数据源的配置

实质上,Mybatis-config.xml这个主配置文件就代表我们如何连接数据库源,那么哪个负责读取xml就是哪个源码负责连接数据库源

显然这个方法是读取environments标签用的

private void environmentsElement(XNode context) throws Exception {
        //如果<environments>标签不为null
        if (context != null) {
            //如果 environment 值为 null
            if (environment == null) {
                //获取<environments default="属性值">中的default属性值
                environment = context.getStringAttribute("default");
            }
            //遍历<environments />标签中的子标签<environment />
            for (XNode child : context.getChildren()) {
                //获取<environment id="属性值">中的id属性值
                String id = child.getStringAttribute("id");
                //遍历所有<environment>的时候一次判断相应的id是否是default设置的值
                if (isSpecifiedEnvironment(id)) {
                    //获取配置的事务管理器
                    TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                    //获取配置的数据源信息
                    DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                    DataSource dataSource = dsFactory.getDataSource();
                    Environment.Builder environmentBuilder = new Environment.Builder(id)
                            .transactionFactory(txFactory)
                            .dataSource(dataSource);
                    configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }
    }

    private boolean isSpecifiedEnvironment(String id) {
        if (environment == null) {
            throw new BuilderException("No environment specified.");
        } else if (id == null) {
            throw new BuilderException("Environment requires an id attribute.");
        } else if (environment.equals(id)) {
            return true;
        }
        return false;
    }
  1. 第 3 行代码:if (context != null) 也就是说我们可以不在 mybatis-configuration.xml 文件中配置标签,这是为了和spring整合时,在spring容器中进行配置。

  2. 第 5 行——第 8 行代码:获取中的default属性值,注意第 5 行 首先判断 environment == null 。因为我们可以配置多个环境,也就是连接多个数据库。

Mybatis内部的数据源包含在这些jar包里

public interface DataSourceFactory {

  void setProperties(Properties props);

  DataSource getDataSource();

}

这就是数据源工厂的接口,几种不同的数据源工厂都实现了它

1 public class JndiDataSourceFactory implements DataSourceFactory 
2 public class UnpooledDataSourceFactory implements DataSourceFactory 
3 public class PooledDataSourceFactory extends UnpooledDataSourceFactory 

而在mybatis源码内也是需要通过configxml文件来生产相应的数据源工厂的其实就是我们配置的type属性

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.");
  }

那么我们看看unpooled的数据源是怎么获取连接的

public Connection getConnection() throws SQLException {
        return doGetConnection(username, password);
    }

    public Connection getConnection(String username, String password) throws SQLException {
        return doGetConnection(username, password);
    }
    private Connection doGetConnection(String username, String password) throws SQLException {
        //将用户名、密码、驱动都封装到Properties文件中
        Properties props = new Properties();
        if (driverProperties != null) {
            props.putAll(driverProperties);
        }
        if (username != null) {
            props.setProperty("user", username);
        }
        if (password != null) {
            props.setProperty("password", password);
        }
        return doGetConnection(props);
    }

    /**
     * 获取数据库连接
     */
    private Connection doGetConnection(Properties properties) throws SQLException {
        //1、初始化驱动
        initializeDriver();
        //2、从DriverManager中获取连接,获取新的Connection对象
        Connection connection = DriverManager.getConnection(url, properties);
        //3、配置connection属性
        configureConnection(connection);
        return connection;
    }

显然其实实际上还是和我们用的时候一样,只是分了多个重载的方法去传参而已,本质还是一样

  1. 加载数据库驱动
  2. 通过url 用户密码获取connection
  3. 配置connection设置是否自动提交以及隔离级别
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章