天天看點

mybatis系列-XML 映射配置檔案(二)

XML 映射配置檔案

MyBatis 的配置檔案包含了影響 MyBatis 行為甚深的設定(settings)和屬性(properties)資訊。文檔的頂層結構如下:

  • configuration 配置
    • properties 屬性
    • settings 設定
    • typeAliases 類型命名
    • typeHandlers 類型處理器
    • objectFactory 對象工廠
    • plugins 插件
    • environments 環境
      • environment 環境變量
        • transactionManager 事務管理器
        • dataSource 資料源
    • databaseIdProvider 資料庫廠商辨別
    • mappers 映射器

properties

這些屬性都是可外部配置且可動态替換的,既可以在典型的 Java 屬性檔案中配置,亦可通過 properties 元素的子元素來傳遞。例如:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>      

其中的屬性就可以在整個配置檔案中使用來替換需要動态配置的屬性值。比如:

<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>      

這個例子中的 username 和 password 将會由 properties 元素中設定的相應值來替換。 driver 和 url 屬性将會由 config.properties 檔案中對應的值來替換。這樣就為配置提供了諸多靈活選擇。

屬性也可以被傳遞到 SqlSessionBuilder.build()方法中。例如:

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props);

// ... or ...

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, props);      

如果屬性在不隻一個地方進行了配置,那麼 MyBatis 将按照下面的順序來加載:

  • 在 properties 元素體内指定的屬性首先被讀取。
  • 然後根據 properties 元素中的 resource 屬性讀取類路徑下屬性檔案或根據 url 屬性指定的路徑讀取屬性檔案,并覆寫已讀取的同名屬性。
  • 最後讀取作為方法參數傳遞的屬性,并覆寫已讀取的同名屬性。

是以,通過方法參數傳遞的屬性具有最高優先級,resource/url 屬性中指定的配置檔案次之,最低優先級的是 properties 屬性中指定的屬性。

settings

這是 MyBatis 中極為重要的調整設定,它們會改變 MyBatis 的運作時行為。下表描述了設定中各項的意圖、預設值等。

設定參數 描述 有效值 預設值
cacheEnabled 該配置影響的所有映射器中配置的緩存的全局開關。 true | false true
lazyLoadingEnabled 延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設定fetchType屬性來覆寫該項的開關狀态。 true | false false
aggressiveLazyLoading 當啟用時,對任意延遲屬性的調用會使帶有延遲加載屬性的對象完整加載;反之,每種屬性将會按需加載。 true | false true
multipleResultSetsEnabled 是否允許單一語句傳回多結果集(需要相容驅動)。 true | false true
useColumnLabel 使用列标簽代替列名。不同的驅動在這方面會有不同的表現, 具體可參考相關驅動文檔或通過測試這兩種不同的模式來觀察所用驅動的結果。 true | false true
useGeneratedKeys 允許 JDBC 支援自動生成主鍵,需要驅動相容。 如果設定為 true 則這個設定強制使用自動生成主鍵,盡管一些驅動不能相容但仍可正常工作(比如 Derby)。 true | false False
autoMappingBehavior 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示取消自動映射;PARTIAL 隻會自動映射沒有定義嵌套結果集映射的結果集。 FULL 會自動映射任意複雜的結果集(無論是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior Specify the behavior when detects an unknown column (or unknown property type) of automatic mapping target.
  • NONE: Do nothing
  • WARNING: Output warning log (The log level of'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'must be set to WARN)
  • FAILING: Fail mapping (Throw SqlSessionException)
NONE, WARNING, FAILING NONE
defaultExecutorType 配置預設的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器将重用語句并執行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 設定逾時時間,它決定驅動等待資料庫響應的秒數。 Any positive integer Not Set (null)
defaultFetchSize Sets the driver a hint as to control fetching size for return results. This parameter value can be override by a query setting. Any positive integer Not Set (null)
safeRowBoundsEnabled 允許在嵌套語句中使用分頁(RowBounds)。 If allow, set the false. true | false False
safeResultHandlerEnabled 允許在嵌套語句中使用分頁(ResultHandler)。 If allow, set the false. true | false True
mapUnderscoreToCamelCase 是否開啟自動駝峰命名規則(camel case)映射,即從經典資料庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的類似映射。 true | false False
localCacheScope MyBatis 利用本地緩存機制(Local Cache)防止循環引用(circular references)和加速重複嵌套查詢。 預設值為 SESSION,這種情況下會緩存一個會話中執行的所有查詢。 若設定值為 STATEMENT,本地會話僅用在語句執行上,對相同 SqlSession 的不同調用将不會共享資料。 SESSION | STATEMENT SESSION
jdbcTypeForNull 當沒有為參數提供特定的 JDBC 類型時,為空值指定 JDBC 類型。 某些驅動需要指定列的 JDBC 類型,多數情況直接用一般類型即可,比如 NULL、VARCHAR 或 OTHER。 JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER OTHER
lazyLoadTriggerMethods 指定哪個對象的方法觸發一次延遲加載。 A method name list separated by commas equals,clone,hashCode,toString
defaultScriptingLanguage 指定動态 SQL 生成的預設語言。 A type alias or fully qualified class name. org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
callSettersOnNulls 指定當結果集中值為 null 的時候是否調用映射對象的 setter(map 對象時為 put)方法,這對于有 Map.keySet() 依賴或 null 值初始化的時候是有用的。注意基本類型(int、boolean等)是不能設定成 null 的。 true | false false
logPrefix 指定 MyBatis 增加到日志名稱的字首。 Any String Not set
logImpl 指定 MyBatis 所用日志的具體實作,未指定時将自動查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING Not set
proxyFactory 指定 Mybatis 建立具有延遲加載能力的對象所用到的代理工具。 CGLIB | JAVASSIST JAVASSIST (MyBatis 3.3 or above)
vfsImpl Specifies VFS implementations Fully qualified class names of custom VFS implementation separated by commas. Not set
useActualParamName Allow referencing statement parameters by their actual names declared in the method signature. To use this feature, your project must be compiled in Java 8 with -parameters option. (Since: 3.4.1) true | false true

一個配置完整的 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

類型别名是為 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>      

每一個在包 domain.blog 中的 Java Bean,在沒有注解的情況下,會使用 Bean 的首字母小寫的非限定類名來作為它的别名。 比如 domain.blog.Author 的别名為 author;若有注解,則别名為其注解值。看下面的例子:

@Alias("author")
public class Author {
    ...
}      

已經為許多常見的 Java 類型内建了相應的類型别名。它們都是大小寫不敏感的,需要注意的是由基本類型名稱重複導緻的特殊處理。

别名 映射的類型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

typeHandlers

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

NOTE If you use classes provided by JSR-310(Date and Time API), you can use the mybatis-typehandlers-jsr310.

類型處理器 Java 類型 JDBC 類型
BooleanTypeHandler java.lang.Boolean, boolean 資料庫相容的 BOOLEAN
ByteTypeHandler java.lang.Byte, byte 資料庫相容的 NUMERIC 或 BYTE
ShortTypeHandler java.lang.Short, short 資料庫相容的 NUMERIC 或 SHORT INTEGER
IntegerTypeHandler java.lang.Integer, int 資料庫相容的 NUMERIC 或 INTEGER
LongTypeHandler java.lang.Long, long 資料庫相容的 NUMERIC 或 LONG INTEGER
FloatTypeHandler java.lang.Float, float 資料庫相容的 NUMERIC 或 FLOAT
DoubleTypeHandler java.lang.Double, double 資料庫相容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandler java.math.BigDecimal 資料庫相容的 NUMERIC 或 DECIMAL
StringTypeHandler java.lang.String CHAR, VARCHAR
ClobReaderTypeHandler java.io.Reader -
ClobTypeHandler java.lang.String CLOB, LONGVARCHAR
NStringTypeHandler java.lang.String NVARCHAR, NCHAR
NClobTypeHandler java.lang.String NCLOB
BlobInputStreamTypeHandler java.io.InputStream -
ByteArrayTypeHandler byte[] 資料庫相容的位元組流類型
BlobTypeHandler byte[] BLOB, LONGVARBINARY
DateTypeHandler java.util.Date TIMESTAMP
DateOnlyTypeHandler java.util.Date DATE
TimeOnlyTypeHandler java.util.Date TIME
SqlTimestampTypeHandler java.sql.Timestamp TIMESTAMP
SqlDateTypeHandler java.sql.Date DATE
SqlTimeTypeHandler java.sql.Time TIME
ObjectTypeHandler Any OTHER 或未指定類型
EnumTypeHandler Enumeration Type VARCHAR-任何相容的字元串類型,存儲枚舉的名稱(而不是索引)
EnumOrdinalTypeHandler Enumeration Type 任何相容的 NUMERIC 或 DOUBLE 類型,存儲枚舉的索引(而不是名稱)。

你可以重寫類型處理器或建立你自己的類型處理器來處理不支援的或非标準的類型。 具體做法為:實作 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 屬性中也同時指定,則注解方式将被忽略。

When deciding which TypeHandler to use in a ResultMap, the Java type is known (from the result type), but the JDBC type is unknown. MyBatis therefore uses the combination javaType=[TheJavaType], jdbcType=null to choose a TypeHandler. This means that using a @MappedJdbcTypes annotation restricts the scope of a TypeHandler and makes it unavailable for use inResultMaps unless explicity set. To make a TypeHandler available for use in a ResultMap, set includeNullJdbcType=true on the @MappedJdbcTypes annotation. Since Mybatis 3.4.0 however, if a single TypeHandler is registered to handle a Java type, it will be used by default in ResultMaps using this Java type (i.e. even without includeNullJdbcType=true).

最後,可以讓 MyBatis 為你查找類型處理器:

<!-- mybatis-config.xml -->
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>      

注意在使用自動檢索(autodiscovery)功能的時候,隻能通過注解方式來指定 JDBC 的類型。

你能建立一個泛型類型處理器,它可以處理多于一個類。為達到此目的, 需要增加一個接收該類作為參數的構造器,這樣在構造一個類型處理器的時候 MyBatis 就會傳入一個具體的類。

//GenericTypeHandler.java
public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {

  private Class<E> type;

  public GenericTypeHandler(Class<E> type) {
    if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
    this.type = type;
  }
  ...      

EnumTypeHandler 和 EnumOrdinalTypeHandler 都是泛型類型處理器(generic TypeHandlers), 我們将會在接下來的部分詳細探讨。

處理枚舉類型

若想映射枚舉類型 Enum,則需要從 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中選一個來使用。

比如說我們想存儲取近似值時用到的舍入模式。預設情況下,MyBatis 會利用 EnumTypeHandler 來把 Enum 值轉換成對應的名字。

注意 EnumTypeHandler 在某種意義上來說是比較特别的,其他的處理器隻針對某個特定的類,而它不同,它會處理任意繼承了 Enum 的類。

不過,我們可能不想存儲名字,相反我們的 DBA 會堅持使用整形值代碼。那也一樣輕而易舉: 在配置檔案中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 這樣每個 RoundingMode 将通過他們的序數值來映射成對應的整形。

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
</typeHandlers>      

但是怎樣能将同樣的 Enum 既映射成字元串又映射成整形呢?

自動映射器(auto-mapper)會自動地選用 EnumOrdinalTypeHandler 來處理, 是以如果我們想用普通的 EnumTypeHandler,就非要為那些 SQL 語句顯式地設定要用到的類型處理器不可。

(下一節才開始講映射器檔案,是以如果是首次閱讀該文檔,你可能需要先越過這一步,過會再來看。)

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
		<id column="id" property="id"/>
		<result column="name" property="name"/>
		<result column="funkyNumber" property="funkyNumber"/>
		<result column="roundingMode" property="roundingMode"/>
	</resultMap>

	<select id="getUser" resultMap="usermap">
		select * from users
	</select>
	<insert id="insert">
	    insert into users (id, name, funkyNumber, roundingMode) values (
	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode}
	    )
	</insert>
	
	<resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap2">
		<id column="id" property="id"/>
		<result column="name" property="name"/>
		<result column="funkyNumber" property="funkyNumber"/>
		<result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
	</resultMap>
	<select id="getUser2" resultMap="usermap2">
		select * from users2
	</select>
	<insert id="insert2">
	    insert into users2 (id, name, funkyNumber, roundingMode) values (
	    	#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
	    )
	</insert>

</mapper>      

注意,這裡的 select 語句強制使用 resultMap 來代替 resultType。

對象工廠(objectFactory)

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>      

ObjectFactory 接口很簡單,它包含兩個建立用的方法,一個是處理預設構造方法的,另外一個是處理帶參數的構造方法的。 最後,setProperties 方法可以被用來配置 ObjectFactory,在初始化你的 ObjectFactory 執行個體後, objectFactory 元素體中定義的屬性會被傳遞給 setProperties 方法。

插件(plugins)

MyBatis 允許你在已映射語句執行過程中的某一點進行攔截調用。預設情況下,MyBatis 允許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

這些類中方法的細節可以通過檢視每個方法的簽名來發現,或者直接檢視 MyBatis 的發行包中的源代碼。 假設你想做的不僅僅是監控方法的調用,那麼你應該很好的了解正在重寫的方法的行為。 因為如果在試圖修改或重寫已有方法的行為的時候,你很可能在破壞 MyBatis 的核心子產品。 這些都是更低層的類和方法,是以使用插件的時候要特别當心。

通過 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 是負責執行低層映射語句的内部對象。

NOTE 覆寫配置類

除了用插件來修改 MyBatis 核心行為之外,還可以通過完全覆寫配置類來達到目的。隻需繼承後覆寫其中的每個方法,再把它傳遞到 sqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,這可能會嚴重影響 MyBatis 的行為,務請慎之又慎。

配置環境(environments)

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

不過要記住:盡管可以配置多個環境,每個 SqlSessionFactory 執行個體隻能選擇其一。

是以,如果你想連接配接兩個資料庫,就需要建立兩個 SqlSessionFactory 執行個體,每個資料庫對應一個。而如果是三個資料庫,就需要三個執行個體,依此類推,記起來很簡單:

  • 每個資料庫對應一個 SqlSessionFactory 執行個體

為了指定建立哪種環境,隻要将它作為可選的參數傳遞給 SqlSessionFactoryBuilder 即可。可以接受環境配置的兩個方法簽名是:

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);      

如果忽略了環境參數,那麼預設環境将會被加載,如下所示:

SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);      

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

<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>      

注意這裡的關鍵點:

  • 預設的環境 ID(比如:default=”development”)。
  • 每個 environment 元素定義的環境 ID(比如:id=”development”)。
  • 事務管理器的配置(比如:type=”JDBC”)。
  • 資料源的配置(比如:type=”POOLED”)。

預設的環境和環境 ID 是一目了然的。随你怎麼命名,隻要保證預設環境要比對其中一個環境ID。

事務管理器(transactionManager)

在 MyBatis 中有兩種類型的事務管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC – 這個配置就是直接使用了 JDBC 的送出和復原設定,它依賴于從資料源得到的連接配接來管理事務範圍。
  • MANAGED – 這個配置幾乎沒做什麼。它從來不送出或復原一個連接配接,而是讓容器來管理事務的整個生命周期(比如 JEE 應用伺服器的上下文)。 預設情況下它會關閉連接配接,然而一些容器并不希望這樣,是以需要将 closeConnection 屬性設定為 false 來阻止它預設的關閉行為。例如:
    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>      

NOTE如果你正在使用 Spring + MyBatis,則沒有必要配置事務管理器, 因為 Spring 子產品會使用自帶的管理器來覆寫前面的配置。

這兩種事務管理器類型都不需要任何屬性。它們不過是類型别名,換句話說,你可以使用 TransactionFactory 接口的實作類的完全限定名或類型别名代替它們。

public interface TransactionFactory {
  void setProperties(Properties props);  
  Transaction newTransaction(Connection conn);
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);  
}      

任何在 XML 中配置的屬性在執行個體化之後将會被傳遞給 setProperties() 方法。你也需要建立一個 Transaction 接口的實作類,這個接口也很簡單:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}      

使用這兩個接口,你可以完全自定義 MyBatis 對事務的處理。

資料源(dataSource)

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 秒)。
  • poolPingQuery – 發送到資料庫的偵測查詢,用來檢驗連接配接是否處在正常工作秩序中并準備接受請求。預設是“NO PING QUERY SET”,這會導緻多數資料庫驅動失敗時帶有一個恰當的錯誤消息。
  • poolPingEnabled – 是否啟用偵測查詢。若開啟,也必須使用一個可執行的 SQL 語句設定 poolPingQuery 屬性(最好是一個非常快的 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 屬性。

通過需要實作接口 org.apache.ibatis.datasource.DataSourceFactory , 也可使用任何第三方資料源,:

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}      

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父類來建構新的資料源擴充卡,比如下面這段插入 C3P0 資料源所必需的代碼:

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
        
public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

  public C3P0DataSourceFactory() {
    this.dataSource = new ComboPooledDataSource();
  }
}      

為了令其工作,為每個需要 MyBatis 調用的 setter 方法中增加一個屬性。下面是一個可以連接配接至 PostgreSQL 資料庫的例子:

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>      

databaseIdProvider

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>      

在有 properties 時,DB_VENDOR databaseIdProvider 的将被設定為第一個能比對資料庫産品名稱的屬性鍵對應的值,如果沒有比對的屬性将會設定為 “null”。 在這個例子中,如果getDatabaseProductName() 傳回“Oracle (DataDirect)”,databaseId 将被設定為“oracle”。

你可以通過實作接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注冊來建構自己的 DatabaseIdProvider:

public interface DatabaseIdProvider {
  void setProperties(Properties p);
  String getDatabaseId(DataSource dataSource) throws SQLException;
}      

映射器(mappers)

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

<!-- Using classpath relative resources -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>      
<!-- Using url fully qualified paths -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>      
<!-- Using mapper interface classes -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>      
<!-- Register all interfaces in a package as mappers -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>      

這些配置會告訴了 MyBatis 去哪裡找映射檔案,剩下的細節就應該是每個 SQL 映射檔案了,也就是接下來我們要讨論的。

繼續閱讀