mybatis原理解析


本文是結合spring-mybatis整合進行的分析

1、先看看依賴的jar包:

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.8</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.2</version>
        </dependency>        

2、mybatis主要兩個關鍵對象時SqlSessionFactory和SqlSession,接下來主要結合源碼對這兩個對象流程進行分析:

在分析這兩個對象之前先來看看XML配置情況:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
       default-autowire="byName" default-lazy-init="true">

    <!-- DataSource數據 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="name" value="souchecar"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="20"/>
        <property name="minIdle" value="2"/>
        <property name="initialSize" value="2"/>
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="defaultAutoCommit" value="true"/>
        <property name="removeAbandoned" value="true"/>
        <property name="removeAbandonedTimeout" value="60"/>
        <property name="logAbandoned" value="true"/>
        <property name="filters" value="stat"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- <property name="configLocation" value="classpath:Configuration.xml" /> -->
        <property name="mapperLocations">
            <list>
                <value>classpath*:sqlmap/**/*.xml</value>
            </list>
        </property>
        <property name="dataSource" ref="dataSource"/>

    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

</beans>

在配置中主要包含了數據源DruidDataSource,SqlSessionFactoryBean(包含了mybatis的映射文件),事物DataSourceTransactionManager,以及SqlSessionTemplate信息的配置,這里就不對具體的配置作用做過多的介紹

3、SqlSessionFactory

  1)SqlSessionFactory對象是mybatis中的核心對象之一,主要是通過SqlSessionFactory來創建SqlSession對象,一般在一個數據庫中,最好采用單例的模式,將SqlSessionFactory創建成一個單例的對象;

  2)結合以上的配置和源碼來分析SqlSessionFactory創建的一個過程:在配置文件中,主要是通過SqlSessionFactoryBean來管理SqlSessionFactory創建過程,因為該類實現了InitializingBean接口,所以在spring初始化改bean的時候,會先執行InitializingBean接口中的afterPropertiesSet()方法,在該方法中會去調用buildSqlSessionFactory()方法,該方法是用來創建Configuration對象,將配置文件中配置項信息加載到該對象中,然后在根據Configuration創建SqlSessionFactory,如下:

  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }
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);
    }

    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (logger.isDebugEnabled()) {
          logger.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }

    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (logger.isDebugEnabled()) {
          logger.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }

    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (logger.isDebugEnabled()) {
          logger.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }

    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (logger.isDebugEnabled()) {
          logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }

    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (logger.isDebugEnabled()) {
          logger.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();

        if (logger.isDebugEnabled()) {
          logger.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);

    if (this.databaseIdProvider != null) {
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
     // 1.獲取配置中映射mapper標簽中的信息,存儲到MappedStatement對象中,並sql存儲到SqlSource對象中 
try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (logger.isDebugEnabled()) { logger.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (logger.isDebugEnabled()) { logger.debug("Property 'mapperLocations' was not specified or no matching resources found"); } }   // 2.創建SqlSessionFactory對象
return this.sqlSessionFactoryBuilder.build(configuration); }

  在該方法中先來分析一下xmlMapperBuilder.parse()方法,這個方法主要是將映射文件中的信息存儲到相應的對象中:

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper")); //我們關注的是這個方法
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

  在方法中configurationElement()主要是用來解析mapper標簽中的信息,我們主要關注的是這個方法,下來的方法是用來解析映射文件中其他屬性信息的

 1   private void configurationElement(XNode context) {
 2     try {
 3       String namespace = context.getStringAttribute("namespace");
 4       if (namespace.equals("")) {
 5           throw new BuilderException("Mapper's namespace cannot be empty");
 6       }
 7       builderAssistant.setCurrentNamespace(namespace);
 8       cacheRefElement(context.evalNode("cache-ref"));
 9       cacheElement(context.evalNode("cache"));
10       parameterMapElement(context.evalNodes("/mapper/parameterMap"));
11       resultMapElements(context.evalNodes("/mapper/resultMap"));
12       sqlElement(context.evalNodes("/mapper/sql"));
13       buildStatementFromContext(context.evalNodes("select|insert|update|delete")); //我們關注的是這個方法
14     } catch (Exception e) {
15       throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
16     }
17   }

  在configurationElement()方法中用來解析命名空間,參數集,結果集,select,insert,update,delete,標簽等等,我們關注的是buildStatementFromContext(context.evalNodes("select|insert|update|delete"))方法

 1   private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
 2     for (XNode context : list) {
 3       final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
 4       try {
 5         statementParser.parseStatementNode();
 6       } catch (IncompleteElementException e) {
 7         configuration.addIncompleteStatement(statementParser);
 8       }
 9     }
10   }

  在parseStatementNode()方法中,主要是用來將映射文件中的配置信息存儲到MappedStatement對象中去,並將sql語句存儲到了SqlSource對象中,到時候執行sql的時候,直接從該對象中獲取;

4、在配置文件中sqlSession使用的是SqlSessionTemplate,實現了SqlSession接口,就是一個SqlSession模板類,在加載<bean id = sqlSession>標簽時,會去調用SqlSessionTemplate構造方法,SqlSessionTemplate類有三個構造方法,最終會調用

參數sqlSessionFactory是SqlSessionFactoryBean初始化時生成的DefaultSqlSessionFactory,參數executorType是自己傳入的,默認是simple,在這個構造方法中會初始換sqlSession(標記部分),通過動態代理生成SqlSession代理類,這里重點介紹

SqlSessionInterceptor,這個是SqlSessionTemplate類中的內部類,實現了InvokerHandler,是一個代理類,所以生成的SqlSession代理類,每個方法都會去執行SqlSessionInterceptor中的invoke()方法,如下所示:

 

首先會去初始化sqlSession對象,在getSqlSession(sessionFactory, executorType, exceptionTranslator)方法中會去調用sessionFactory中的openSession(executorType)方法,又通過調用openSessionFromDataSource(execType, level, autoCommit)方法,在該方法中進行初始化,對事物對象,executor進行初始化(Configuration.newExecutor(tx, execType)根據傳入的類型創建對應的Executor對象),並創建且返回defaultSqlSession對象;executor對象對接下來sql的執行很重要,會在下一節進行介紹;

5、SqlSession

  1)SqlSession主要有四大核心組件對象:Executor,StatementHandler,ParameterHandler,ResultSetHandler

  2)在分析這四個組件對象之前,先介紹一下,為什么在使用mybatis,只需要寫映射文件對應的接口類,不需要寫接口的實現類,這是因為mybatis使用Java中的動態代理,在SqlSession.getMapper(Class<T> type)方法時,本質是調用Configuration類下getMapper(Class<T> type, SqlSession sqlSession),調用關系如下:

SqlSession.getMapper——>Configuration.getMapper——>MapperRegistry.getMapper——>MapperProxyFactory.newInstance

在MapperProxyFactory.newInstance方法中,會使用代理類MapperProxy為該接口生成一個代理對象,在代理類中的每個方法中都會調用MapperProxy類中的invoke()方法,在invoke()方法中會為每個方法生成一個MapperMethod對象,在去調用MapperMethod類中execute()方法,這個其實就是SqlSession的入口;

 1 public class MapperProxyFactory<T> {
 2 
 3   private final Class<T> mapperInterface;
 4   private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 5 
 6   public MapperProxyFactory(Class<T> mapperInterface) {
 7     this.mapperInterface = mapperInterface;
 8   }
 9 
10   public Class<T> getMapperInterface() {
11     return mapperInterface;
12   }
13 
14   public Map<Method, MapperMethod> getMethodCache() {
15     return methodCache;
16   }
17 
18   @SuppressWarnings("unchecked")
19   protected T newInstance(MapperProxy<T> mapperProxy) {
20     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
21   }
22 
23   public T newInstance(SqlSession sqlSession) {
24     final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
25     return newInstance(mapperProxy);
26   }
27 
28 }
 1 public class MapperProxy<T> implements InvocationHandler, Serializable {
 2 
 3   private static final long serialVersionUID = -6424540398559729838L;
 4   private final SqlSession sqlSession;
 5   private final Class<T> mapperInterface;
 6   private final Map<Method, MapperMethod> methodCache;
 7 
 8   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
 9     this.sqlSession = sqlSession;
10     this.mapperInterface = mapperInterface;
11     this.methodCache = methodCache;
12   }
13 
14   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
15     if (Object.class.equals(method.getDeclaringClass())) {
16       try {
17         return method.invoke(this, args);
18       } catch (Throwable t) {
19         throw ExceptionUtil.unwrapThrowable(t);
20       }
21     }
22     final MapperMethod mapperMethod = cachedMapperMethod(method);
23     return mapperMethod.execute(sqlSession, args);
24   }
25 
26   private MapperMethod cachedMapperMethod(Method method) {
27     MapperMethod mapperMethod = methodCache.get(method);
28     if (mapperMethod == null) {
29       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
30       methodCache.put(method, mapperMethod);
31     }
32     return mapperMethod;
33   }
34 
35 }

 

3)在execute方法中會根據sql類型,選擇相應的方法進行執行,其實本質是調用SqlSession接口中相應的方法,SqlSession有三個實現類,其實最后本質還是調用的是DefaultSqlSession中的實現方法(在上節中介紹會初始化DefaultSqlSession對象),底層的調用是Executor的query()方法,調用的是實現類BaseExecutor的query()方法,在該方法中調用的是doQuery()方法;Executor有三個實現類,SimpleExecutor(默認)、ReuseExecutor、BatchExecutor;具體使用哪個在初始化SqlSession時確定,參考上節;在接下來的例子主要是以SimpleExecutor類舉例:

 1   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.<E>query(stmt, resultHandler);
 8     } finally {
 9       closeStatement(stmt);
10     }
11   }

 

4)StatementHandler

  在doquery()方法中會創建StatementHandler對象,並調用prepareStatement對sql進行預編譯和參數初始化,在prepareStatement方法中又通過調用StatementHandler接口中的prepare()創建Statement對象和調用parameterize()方法進行參數初始化和sql預編譯;

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
 stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }

 

5)ParameterHandler

  在調用parameterize()方法,底層是通過ParameterHandler.setParameters(statement)方法進行參數的初始化過程;具體的實現過程可以參考DefaultParameterHandler.setParameters(statement)方法;

6)ResultSetHandler

  通過StatementHandler.query()方法會將sql執行返回的結果進行封裝,根據配置的resultSetType類型進行轉換,具體實現可以參考DefaultResultSetHandler類中的handleResultSets方法;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM