[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設置是否自動提交以及隔離級別
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章