1. 程式人生 > >mybatis加載配置文件詳解

mybatis加載配置文件詳解

creat insert mapper mman sql 源碼 none includes lds

spring整合Mybatis後,SqlSessionFactory的創建由spring進行了代理,以下是SqlSessionFactory創建的流程

SqlSessionFactoryBean:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class
); private Resource configLocation; private Resource[] mapperLocations; private DataSource dataSource; //spring通過SqlSessionFactoryBean類代理創建SqlSessionFactoryBuilder private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory;
//創建sqlSessionFactory的方法體 protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null
, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property ‘configLocation‘ not specified, using default MyBatis Configuration"); } configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } ... return this.sqlSessionFactoryBuilder.build(configuration); }ss @Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property ‘dataSource‘ is required"); notNull(sqlSessionFactoryBuilder, "Property ‘sqlSessionFactoryBuilder‘ is required"); //調用方法創建sqlSessionFactory this.sqlSessionFactory = buildSqlSessionFactory(); } @Override public void onApplicationEvent(ApplicationEvent event) { if (failFast && event instanceof ContextRefreshedEvent) { // fail-fast -> check all statements are completed this.sqlSessionFactory.getConfiguration().getMappedStatementNames(); } } }

核心流程

  1.由spring創建一個SqlSessionFactoryBuilder對象

  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

  2.在後期調用buildSqlSessionFactory方法時會創建XMLConfigBuilder對象和configuration對象
   xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
   configuration = xmlConfigBuilder.getConfiguration();
   3.最後創建SqlSessionFactory對象
   this.sqlSessionFactoryBuilder.build(configuration);


XMLConfigBuilder中主要關註的代碼:
//基於構造器創建對象
private
XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
//對配置文件的解析
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
//配置文件中不同Node的處理方式
private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //issue #117 read properties first typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }

主要以configuration節點和我們業務模塊的配置文件進行userDao.xml和logDao.xml進行講述

mybatis-config.xml
這就是上述要加載的configuration節點;之後根據configuration節點進行不同的Node解析處理
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 全局參數 -->
    <settings>
        <!-- 使全局的映射器啟用或禁用緩存。 -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 全局啟用或禁用延遲加載。當禁用時,所有關聯對象都會即時加載。 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 當啟用時,有延遲加載屬性的對象在被調用時將會完全加載任意屬性。否則,每種屬性將會按需要加載。 -->
        <setting name="aggressiveLazyLoading" value="true"/>
        <!-- 是否允許單條sql 返回多個數據集  (取決於驅動的兼容性) default:true -->
        <setting name="multipleResultSetsEnabled" value="true"/>
        <!-- 是否可以使用列的別名 (取決於驅動的兼容性) default:true -->
        <setting name="useColumnLabel" value="true"/>
        <!-- 允許JDBC 生成主鍵。需要驅動器支持。如果設為了true,這個設置將強制使用被生成的主鍵,有一些驅動器不兼容不過仍然可以執行。  default:false  -->
        <setting name="useGeneratedKeys" value="false"/>
        <!-- 指定 MyBatis 如何自動映射 數據基表的列 NONE:不隱射 PARTIAL:部分  FULL:全部  -->  
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <!-- 這是默認的執行類型  (SIMPLE: 簡單; REUSE: 執行器可能重復使用prepared statements語句;BATCH: 執行器可以重復執行語句和批量更新)  -->
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <!-- 使用駝峰命名法轉換字段。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 設置本地緩存範圍 session:就會有數據的共享  statement:語句範圍 (這樣就不會有數據的共享 ) defalut:session -->
        <setting name="localCacheScope" value="SESSION"/>
        <!-- 設置但JDBC類型為空時,某些驅動程序 要指定值,default:OTHER,插入空值時不需要指定類型 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
    
    <!-- 類型別名-->
    <typeAliases>
        <typeAlias alias="Page" type="cn.common.persistence.Page" /> 
    </typeAliases>
    
    <!-- 插件配置 -->
    <plugins>
        <!-- 攔截Executor -->
        <plugin interceptor="cn.common.persistence.interceptor.PaginationInterceptor" />
    </plugins>
</configuration>

對應我們的業務模塊userDao.xml和logDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.module.sys.dao.LogDao">
    <select id="xx" resultType="Log">
        SELECT 
           xx
        FROM xx
        JOIN xx ON xx
        JOIN xx ON xx
        WHERE xx BETWEEN #{xx} AND #{xx}
        <if test="xx">
            AND xx LIKE 
                    <if test="xx">‘%‘||#{xx}||‘%‘</if>
                    <if test="xx">‘%‘+#{xx}+‘%‘</if>
        </if>
        ORDER BY xx
    </select>
    
    <insert id="xx">
        INSERT INTO xx(
           xx
        ) VALUES (
           xx
        )
    </insert>
    
</mapper>

業務xxDao.xml解析如下:

XMLConfigBuilder

  private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); 
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); 
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
    //解析業務配置文件 mapperElement(root.evalNode(
"mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }

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

XMLMapperBuilder

下面是主要關註的方法(以select|insert|update|delete解析講解為主)

  
//負責對業務dao.xml元素進行解析
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace.equals("")) { throw new BuilderException("Mapper‘s namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql"));
   //這是我們sql經常寫的sql標簽 buildStatementFromContext(context.evalNodes(
"select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); }

//select|insert|update|delete最終會在這裏解析
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) {
    //由該對象負責為select|insert|update|delete進行解析
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try {
     //這是解析sql的核心方法 statementParser.parseStatementNode(); }
catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }


//sql解析
public
void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return; Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)

//讀者註意: 這就是很多面試經常會被問到的 #和$的區別 其實源碼本質是通過#和$的,對應的生產動態sql解析對象和靜態sql解析對象 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); }   
    
  //最終的解析結果會被這樣存放=>最終會調用configuration.addMappedStatement(statement); builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }

最終存儲的對象Configuration
 //sql被解析後會由一個map對象進行存儲
//key=> cn.module.sys.dao.logDao.get(MappedStatement的ID屬性)
//value=> MappedStatement對象
public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); }




mybatis加載配置文件詳解