mybatis入門介紹以及源碼分析

MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。

從 XML 中構建 SqlSessionFactory

每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例爲中心的。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個預先定製的 Configuration 的實例構建出 SqlSessionFactory 的實例。

從 XML 文件中構建 SqlSessionFactory 的實例非常簡單,建議使用類路徑下的資源文件進行配置。但是也可以使用任意的輸入流(InputStream)實例,包括字符串形式的文件路徑或者 file:// 的 URL 形式的文件路徑來配置。MyBatis 包含一個名叫 Resources 的工具類,它包含一些實用方法,可使從 classpath 或其他位置加載資源文件更加容易。

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

XML 配置文件(configuration XML)中包含了對 MyBatis 系統的核心設置,包含獲取數據庫連接實例的數據源(DataSource)和決定事務作用域和控制方式的事務管理器(TransactionManager)。XML 配置文件的詳細內容後面再探討,這裏先給出一個簡單的示例:

<?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>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

要注意 XML 頭部的聲明,用來驗證 XML 文檔正確性。environment 元素體中包含了事務管理和連接池的配置。mappers 元素則是包含一組 mapper 映射器(這些 mapper 的 XML 文件包含了 SQL 代碼和映射定義信息)。

下面看一下源碼:

首先看一下SqlSessionFactoryBuilder,用來創建SqlSessionFactory

/**
 * Builds {@link SqlSession} instances.
 *
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {//讀取字符流
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

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

  public SqlSessionFactory build(InputStream inputStream) {//讀取字節流
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {//把配置文件解析成Configuration對象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }
    
  public SqlSessionFactory build(Configuration config) {//通過Configuration構造SqlSessionFactory
    return new DefaultSqlSessionFactory(config);//實例化DefaultSqlSessionFactory對象
  }

}
  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

XMLMapperEntityResolver是MyBatis DTD的離線實體解析器

  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }
  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();//解析XML工具
    this.xpath = factory.newXPath();
  }
  private Document createDocument(InputSource inputSource) {
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();//用於創建DOM模式的解析器對象
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      return builder.parse(inputSource);//得到代表整個文檔的 Document 對象
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }
  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;
  }

得到Document對象後,包裝成Configuration對象

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));//解析configuration節點
    return configuration;
  }
  private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties"));//解析properties
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);//設置settings
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      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);
    }
  }

properties

  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>
如果屬性在不只一個地方進行了配置,那麼 MyBatis 將按照下面的順序來加載:

  • 在 properties 元素體內指定的屬性首先被讀取。
  • 然後根據 properties 元素中的 resource 屬性讀取類路徑下屬性文件或根據 url 屬性指定的路徑讀取屬性文件,並覆蓋已讀取的同名屬性。
  • 最後讀取作爲方法參數傳遞的屬性,並覆蓋已讀取的同名屬性。
因此,通過方法參數傳遞的屬性具有最高優先級,resource/url 屬性中指定的配置文件次之,最低優先級的是 properties 屬性中指定的屬性。


settings

  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    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).");
      }
    }
    return props;
  }

設置參數描述有效值默認值
cacheEnabled全局地開啓或關閉配置文件中的所有映射器已經配置的任何緩存。true | falsetrue
lazyLoadingEnabled延遲加載的全局開關。當開啓時,所有關聯對象都會延遲加載。 特定關聯關係中可通過設置fetchType屬性來覆蓋該項的開關狀態。true | falsefalse
aggressiveLazyLoading當開啓時,任何方法的調用都會加載該對象的所有屬性。否則,每個屬性會按需加載(參考lazyLoadTriggerMethods).true | falsefalse (true in ≤3.4.1)
multipleResultSetsEnabled是否允許單一語句返回多結果集(需要兼容驅動)。true | falsetrue
useColumnLabel使用列標籤代替列名。不同的驅動在這方面會有不同的表現, 具體可參考相關驅動文檔或通過測試這兩種不同的模式來觀察所用驅動的結果。true | falsetrue
useGeneratedKeys允許 JDBC 支持自動生成主鍵,需要驅動兼容。 如果設置爲 true 則這個設置強制使用自動生成主鍵,儘管一些驅動不能兼容但仍可正常工作(比如 Derby)。true | falseFalse
autoMappingBehavior指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示取消自動映射;PARTIAL 只會自動映射沒有定義嵌套結果集映射的結果集。 FULL 會自動映射任意複雜的結果集(無論是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定發現自動映射目標未知列(或者未知屬性類型)的行爲。
  • NONE: 不做任何反應
  • WARNING: 輸出提醒日誌 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'的日誌等級必須設置爲 WARN)
  • FAILING: 映射失敗 (拋出 SqlSessionException)
NONE, WARNING, FAILINGNONE
defaultExecutorType配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器將重用語句並執行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout設置超時時間,它決定驅動等待數據庫響應的秒數。任意正整數Not Set (null)
defaultFetchSize爲驅動的結果集獲取數量(fetchSize)設置一個提示值。此參數只可以在查詢設置中被覆蓋。任意正整數Not Set (null)
safeRowBoundsEnabled允許在嵌套語句中使用分頁(RowBounds)。如果允許使用則設置爲false。true | falseFalse
safeResultHandlerEnabled允許在嵌套語句中使用分頁(ResultHandler)。如果允許使用則設置爲false。true | falseTrue
mapUnderscoreToCamelCase是否開啓自動駝峯命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的類似映射。true | falseFalse
localCacheScopeMyBatis 利用本地緩存機制(Local Cache)防止循環引用(circular references)和加速重複嵌套查詢。 默認值爲 SESSION,這種情況下會緩存一個會話中執行的所有查詢。 若設置值爲 STATEMENT,本地會話僅用在語句執行上,對相同 SqlSession 的不同調用將不會共享數據。SESSION | STATEMENTSESSION
jdbcTypeForNull當沒有爲參數提供特定的 JDBC 類型時,爲空值指定 JDBC 類型。 某些驅動需要指定列的 JDBC 類型,多數情況直接用一般類型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量. 大多都爲: NULL, VARCHAR and OTHEROTHER
lazyLoadTriggerMethods指定哪個對象的方法觸發一次延遲加載。用逗號分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定動態 SQL 生成的默認語言。一個類型別名或完全限定類名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默認 TypeHandler 。 (從3.4.5開始)一個類型別名或完全限定類名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定當結果集中值爲 null 的時候是否調用映射對象的 setter(map 對象時爲 put)方法,這對於有 Map.keySet() 依賴或 null 值初始化的時候是有用的。注意基本類型(int、boolean等)是不能設置成 null 的。true | falsefalse
returnInstanceForEmptyRow當返回行的所有列都是空時,MyBatis默認返回null。 當開啓這個設置時,MyBatis會返回一個空實例。 請注意,它也適用於嵌套的結果集 (i.e. collectioin and association)。(從3.4.2開始)true | falsefalse
logPrefix指定 MyBatis 增加到日誌名稱的前綴。任何字符串Not set
logImpl指定 MyBatis 所用日誌的具體實現,未指定時將自動查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGINGNot set
proxyFactory指定 Mybatis 創建具有延遲加載能力的對象所用到的代理工具。CGLIB | JAVASSISTJAVASSIST (MyBatis 3.3 or above)
vfsImpl指定VFS的實現自定義VFS的實現的類全限定名,以逗號分隔。Not set
useActualParamName允許使用方法簽名中的名稱作爲語句參數名稱。 爲了使用該特性,你的工程必須採用Java 8編譯,並且加上-parameters選項。(從3.4.1開始)true | falsetrue
configurationFactory

指定一個提供Configuration實例的類。 這個被返回的Configuration實例用來加載被反序列化對象的懶加載屬性值。 這個類必須包含一個簽名方法static Configuration getConfiguration(). (從 3.2.3 版本開始)

類型別名或者全類名.Not set

一個配置完整的 settings 元素的示例如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

typeAliases

  private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

類型別名是爲 Java 類型設置一個短的名字。它只和 XML 配置有關,存在的意義僅在於用來減少類完全限定名的冗餘。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
當這樣配置時,Blog可以用在任何使用domain.blog.Blog的地方。
也可以指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

plugins

  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
通過 MyBatis 提供的強大機制,使用插件是非常簡單的,只需實現 Interceptor 接口,並指定想要攔截的方法簽名即可。
// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

上面的插件將會攔截在 Executor 實例中所有的 “update” 方法調用, 這裏的 Executor 是負責執行低層映射語句的內部對象。

objectFactory

  private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties properties = context.getChildrenAsProperties();
      ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
      factory.setProperties(properties);
      configuration.setObjectFactory(factory);
    }
  }

MyBatis 每次創建結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成。 默認的對象工廠需要做的僅僅是實例化目標類,要麼通過默認構造方法,要麼在參數映射存在的時候通過參數構造方法來實例化。 如果想覆蓋對象工廠的默認行爲,則可以通過創建自己的對象工廠來實現。比如:

// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }}
<!-- mybatis-config.xml -->
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>
objectWrapperFactory
  private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
      configuration.setObjectWrapperFactory(factory);
    }
  }
reflectorFactory
  private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
       String type = context.getStringAttribute("type");
       ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
       configuration.setReflectorFactory(factory);
    }
  }
environments
  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)) {
          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());
        }
      }
    }
  }

MyBatis 可以配置成適應多種環境,這種機制有助於將 SQL 映射應用於多種數據庫之中, 現實情況下有多種理由需要這麼做。例如,開發、測試和生產環境需要有不同的配置;或者共享相同 Schema 的多個生產數據庫, 想使用相同的 SQL 映射。許多類似的用例。

不過要記住:儘管可以配置多個環境,每個 SqlSessionFactory 實例只能選擇其一。

環境元素定義瞭如何配置環境。

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>
在 MyBatis 中有兩種類型的事務管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC – 這個配置就是直接使用了 JDBC 的提交和回滾設置,它依賴於從數據源得到的連接來管理事務作用域。
  • MANAGED – 這個配置幾乎沒做什麼。它從來不提交或回滾一個連接,而是讓容器來管理事務的整個生命週期(比如 JEE 應用服務器的上下文)。 默認情況下它會關閉連接,然而一些容器並不希望這樣,因此需要將 closeConnection 屬性設置爲 false 來阻止它默認的關閉行爲。

如果你正在使用 Spring + MyBatis,則沒有必要配置事務管理器, 因爲 Spring 模塊會使用自帶的管理器來覆蓋前面的配置。


dataSource 元素使用標準的 JDBC 數據源接口來配置 JDBC 連接對象的資源。


許多 MyBatis 的應用程序會按示例中的例子來配置數據源。雖然這是可選的,但爲了使用延遲加載,數據源是必須配置的。
有三種內建的數據源類型(也就是 type=”[UNPOOLED|POOLED|JNDI]”):

UNPOOLED– 這個數據源的實現只是每次被請求時打開和關閉連接。雖然有點慢,但對於在數據庫連接可用性方面沒有太高要求的簡單應用程序來說,是一個很好的選擇。 不同的數據庫在性能方面的表現也是不一樣的,對於某些數據庫來說,使用連接池並不重要,這個配置就很適合這種情形。UNPOOLED 類型的數據源僅僅需要配置以下 5 種屬性:

driver – 這是 JDBC 驅動的 Java 類的完全限定名(並不是 JDBC 驅動中可能包含的數據源類)。
url – 這是數據庫的 JDBC URL 地址。
username – 登錄數據庫的用戶名。
password – 登錄數據庫的密碼。
defaultTransactionIsolationLevel – 默認的連接事務隔離級別。
作爲可選項,你也可以傳遞屬性給數據庫驅動。要這樣做,屬性的前綴爲“driver.”,例如:

driver.encoding=UTF8 這將通過 DriverManager.getConnection(url,driverProperties) 方法傳遞值爲 UTF8 的 encoding 屬性給數據庫驅動。


POOLED– 這種數據源的實現利用“池”的概念將 JDBC 連接對象組織起來,避免了創建新的連接實例時所必需的初始化和認證時間。 這是一種使得併發 Web 應用快速響應請求的流行處理方式。

除了上述提到 UNPOOLED 下的屬性外,還有更多屬性用來配置 POOLED 的數據源:

poolMaximumActiveConnections – 在任意時間可以存在的活動(也就是正在使用)連接數量,默認值:10
poolMaximumIdleConnections – 任意時間可能存在的空閒連接數。
poolMaximumCheckoutTime – 在被強制返回之前,池中連接被檢出(checked out)時間,默認值:20000 毫秒(即 20 秒)
poolTimeToWait – 這是一個底層設置,如果獲取連接花費了相當長的時間,連接池會打印狀態日誌並重新嘗試獲取一個連接(避免在誤配置的情況下一直安靜的失敗),默認值:20000 毫秒(即 20 秒)。
poolMaximumLocalBadConnectionTolerance – 這是一個關於壞連接容忍度的底層設置, 作用於每一個嘗試從緩存池獲取連接的線程. 如果這個線程獲取到的是一個壞的連接,那麼這個數據源允許這個線程嘗試重新獲取一個新的連接,但是這個重新嘗試的次數不應該超過 poolMaximumIdleConnections 與 poolMaximumLocalBadConnectionTolerance 之和。 默認值:3 (新增於 3.4.5)
poolPingQuery – 發送到數據庫的偵測查詢,用來檢驗連接是否正常工作並準備接受請求。默認是“NO PING QUERY SET”,這會導致多數數據庫驅動失敗時帶有一個恰當的錯誤消息。
poolPingEnabled – 是否啓用偵測查詢。若開啓,需要設置 poolPingQuery 屬性爲一個可執行的 SQL 語句(最好是一個速度非常快的 SQL 語句),默認值:false。

poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的頻率。可以被設置爲和數據庫連接超時時間一樣,來避免不必要的偵測,默認值:0(即所有連接每一時刻都被偵測 — 當然僅當 poolPingEnabled 爲 true 時適用)。


JNDI – 這個數據源的實現是爲了能在如 EJB 或應用服務器這類容器中使用,容器可以集中或在外部配置數據源,然後放置一個 JNDI 上下文的引用。這種數據源配置只需要兩個屬性:

initial_context – 這個屬性用來在 InitialContext 中尋找上下文(即,initialContext.lookup(initial_context))。這是個可選屬性,如果忽略,那麼 data_source 屬性將會直接從 InitialContext 中尋找。
data_source – 這是引用數據源實例位置的上下文的路徑。提供了 initial_context 配置時會在其返回的上下文中進行查找,沒有提供時則直接在 InitialContext 中查找。
和其他數據源配置類似,可以通過添加前綴“env.”直接把屬性傳遞給初始上下文。比如:

env.encoding=UTF8這就會在初始上下文(InitialContext)實例化時往它的構造方法傳遞值爲 UTF8 的 encoding 屬性。


databaseIdProvider

  private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // awful patch to keep backward compatibility
      if ("VENDOR".equals(type)) {
          type = "DB_VENDOR";
      }
      Properties properties = context.getChildrenAsProperties();
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }
MyBatis 可以根據不同的數據庫廠商執行不同的語句,這種多廠商的支持是基於映射語句中的 databaseId 屬性。 MyBatis 會加載不帶 databaseId 屬性和帶有匹配當前數據庫 databaseId 屬性的所有語句。 如果同時找到帶有 databaseId 和不帶 databaseId 的相同語句,則後者會被捨棄。 爲支持多廠商特性只要像下面這樣在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:
<databaseIdProvider type="DB_VENDOR" />

這裏的 DB_VENDOR 會通過 DatabaseMetaData#getDatabaseProductName() 返回的字符串進行設置。 由於通常情況下這個字符串都非常長而且相同產品的不同版本會返回不同的值,所以最好通過設置屬性別名來使其變短,如下:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>        
  <property name="Oracle" value="oracle" />
</databaseIdProvider>
typeHandlers
  private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。下表描述了一些默認的類型處理器。

類型處理器Java 類型JDBC 類型
BooleanTypeHandlerjava.lang.Booleanboolean數據庫兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Bytebyte數據庫兼容的 NUMERIC 或 BYTE
ShortTypeHandlerjava.lang.Shortshort數據庫兼容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandlerjava.lang.Integerint數據庫兼容的 NUMERIC 或 INTEGER
LongTypeHandlerjava.lang.Longlong數據庫兼容的 NUMERIC 或 LONG INTEGER
FloatTypeHandlerjava.lang.Floatfloat數據庫兼容的 NUMERIC 或 FLOAT
DoubleTypeHandlerjava.lang.Doubledouble數據庫兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal數據庫兼容的 NUMERIC 或 DECIMAL
StringTypeHandlerjava.lang.StringCHARVARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOBLONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHARNCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]數據庫兼容的字節流類型
BlobTypeHandlerbyte[]BLOBLONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定類型
EnumTypeHandlerEnumeration TypeVARCHAR-任何兼容的字符串類型,存儲枚舉的名稱(而不是索引)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERIC 或 DOUBLE 類型,存儲枚舉的索引(而不是名稱)。
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHAR or LONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE
你可以重寫類型處理器或創建你自己的類型處理器來處理不支持的或非標準的類型。 具體做法爲:實現 org.apache.ibatis.type.TypeHandler 接口, 或繼承一個很便利的類 org.apache.ibatis.type.BaseTypeHandler, 然後可以選擇性地將它映射到一個 JDBC 類型。比如:
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}
<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
使用這個的類型處理器將會覆蓋已經存在的處理 Java 的 String 類型屬性和 VARCHAR 參數及結果的類型處理器。 要注意 MyBatis 不會窺探數據庫元信息來決定使用哪種類型,所以你必須在參數和結果映射中指明那是 VARCHAR 類型的字段, 以使其能夠綁定到正確的類型處理器上。 這是因爲:MyBatis 直到語句被執行才清楚數據類型。

通過類型處理器的泛型,MyBatis 可以得知該類型處理器處理的 Java 類型,不過這種行爲可以通過兩種方法改變:

在類型處理器的配置元素(typeHandler element)上增加一個 javaType 屬性(比如:javaType="String");
在類型處理器的類上(TypeHandler class)增加一個 @MappedTypes 註解來指定與其關聯的 Java 類型列表。 如果在 javaType 屬性中也同時指定,則註解方式將被忽略。
可以通過兩種方式來指定被關聯的 JDBC 類型:

在類型處理器的配置元素上增加一個 jdbcType 屬性(比如:jdbcType="VARCHAR");
在類型處理器的類上(TypeHandler class)增加一個 @MappedJdbcTypes 註解來指定與其關聯的 JDBC 類型列表。 如果在 jdbcType 屬性中也同時指定,則註解方式將被忽略。
當決定在ResultMap中使用某一TypeHandler時,此時java類型是已知的(從結果類型中獲得),但是JDBC類型是未知的。 因此Mybatis使用javaType=[TheJavaType], jdbcType=null的組合來選擇一個TypeHandler。 這意味着使用@MappedJdbcTypes註解可以限制TypeHandler的範圍,同時除非顯式的設置,否則TypeHandler在ResultMap中將是無效的。 如果希望在ResultMap中使用TypeHandler,那麼設置@MappedJdbcTypes註解的includeNullJdbcType=true即可。 然而從Mybatis 3.4.0開始,如果只有一個註冊的TypeHandler來處理Java類型,那麼它將是ResultMap使用Java類型時的默認值(即使沒有includeNullJdbcType=true)。
最後,可以讓 MyBatis 爲你查找類型處理器:
<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>
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.");
          }
        }
      }
    }
  }

既然 MyBatis 的行爲已經由上述元素配置完了,我們現在就要定義 SQL 映射語句了。但是首先我們需要告訴 MyBatis 到哪裏去找到這些語句。 Java 在自動查找這方面沒有提供一個很好的方法,所以最佳的方式是告訴 MyBatis 到哪裏去找映射文件。你可以使用相對於類路徑的資源引用, 或完全限定資源定位符(包括 file:/// 的 URL),或類名和包名等。例如:

<!-- 使用相對於類路徑的資源引用 -->
<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>

從 SqlSessionFactory 中獲取 SqlSession

既然有了 SqlSessionFactory ,顧名思義,我們就可以從中獲得 SqlSession 的實例了。SqlSession 完全包含了面向數據庫執行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執行已映射的 SQL 語句。例如:

SqlSession session = sqlSessionFactory.openSession();
try {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

下面看一下源碼:

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

DefaultSqlSessionFactory實現類:

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;//默認的執行器
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//得到事務管理器
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//新建事務
      final Executor executor = configuration.newExecutor(tx, execType);//創建執行器
      return new DefaultSqlSession(configuration, executor, autoCommit);//返回sqlSession
    } catch (Exception e) {
      closeTransaction(tx); // 關閉事務
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);//默認使用SimpleExecutor
    }
    if (cacheEnabled) {//如果開啓緩存,則創建CachingExecutor
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);//插件攔截
    return executor;
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章