MyBatis 的配置檔案包含了會深深影響 MyBatis 行為的設定和屬性資訊。 + 配置文件的最上層結構如下:
+ +這些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性檔案中配置這些屬性,也可以在 properties 元素的子元素中設定。例如:
+ +設定好的屬性可以在整個配置檔案中用來替換需要動態配置的屬性值。比如: +
+ +這個例子中的 username 和 password 將會由 properties 元素中設定的相應值來替換。 + driver 和 url 屬性將會由 config.properties 檔案中對應的值來替換。這樣就為配置提供了諸多靈活選擇。
+也可以在 SqlSessionFactoryBuilder.build() 方法中傳入屬性值。例如:
+ +如果一個屬性在不只一個地方進行了配置,那麼,MyBatis 將按照下面的順序來載入:
+因此,透過方法引數傳遞的屬性具有最高優先順序,resource/url + 屬性中指定的配置檔案次之,最低優先順序的則是 properties 元素中指定的屬性。
+ ++ 從 MyBatis 3.4.2 開始,你可以為佔位符指定一個預設值。例如: +
+ + ++ 這個特性預設是關閉的。要啟用這個特性,需要新增一個特定的屬性來開啟這個特性。例如: +
+ + + +
+ 提示
+ 如果你在屬性名中使用了 ":"
+ 字元(如:db:username
),或者在 SQL
+ 對映中使用了 OGNL 表示式的三元運算子(如: ${tableName != null ?
+ tableName : 'global_constants'}
),就需要設定特定的屬性來修改分隔屬性名和預設值的字元。例如:
+
這是 MyBatis 中極為重要的調整設定,它們會改變 MyBatis 的執行時行為。 + 下表描述了設定中各項設定的含義、預設值等。
+ +設定名 | +描述 | +有效值 | +預設值 | +
---|---|---|---|
+ cacheEnabled + | ++ 全域性性地開啟或關閉所有對映器配置檔案中已配置的任何快取。 + | ++ true | false + | ++ true + | +
+ lazyLoadingEnabled + | +
+ 延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入。
+ 特定關聯關係中可透過設定 fetchType
+ 屬性來覆蓋該項的開關狀態。
+ |
+ + true | false + | ++ false + | +
+ aggressiveLazyLoading + | +
+ 開啟時,任一方法的呼叫都會載入該物件的所有延遲載入屬性。
+ 否則,每個延遲載入屬性會按需載入(參考 lazyLoadTriggerMethods )。
+ |
+ + true | false + | ++ false (在 3.4.1 及之前的版本中預設為 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 + | +
+ 指定發現自動對映目標未知列(或未知屬性型別)的行為。
+
|
+ + NONE, WARNING, FAILING + | ++ NONE + | +
+ defaultExecutorType + | ++ 配置預設的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(PreparedStatement); + BATCH 執行器不僅重用語句還會執行批量更新。 + | ++ SIMPLE + REUSE + BATCH + | ++ SIMPLE + | +
+ defaultStatementTimeout + | ++ 設定超時時間,它決定資料庫驅動等待資料庫響應的秒數。 + | ++ 任意正整數 + | ++ 未設定 (null) + | +
+ defaultFetchSize + | ++ 為驅動的結果集獲取數量(fetchSize)設定一個建議值。此引數只可以在查詢設定中被覆蓋。 + | ++ 任意正整數 + | ++ 未設定 (null) + | +
+ defaultResultSetType + | ++ 指定語句預設的滾動策略。(新增於 3.5.2) + | ++ FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同於未設定) + | ++ 未設定 (null) + | +
+ safeRowBoundsEnabled + | ++ 是否允許在巢狀語句中使用分頁(RowBounds)。如果允許使用則設定為 false。 + | ++ true | false + | ++ False + | +
+ safeResultHandlerEnabled + | ++ 是否允許在巢狀語句中使用結果處理器(ResultHandler)。如果允許使用則設定為 false。 + | ++ true | false + | ++ True + | +
+ mapUnderscoreToCamelCase + | ++ 是否開啟駝峰命名自動對映,即從經典資料庫列名 + A_COLUMN 對映到經典 Java 屬性名 aColumn。 + | ++ true | false + | ++ False + | +
+ localCacheScope + | ++ MyBatis 利用本地快取機制(Local Cache)防止迴圈參考和加速重複的巢狀查詢。 + 預設值為 SESSION,會快取一個會話中執行的所有查詢。 + 若設定值為 STATEMENT,本地快取將僅用於執行語句,對相同 SqlSession 的不同查詢將不會進行快取。 + | ++ SESSION | STATEMENT + | ++ SESSION + | +
+ jdbcTypeForNull + | ++ 當沒有為引數指定特定的 JDBC 型別時,空值的預設 JDBC 型別。 + 某些資料庫驅動需要指定列的 JDBC 型別,多數情況直接用一般型別即可,比如 NULL、VARCHAR 或 OTHER。 + | ++ JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 + | ++ OTHER + | +
+ 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 | false + | ++ false + | +
+ returnInstanceForEmptyRow + | +
+ 當返回行的所有列都是空時,MyBatis預設返回 null 。
+ 當開啟這個設定時,MyBatis會返回一個空實例。
+ 請注意,它也適用於巢狀的結果集(如集合或關聯)。(新增於 3.4.2)
+ |
+ + true | false + | ++ false + | +
+ logPrefix + | ++ 指定 MyBatis 增加到日誌名稱的字首。 + | ++ 任何字串 + | ++ 未設定 + | +
+ logImpl + | ++ 指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢。 + | ++ SLF4J | LOG4J(3.5.9 起廢棄) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING + | ++ 未設定 + | +
+ proxyFactory + | ++ 指定 Mybatis 建立可延遲載入物件所用到的代理工具。 + | ++ CGLIB (3.5.10 起廢棄) | JAVASSIST + | ++ JAVASSIST (MyBatis 3.3 以上) + | +
+ vfsImpl + | ++ 指定 VFS 的實現 + | ++ 自訂 VFS 的實現的類別全限定名,以逗號分隔。 + | ++ 未設定 + | +
+ useActualParamName + | +
+ 允許使用方法簽名中的名稱作為語句引數名稱。
+ 為了使用該特性,你的專案必須採用 Java 8 編譯,並且加上 -parameters 選項。(新增於 3.4.1)
+ |
+ + true | false + | ++ true + | +
+ configurationFactory + | +
+ 指定一個提供 Configuration 實例的類別。
+ 這個被返回的 Configuration 實例用來載入被反序列化物件的延遲載入屬性值。
+ 這個類別必須包含一個簽名為static Configuration getConfiguration() 的方法。(新增於 3.2.3)
+ |
+ + 一個型別別名或完全限定類別名稱。 + | ++ 未設定 + | +
+ shrinkWhitespacesInSql + | ++ 從SQL中刪除多餘的空格字元。請注意,這也會影響SQL中的文字字串。 (新增於 3.5.5) + | ++ true | false + | ++ false + | +
+ defaultSqlProviderType + | +
+ 指定一個擁有 provider 方法的 sql provider 類別 (新增於 3.5.6).
+ 這個類別適用於指定 sql provider 註解上的type (或 value ) 屬性(當這些屬性在註解中被忽略時)。 (e.g. @SelectProvider )
+ |
+ + 型別別名或者全限定名 + | ++ 未設定 + | +
+ nullableOnForEach + | ++ 為 'foreach' 標籤的 'nullable' 屬性指定預設值。(新增於 3.5.9) + | ++ true | false + | ++ false + | +
+ argNameBasedConstructorAutoMapping + | ++ 當應用構造器自動對映時,引數名稱被用來搜尋要對映的列,而不再依賴列的順序。(新增於 3.5.10) + | ++ true | false + | ++ false + | +
+ 一個配置完整的 settings 元素的示例如下: +
+ + +型別別名可為 Java 型別設定一個縮寫名字。 + 它僅用於 XML 配置,意在降低冗餘的全限定類別名稱書寫。例如:
+ +當這樣配置時,Blog
可以用在任何使用 domain.blog.Blog
的地方。
也可以指定一個套件名稱,MyBatis 會在套件名稱下面搜尋需要的 Java Bean,比如: +
+ +每一個在包 domain.blog
中的 Java Bean,在沒有註解的情況下,會使用
+ Bean 的首字母小寫的非限定類別名稱來作為它的別名。
+ 比如 domain.blog.Author
的別名為
+ author
;若有註解,則別名為其註解值。見下面的例子:
下面是一些為常見的 Java 型別內建的型別別名。它們都是不區分大小寫的,注意,為了應對原始型別的命名重複,採取了特殊的命名風格。
++ 別名 + | ++ 對映的型別 + | +
---|---|
+ _byte + | ++ byte + | +
+ _char (since 3.5.10) + | ++ char + | +
+ _character (since 3.5.10) + | ++ char + | +
+ _long + | ++ long + | +
+ _short + | ++ short + | +
+ _int + | ++ int + | +
+ _integer + | ++ int + | +
+ _double + | ++ double + | +
+ _float + | ++ float + | +
+ _boolean + | ++ boolean + | +
+ string + | ++ String + | +
+ byte + | ++ Byte + | +
+ char (since 3.5.10) + | ++ Character + | +
+ character (since 3.5.10) + | ++ Character + | +
+ long + | ++ Long + | +
+ short + | ++ Short + | +
+ int + | ++ Integer + | +
+ integer + | ++ Integer + | +
+ double + | ++ Double + | +
+ float + | ++ Float + | +
+ boolean + | ++ Boolean + | +
+ date + | ++ Date + | +
+ decimal + | ++ BigDecimal + | +
+ bigdecimal + | ++ BigDecimal + | +
+ biginteger + | ++ BigInteger + | +
+ object + | ++ Object + | +
+ date[] + | ++ Date[] + | +
+ decimal[] + | ++ BigDecimal[] + | +
+ bigdecimal[] + | ++ BigDecimal[] + | +
+ biginteger[] + | ++ BigInteger[] + | +
+ object[] + | ++ Object[] + | +
+ map + | ++ Map + | +
+ hashmap + | ++ HashMap + | +
+ list + | ++ List + | +
+ arraylist + | ++ ArrayList + | +
+ collection + | ++ Collection + | +
+ iterator + | ++ Iterator + | +
MyBatis 在設定預處理語句(PreparedStatement)中的引數或從結果集中取出一個值時, + 都會用型別處理器將獲取到的值以合適的方式轉換成 Java 型別。下表描述了一些預設的型別處理器。
++ 提示 + 從 3.4.5 開始,MyBatis 預設支援 JSR-310(日期和時間 API) 。 +
++ 型別處理器 + | ++ Java 型別 + | ++ JDBC 型別 + | +
---|---|---|
+ BooleanTypeHandler
+ |
+
+ java.lang.Boolean , boolean
+ |
+
+ 資料庫相容的 BOOLEAN
+ |
+
+ ByteTypeHandler
+ |
+
+ java.lang.Byte , byte
+ |
+
+ 資料庫相容的 NUMERIC 或 BYTE
+ |
+
+ ShortTypeHandler
+ |
+
+ java.lang.Short , short
+ |
+
+ 資料庫相容的 NUMERIC 或 SMALLINT
+ |
+
+ IntegerTypeHandler
+ |
+
+ java.lang.Integer , int
+ |
+
+ 資料庫相容的 NUMERIC 或 INTEGER
+ |
+
+ LongTypeHandler
+ |
+
+ java.lang.Long , long
+ |
+
+ 資料庫相容的 NUMERIC 或 BIGINT
+ |
+
+ 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
+ 型別,用來儲存列舉的序數值(而不是名稱)。
+ |
+
+ SqlxmlTypeHandler
+ |
+
+ java.lang.String
+ |
+
+ SQLXML
+ |
+
+ InstantTypeHandler
+ |
+
+ java.time.Instant
+ |
+
+ TIMESTAMP
+ |
+
+ LocalDateTimeTypeHandler
+ |
+
+ java.time.LocalDateTime
+ |
+
+ TIMESTAMP
+ |
+
+ LocalDateTypeHandler
+ |
+
+ java.time.LocalDate
+ |
+
+ DATE
+ |
+
+ LocalTimeTypeHandler
+ |
+
+ java.time.LocalTime
+ |
+
+ TIME
+ |
+
+ OffsetDateTimeTypeHandler
+ |
+
+ java.time.OffsetDateTime
+ |
+
+ TIMESTAMP
+ |
+
+ OffsetTimeTypeHandler
+ |
+
+ java.time.OffsetTime
+ |
+
+ TIME
+ |
+
+ ZonedDateTimeTypeHandler
+ |
+
+ java.time.ZonedDateTime
+ |
+
+ TIMESTAMP
+ |
+
+ YearTypeHandler
+ |
+
+ java.time.Year
+ |
+
+ INTEGER
+ |
+
+ MonthTypeHandler
+ |
+
+ java.time.Month
+ |
+
+ INTEGER
+ |
+
+ YearMonthTypeHandler
+ |
+
+ java.time.YearMonth
+ |
+
+ VARCHAR 或 LONGVARCHAR
+ |
+
+ JapaneseDateTypeHandler
+ |
+
+ java.time.chrono.JapaneseDate
+ |
+
+ DATE
+ |
+
+ 你可以重寫已有的型別處理器或建立你自己的型別處理器來處理不支援的或非標準的型別。
+ 具體做法為:實現 org.apache.ibatis.type.TypeHandler
介面,
+ 或繼承一個很便利的類別 org.apache.ibatis.type.BaseTypeHandler
,
+ 並且可以(可選地)將它對映到一個 JDBC 型別。比如:
+
+ 使用上述的型別處理器將會覆蓋已有的處理 Java String 型別的屬性以及 + VARCHAR 型別的引數和結果的型別處理器。 + 要注意 MyBatis 不會透過檢測資料庫元資訊來決定使用哪種型別,所以你必須在引數和結果對映中指明欄位是 VARCHAR 型別, + 以使其能夠繫結到正確的型別處理器上。這是因為 MyBatis 直到語句被執行時才清楚資料型別。 +
++ 透過型別處理器的泛型,MyBatis 可以得知該型別處理器處理的 Java 型別,不過這種行為可以透過兩種方法改變: +
+javaType
屬性(比如:javaType="String"
);
+ @MappedTypes
註解指定與其關聯的 Java 型別列表。
+ 如果在 javaType
屬性中也同時指定,則註解上的配置將被忽略。
+ 可以透過兩種方式來指定關聯的 JDBC 型別:
+jdbcType
+ 屬性(比如:jdbcType="VARCHAR"
);
+ @MappedJdbcTypes
+ 註解指定與其關聯的 JDBC 型別列表。
+ 如果在 jdbcType
屬性中也同時指定,則註解上的配置將被忽略。
+
+ 當在 ResultMap
中決定使用哪種型別處理器時,此時 Java
+ 型別是已知的(從結果型別中獲得),但是 JDBC 型別是未知的。
+ 因此 Mybatis 使用 javaType=[Java 型別], jdbcType=null
+ 的組合來選擇一個型別處理器。
+ 這意味著使用 @MappedJdbcTypes
+ 註解可以限制型別處理器的作用範圍,並且可以確保,除非顯式地設定,否則型別處理器在
+ ResultMap
中將不會生效。
+ 如果希望能在 ResultMap
中隱式地使用型別處理器,那麼設定
+ @MappedJdbcTypes
註解的 includeNullJdbcType=true
即可。
+ 然而從 Mybatis 3.4.0 開始,如果某個 Java 型別只有一個註冊的型別處理器,即使沒有設定 includeNullJdbcType=true
,那麼這個型別處理器也會是 ResultMap
使用 Java
+ 型別時的預設處理器。
+
最後,可以讓 MyBatis 幫你查詢型別處理器:
+ + +注意在使用自動發現功能的時候,只能透過註解方式來指定 JDBC 的型別。
+你可以建立能夠處理多個類別的泛型型別處理器。為了使用泛型型別處理器, + 需要增加一個接受該類別的 class 作為引數的構造器,這樣 MyBatis + + 會在構造一個型別處理器實例的時候傳入一個具體的類別。
+ + +EnumTypeHandler
和 EnumOrdinalTypeHandler
+ 都是泛型型別處理器,我們將會在接下來的部分詳細探討。
若想對映列舉型別 Enum
,則需要從 EnumTypeHandler
+ 或者 EnumOrdinalTypeHandler
中選擇一個來使用。
比如說我們想儲存取近似值時用到的舍入模式。預設情況下,MyBatis 會利用
+ EnumTypeHandler
來把 Enum
值轉換成對應的名字。
EnumTypeHandler
+ 在某種意義上來說是比較特別的,其它的處理器只針對某個特定的類別,而它不同,它會處理任意繼承了
+ Enum
的類別。
+
+ 不過,我們可能不想儲存名字,相反我們的 DBA 會堅持使用整形值程式碼。那也一樣簡單:在配置檔案中把
+ EnumOrdinalTypeHandler
加到 typeHandlers
中即可,
+ 這樣每個 RoundingMode
將透過他們的序數值來對映成對應的整形數值。
+
但要是你想在一個地方將 Enum
對映成字串,在另外一個地方對映成整形值呢?
+ 自動對映器(auto-mapper)會自動地選用 EnumOrdinalTypeHandler
來處理列舉型別,
+ 所以如果我們想用普通的 EnumTypeHandler
,就必須要顯式地為那些 SQL 語句設定要使用的型別處理器。
+
(下一節才開始介紹對映器檔案,如果你是首次閱讀該文件,你可能需要先跳過這裡,過會再來看。)
+ +注意,這裡的 select 語句必須指定 resultMap
而不是 resultType
。
每次 MyBatis 建立結果物件的新實例時,它都會使用一個物件工廠(ObjectFactory)實例來完成實例化工作。 + 預設的物件工廠需要做的僅僅是實例化目標類別,要麼透過預設無參構造方法,要麼透過存在的引數對映來呼叫帶有引數的構造方法。 + 如果想覆蓋物件工廠的預設行為,可以透過建立自己的物件工廠來實現。比如:
+ + + +ObjectFactory 介面很簡單,它包含兩個建立實例用的方法,一個是處理預設無參構造方法的,另外一個是處理帶引數的構造方法的。 + 另外,setProperties 方法可以被用來配置 ObjectFactory,在初始化你的 ObjectFactory 實例後, + objectFactory 元素體中定義的屬性會被傳遞給 setProperties 方法。
+ ++ MyBatis 允許你在對映語句執行過程中的某一點進行攔截呼叫。預設情況下,MyBatis 允許使用外掛來攔截的方法呼叫包括: +
+這些類別中方法的細節可以透過檢視每個方法的簽名來發現,或者直接檢視 MyBatis 發行套件中的原始碼。 + 如果你想做的不僅僅是監控方法的呼叫,那麼你最好相當瞭解要重寫的方法的行為。 + 因為在試圖修改或重寫已有方法的行為時,很可能會破壞 MyBatis 的核心模組。 + 這些都是更底層的類別和方法,所以使用外掛的時候要特別當心。
+透過 MyBatis 提供的強大機制,使用外掛是非常簡單的,只需實現 Interceptor 介面,並指定想要攔截的方法簽名即可。
+ + + + +上面的外掛將會攔截在 Executor 實例中所有的 “update” 方法呼叫, + 這裡的 Executor 是負責執行底層對映語句的內部物件。
+提示 + 覆蓋配置類別 +
+除了用外掛來修改 MyBatis 核心行為以外,還可以透過完全覆蓋配置類別來達到目的。只需繼承配置類別後覆蓋其中的某個方法,再把它傳遞到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,這可能會極大影響 MyBatis 的行為,務請慎之又慎。
++ MyBatis 可以配置成適應多種環境,這種機制有助於將 SQL 對映應用於多種資料庫之中, + 現實情況下有多種理由需要這麼做。例如,開發、測試和生產環境需要有不同的配置;或者想在具有相同 Schema + 的多個生產資料庫中使用相同的 SQL 對映。還有許多類似的使用場景。
++ 不過要記住:儘管可以配置多個環境,但每個 SqlSessionFactory + 實例只能選擇一種環境。 +
++ 所以,如果你想連線兩個資料庫,就需要建立兩個 SqlSessionFactory + 實例,每個資料庫對應一個。而如果是三個資料庫,就需要三個實例,依此類別推,記起來很簡單: +
++ 為了指定建立哪種環境,只要將它作為可選的引數傳遞給 + SqlSessionFactoryBuilder 即可。可以接受環境配置的兩個方法簽名是: +
+ + + +如果忽略了環境引數,那麼將會載入預設環境,如下所示: +
+ + + +environments 元素定義瞭如何配置環境。 +
+ + ++ 注意一些關鍵點: +
++ 預設環境和環境 ID 顧名思義。 + 環境可以隨意命名,但務必保證預設的環境 ID 要匹配其中一個環境 ID。 +
++ 事務管理器(transactionManager) +
+在 MyBatis 中有兩種型別的事務管理器(也就是 type="[JDBC|MANAGED]"):
++ 提示 + 如果你正在使用 Spring + MyBatis,則沒有必要配置事務管理器,因為 + Spring 模組會使用自帶的管理器來覆蓋前面的配置。 +
++ 這兩種事務管理器型別都不需要設定任何屬性。它們其實是型別別名,換句話說,你可以用 + TransactionFactory 介面實現類別的全限定名或型別別名代替它們。 +
+ +在事務管理器實例化後,所有在 XML 中配置的屬性將會被傳遞給 setProperties() + 方法。你的實現還需要建立一個 Transaction 介面的實現類別,這個介面也很簡單:
+ +使用這兩個介面,你可以完全自訂 MyBatis 對事務的處理。
++ 資料來源(dataSource) +
+dataSource 元素使用標準的 JDBC 資料來源介面來配置 JDBC 連線物件的資源。
+有三種內建的資料來源型別(也就是 type="[UNPOOLED|POOLED|JNDI]"):
++ UNPOOLED– 這個資料來源的實現會每次請求時開啟和關閉連線。雖然有點慢,但對那些資料庫連線可用性要求不高的簡單應用程式來說,是一個很好的選擇。 + 效能表現則依賴於使用的資料庫,對某些資料庫來說,使用連線池並不重要,這個配置就很適合這種情形。UNPOOLED 型別的資料來源僅僅需要配置以下 5 種屬性:
+driver
– 這是 JDBC 驅動的 Java 類別全限定名(並不是 JDBC 驅動中可能包含的資料來源類別)。
+ url
– 這是資料庫的 JDBC URL 地址。
+ username
– 登入資料庫的使用者名稱。
+ password
– 登入資料庫的密碼。
+ defaultTransactionIsolationLevel
– 預設的連線事務隔離級別。
+ defaultNetworkTimeout
– 等待資料庫操作完成的預設網路超時時間(單位:毫秒)。檢視 java.sql.Connection#setNetworkTimeout()
的 API 文件以獲取更多資訊。
+ 作為可選項,你也可以傳遞屬性給資料庫驅動。只需在屬性名加上“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))。這是個可選屬性,如果忽略,那麼將會直接從 InitialContext 中尋找 data_source 屬性。
+ data_source
– 這是參考資料來源實例位置的上下文路徑。提供了 initial_context 配置時會在其返回的上下文中進行查詢,沒有提供時則直接在 InitialContext 中查詢。
+ 和其他資料來源配置類似,可以透過新增字首“env.”直接把屬性傳遞給 InitialContext。比如: +
+env.encoding=UTF8
這就會在 InitialContext 實例化時往它的構造方法傳遞值為 UTF8
的 encoding
屬性。
+
+ 你可以透過實現介面 org.apache.ibatis.datasource.DataSourceFactory
來使用第三方資料來源實現:
+
+ org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
可被用作父類別來建構新的資料來源介面卡,比如下面這段插入 C3P0 資料來源所必需的程式碼:
+
為了令其工作,記得在配置檔案中為每個希望 MyBatis 呼叫的 setter 方法增加對應的屬性。 + 下面是一個可以連線至 PostgreSQL 資料庫的例子:
+ + + +
+ MyBatis 可以根據不同的資料庫廠商執行不同的語句,這種多廠商的支援是基於對映語句中的 databaseId
屬性。
+ MyBatis 會載入帶有匹配當前資料庫 databaseId
屬性和所有不帶 databaseId
屬性的語句。
+ 如果同時找到帶有 databaseId
和不帶 databaseId
的相同語句,則後者會被捨棄。
+ 為支援多廠商特性,只要像下面這樣在 mybatis-config.xml 檔案中加入 databaseIdProvider
即可:
+
+ databaseIdProvider 對應的 DB_VENDOR 實現會將 databaseId 設定為
+ DatabaseMetaData#getDatabaseProductName()
返回的字串。
+ 由於通常情況下這些字串都非常長,而且相同產品的不同版本會返回不同的值,你可能想透過設定屬性別名來使其變短:
+
+ 在提供了屬性別名時,databaseIdProvider 的 DB_VENDOR 實現會將 databaseId
+ 設定為資料庫產品名與屬性中的名稱第一個相匹配的值,如果沒有匹配的屬性,將會設定為 “null”。
+ 在這個例子中,如果 getDatabaseProductName()
+ 返回“Oracle (DataDirect)”,databaseId 將被設定為“oracle”。
+
+ 你可以透過實現介面 org.apache.ibatis.mapping.DatabaseIdProvider
+ 並在 mybatis-config.xml 中註冊來建構自己的 DatabaseIdProvider:
+
+ 既然 MyBatis 的行為已經由上述元素配置完了,我們現在就要來定義 SQL 對映語句了。
+ 但首先,我們需要告訴 MyBatis 到哪裡去找到這些語句。
+ 在自動查詢資源方面,Java 並沒有提供一個很好的解決方案,所以最好的辦法是直接告訴
+ MyBatis 到哪裡去找對映檔案。
+ 你可以使用相對於類別路徑的資源參考,或完全限定資源定位符(包括 file:///
形式的 URL),或類別名稱和套件名稱等。例如:
+
這些配置會告訴 MyBatis 去哪裡找對映檔案,剩下的細節就應該是每個 SQL + 對映檔案了,也就是接下來我們要討論的。
+動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記新增必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
+使用動態 SQL 並非一件易事,但藉助可用於任何 SQL 對映語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
+如果你之前用過 JSTL 或任何基於類別 XML 語言的文字處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間瞭解大量的元素。藉助功能強大的基於 OGNL 的表示式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。
+使用動態 SQL 最常見情景是根據條件包含 where 子句的一部分。比如:
+ +這條語句提供了可選的查詢文字功能。如果不傳入 “title”,那麼所有處於 “ACTIVE” 狀態的 BLOG 都會返回;如果傳入了 “title” 引數,那麼就會對 “title” 一列進行模糊查詢並返回對應的 BLOG 結果(細心的讀者可能會發現,“title” 的引數值需要包含查詢掩碼或萬用字元字元)。
+如果希望透過 “title” 和 “author” 兩個引數進行可選搜尋該怎麼辦呢?首先,我想先將語句名稱修改成更名副其實的名稱;接下來,只需要加入另一個條件即可。
+ +有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。
+還是上面的例子,但是策略變為:傳入了 “title” 就按 “title” 查詢,傳入了 “author” 就按 “author” 查詢的情形。若兩者都沒有傳入,就返回標記為 featured 的 BLOG(這可能是管理員認為,與其返回大量的無意義隨機 Blog,還不如返回一些由管理員精選的 Blog)。
+ +前面幾個例子已經方便地解決了一個臭名昭著的動態 SQL 問題。現在回到之前的 “if” 示例,這次我們將 “state = ‘ACTIVE’” 設定成動態條件,看看會發生什麼。
+ +如果沒有匹配的條件會怎麼樣?最終這條 SQL 會變成這樣:
+ +這會導致查詢失敗。如果匹配的只是第二個條件又會怎樣?這條 SQL 會是這樣: +
+ +這個查詢也會失敗。這個問題不能簡單地用條件元素來解決。這個問題是如此的難以解決,以至於解決過的人不會再想碰到這種問題。
+MyBatis 有一個簡單且適合大多數場景的解決辦法。而在其他場景中,可以對其進行自訂以符合需求。而這,只需要一處簡單的改動:
+ +where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。
+如果 where 元素與你期望的不太一樣,你也可以透過自訂 trim 元素來訂製 where 元素的功能。比如,和 where 元素等價的自訂 trim 元素為:
+ +prefixOverrides 屬性會忽略透過管道符分隔的文字序列(注意此例中的空格是必要的)。上述例子會移除所有 prefixOverrides 屬性中指定的內容,並且插入 prefix 屬性中指定的內容。
+用於動態更新語句的類似解決方案叫做 set。set 元素可以用於動態包含需要更新的列,忽略其它不更新的列。比如:
+ +這個例子中,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。
+或者,你可以透過使用trim元素來達到同樣的效果:
+ +注意,我們覆蓋了字尾值設定,並且自訂了字首值。
+動態 SQL 的另一個常見使用場景是對集合進行遍歷(尤其是在建構 IN 條件語句的時候)。比如:
+ +foreach 元素的功能非常強大,它允許你指定一個集合,宣告可以在元素體內使用的集合項(item)和索引(index)變數。它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符。這個元素也不會錯誤地新增多餘的分隔符,看它多智慧!
+提示 你可以將任何可迭代物件(如 List、Set 等)、Map 物件或者陣列物件作為集合引數傳遞給 foreach。當使用可迭代物件或者陣列時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 物件(或者 Map.Entry 物件的集合)時,index 是鍵,item 是值。
+至此,我們已經完成了與 XML 配置及對映檔案相關的討論。下一章將詳細探討 Java API,以便你能充分利用已經建立的對映配置。
+要在帶註解的對映器介面類別中使用動態 SQL,可以使用 script 元素。比如:
+ +bind
元素允許你在 OGNL 表示式以外建立一個變數,並將其繫結到當前的上下文。比如:
如果配置了 databaseIdProvider,你就可以在動態程式碼中使用名為 “_databaseId” 的變數來為不同的資料庫建構特定的語句。比如下面的例子:
+ +MyBatis 從 3.2 版本開始支援插入指令碼語言,這允許你插入一種語言驅動,並基於這種語言來編寫動態 SQL 查詢語句。
+可以透過實現以下介面來插入一種語言:
+ +實現自訂語言驅動後,你就可以在 mybatis-config.xml 檔案中將它設定為預設語言:
+ +或者,你也可以使用 lang
屬性為特定的語句指定語言:
+
或者,在你的 mapper 介面上新增 @Lang
註解:
提示 可以使用 Apache Velocity 作為動態語言,更多細節請參考 MyBatis-Velocity 專案。
+你前面看到的所有 xml 標籤都由預設 MyBatis 語言提供,而它由語言驅動 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver
(別名為 xml
)所提供。
要使用 MyBatis, 只需將 + mybatis-x.x.x.jar + 檔案置於類別路徑(classpath)中即可。
+如果使用 Maven 來建構專案,則需將下面的依賴程式碼置於 pom.xml 檔案中:
+ ++ 每個基於 MyBatis 的應用都是以一個 SqlSessionFactory + 的實例為核心的。SqlSessionFactory 的實例可以透過 SqlSessionFactoryBuilder + 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置檔案或一個預先配置的 + Configuration 實例來構建出 SqlSessionFactory 實例。 +
+ ++ 從 XML 檔案中建構 SqlSessionFactory + 的實例非常簡單,建議使用類別路徑下的資原始檔進行配置。 + 但也可以使用任意的輸入流(InputStream)實例,比如用檔案路徑字串或 + file:// URL 構造的輸入流。MyBatis 包含一個名叫 Resources + 的工具類別,它包含一些實用方法,使得從類別路徑或其它位置載入資原始檔更加容易。 +
+ + ++ XML 配置檔案中包含了對 MyBatis + 系統的核心設定,包括獲取資料庫連線實例的資料來源(DataSource)以及決定事務作用域和控制方式的事務管理器(TransactionManager)。後面會再探討 + XML 配置檔案的詳細內容,這裡先給出一個簡單的示例: +
+ + ++ 當然,還有很多可以在 XML 檔案中配置的選項,上面的示例僅羅列了最關鍵的部分。 + 注意 XML 頭部的宣告,它用來驗證 XML 文件的正確性。environment + 元素體中包含了事務管理和連線池的配置。mappers 元素則包含了一組對映器(mapper),這些對映器的 + XML 對映檔案包含了 SQL 程式碼和對映定義資訊。 +
+ ++ 如果你更願意直接從 Java 程式碼而不是 XML 檔案中建立配置,或者想要建立你自己的配置建構器,MyBatis + 也提供了完整的配置類別,提供了所有與 XML 檔案等價的配置項。 +
+ + + ++ 注意該例中,configuration 添加了一個對映器類別(mapper class)。對映器類別是 + Java 類別,它們包含 SQL 對映註解從而避免依賴 XML 對映檔案。不過,由於 + Java 註解的一些限制以及某些 MyBatis 對映的複雜性,要使用大多數高階對映(比如:巢狀聯合對映),仍然需要使用 XML + 對映檔案進行對映。有鑑於此,如果存在一個同名 + XML 對映檔案,MyBatis 會自動查詢並載入它(在這個例子中,基於類別路徑和 BlogMapper.class 的類別名稱,會載入 + BlogMapper.xml)。具體細節稍後討論。 +
++ 既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的實例。SqlSession + 提供了在資料庫執行 SQL 命令所需的所有方法。你可以透過 + SqlSession 實例來直接執行已對映的 SQL 語句。例如: +
+ + ++ 誠然,這種方式能夠正常工作,對使用舊版本 MyBatis + 的使用者來說也比較熟悉。但現在有了一種更簡潔的方式——使用和指定語句的引數和返回值相匹配的介面(比如 + BlogMapper.class),現在你的程式碼不僅更清晰,更加型別安全,還不用擔心可能出錯的字串字面值以及強制型別轉換。
+ +例如:
+ + +現在我們來探究一下這段程式碼究竟做了些什麼。
++ 現在你可能很想知道 SqlSession 和 Mapper 到底具體執行了些什麼操作,但 SQL + 語句對映是個相當廣泛的話題,可能會佔去文件的大部分篇幅。 + 但為了讓你能夠了解個大概,這裡先給出幾個例子。 +
++ 在上面提到的例子中,一個語句既可以透過 XML 定義,也可以透過註解定義。我們先看看 + XML 定義語句的方式,事實上 MyBatis 提供的所有特性都可以利用基於 XML 的對映語言來實現,這使得 + MyBatis 在過去的數年間得以流行。如果你用過舊版本的 MyBatis,你應該對這個概念比較熟悉。 + 但相比於之前的版本,新版本改進了許多 XML 的配置,後面我們會提到這些改進。這裡給出一個基於 XML + 對映語句的示例,它應該可以滿足上個示例中 SqlSession 的呼叫。 +
+ + + ++ 為了這個簡單的例子,我們似乎寫了不少配置,但其實並不多。在一個 + XML 對映檔案中,可以定義無數個對映語句,這樣一來,XML + 頭部和文件型別宣告部分就顯得微不足道了。文件的其它部分很直白,容易理解。 + 它在名稱空間 “org.mybatis.example.BlogMapper” 中定義了一個名為 “selectBlog” + 的對映語句,這樣你就可以用全限定名 + “org.mybatis.example.BlogMapper.selectBlog” 來呼叫對映語句了,就像上面例子中那樣: +
+ + + ++ 你可能會注意到,這種方式和用全限定名呼叫 Java + 物件的方法類似。這樣,該命名就可以直接對映到在名稱空間中同名的對映器類別,並將已對映的 + select 語句匹配到對應名稱、引數和返回型別的方法。因此你就可以像上面那樣,不費吹灰之力地在對應的對映器介面呼叫方法,就像下面這樣: +
+ + + ++ 第二種方法有很多優勢,首先它不依賴於字串字面值,會更安全一點;其次,如果你的 + IDE 有程式碼自動完成功能,那麼程式碼自動完成可以幫你快速選擇到對映好的 SQL 語句。 +
+ +提示 + 對名稱空間的一點補充
++ 在之前版本的 MyBatis + 中,名稱空間(Namespaces)的作用並不大,是可選的。 + 但現在,隨著名稱空間越發重要,你必須指定名稱空間。 +
++ 名稱空間的作用有兩個,一個是利用更長的全限定名來將不同的語句隔離開來,同時也實現了你上面見到的介面繫結。就算你覺得暫時用不到介面繫結,你也應該遵循這裡的規定,以防哪天你改變了主意。 + 長遠來看,只要將名稱空間置於合適的 Java + 包名稱空間之中,你的程式碼會變得更加整潔,也有利於你更方便地使用 MyBatis。 +
++ 命名解析:為了減少輸入量,MyBatis + 對所有具有名稱的配置元素(包括語句,結果對映,快取等)使用瞭如下的命名解析規則。 +
+ ++ 對於像 BlogMapper 這樣的對映器類別來說,還有另一種方法來完成語句對映。 + 它們對映的語句可以不用 XML 來配置,而可以使用 Java 註解來配置。比如,上面的 XML + 示例可以被替換成如下的配置: +
+ ++ 使用註解來對映簡單語句會使程式碼顯得更加簡潔,但對於稍微複雜一點的語句,Java + 註解不僅力不從心,還會讓本就複雜的 SQL 語句更加混亂不堪。 + 因此,如果你需要做一些很複雜的操作,最好用 XML 來對映語句。 +
++ 選擇何種方式來配置對映,以及是否應該要統一對映語句定義的形式,完全取決於你和你的團隊。 + 換句話說,永遠不要拘泥於一種方式,你可以很輕鬆地在基於註解和 XML + 的語句對映方式間自由移植和切換。 +
+理解我們之前討論過的不同作用域和生命週期類別是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題。
++ 提示 + 物件生命週期和依賴注入框架 +
++ 依賴注入框架可以建立執行緒安全的、基於事務的 SqlSession + 和對映器,並將它們直接注入到你的 bean 中,因此可以直接忽略它們的生命週期。 + 如果對如何透過依賴注入框架使用 MyBatis 感興趣,可以研究一下 MyBatis-Spring + 或 MyBatis-Guice 兩個子專案。
++ 這個類別可以被實例化、使用和丟棄,一旦建立了 SqlSessionFactory,就不再需要它了。 + 因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是區域性方法變數)。 + 你可以重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory + 實例,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。 +
++ SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個實例。 + 使用 SqlSessionFactory 的最佳實踐是在應用執行期間不要重複建立多次,多次重建 + SqlSessionFactory 被視為一種程式碼“壞習慣”。因此 + SqlSessionFactory 的最佳作用域是應用作用域。 + 有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。 +
++ 每個執行緒都應該有它自己的 SqlSession 實例。SqlSession + 的實例不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。 + 絕對不能將 SqlSession 實例的參考放在一個類別的靜態域,甚至一個類別的實例變數也不行。 + 也絕不能將 SqlSession 實例的參考放在任何型別的託管作用域中,比如 Servlet 框架中的 HttpSession。 + 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。 + 換句話說,每次收到 HTTP 請求,就可以開啟一個 SqlSession,返回一個響應後,就關閉它。 + 這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。 + 下面的示例就是一個確保 SqlSession 關閉的標準模式: +
+ +在所有程式碼中都遵循這種使用模式,可以保證所有資料庫資源都能被正確地關閉。
+ ++ 對映器是一些繫結對映語句的介面。對映器介面的實例是從 + SqlSession 中獲得的。雖然從技術層面上來講,任何對映器實例的最大作用域與請求它們的 + SqlSession 相同。但方法作用域才是對映器實例的最合適的作用域。 + 也就是說,對映器實例應該在呼叫它們的方法中被獲取,使用完畢之後即可丟棄。 + 對映器實例並不需要被顯式地關閉。儘管在整個請求作用域保留對映器實例不會有什麼問題,但是你很快會發現,在這個作用域上管理太多像 + SqlSession 的資源會讓你忙不過來。 + 因此,最好將對映器放在方法作用域內。就像下面的例子一樣: +
+ + +MyBatis 是一款優秀的持久層框架,它支援自訂 SQL、儲存過程以及高階對映。MyBatis + 免除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作。MyBatis + 可以透過簡單的 XML 或註解來配置和對映原始型別、介面和 Java POJO(Plain Old + Java Objects,普通老式 Java 物件)為資料庫中的記錄。
+如果你發現文件有任何的遺漏,或缺少某一個功能點的說明,最好的解決辦法是先自己學習,然後為遺漏的部份補上相應的文件。
+該文件 xdoc 格式的原始碼檔案可透過專案的 + Git 程式碼函式庫來獲取。fork該原始碼函式庫,作出更新,並提交 Pull Request 吧。
+還有其他像你一樣的人都需要閱讀這份文件,而你,就是這份文件最好的作者。 +
+您可以閱讀 MyBatis 文件的其他語言版本:
+ +想用你的母語來了解 MyBatis 嗎?那就將文件翻譯成你的母語並提供給我們吧!
+既然你已經知道如何配置 MyBatis 以及如何建立對映,是時候來嚐點甜頭了。MyBatis 的 Java API 就是這個甜頭。稍後你將看到,和 JDBC 相比,MyBatis + 大幅簡化你的程式碼併力圖保持其簡潔、容易理解和維護。為了使得 SQL 對映更加優秀,MyBatis 3 引入了許多重要的改進。
+ +在我們深入 Java API 之前,理解關於目錄結構的最佳實踐是很重要的。MyBatis 非常靈活,你可以隨意安排你的檔案。但和其它框架一樣,目錄結構有一種最佳實踐。 +
++ 讓我們看一下典型的應用目錄結構: +
+/my_application + /bin + /devlib + /lib <-- MyBatis *.jar 檔案在這裡。 + /src + /org/myapp/ + /action + /data <-- MyBatis 配置檔案在這裡,包括對映器類別、XML 配置、XML 對映檔案。 + /mybatis-config.xml + /BlogMapper.java + /BlogMapper.xml + /model + /service + /view + /properties <-- 在 XML 配置中出現的屬性值在這裡。 + /test + /org/myapp/ + /action + /data + /model + /service + /view + /properties + /web + /WEB-INF + /web.xml+
當然,這是推薦的目錄結構,並非強制要求,但使用一個通用的目錄結構將更有利於大家溝通。
+本章接下來的示例將假定你遵循這種目錄結構。
+使用 MyBatis 的主要 Java 介面就是 SqlSession。你可以透過這個介面來執行命令,獲取對映器實例和管理事務。在介紹 SqlSession 介面之前,我們先來了解如何獲取一個 SqlSession 實例。SqlSessions 是由 SqlSessionFactory 實例建立的。SqlSessionFactory 物件包含建立 SqlSession 實例的各種方法。而 SqlSessionFactory 本身是由 SqlSessionFactoryBuilder 建立的,它可以從 XML、註解或 Java 配置程式碼來建立 SqlSessionFactory。
++ 提示 當 Mybatis 與一些依賴注入框架(如 Spring 或者 Guice)搭配使用時,SqlSession 將被依賴注入框架建立並注入,所以你不需要使用 SqlSessionFactoryBuilder 或者 SqlSessionFactory,可以直接閱讀 SqlSession 這一節。請參考 Mybatis-Spring 或者 Mybatis-Guice 手冊以瞭解更多資訊。
+SqlSessionFactoryBuilder 有五個 build() 方法,每一種都允許你從不同的資源中建立一個 SqlSessionFactory 實例。
+ +第一種方法是最常用的,它接受一個指向 XML 檔案(也就是之前討論的 mybatis-config.xml 檔案)的 InputStream 實例。可選的引數是 environment 和 properties。environment 決定載入哪種環境,包括資料來源和事務管理器。比如:
+ + +如果你呼叫了帶 environment 引數的 build 方法,那麼 MyBatis 將使用該環境對應的配置。當然,如果你指定了一個無效的環境,會收到錯誤。如果你呼叫了不帶 environment 引數的 build 方法,那麼就會使用預設的環境配置(在上面的示例中,透過 default="development" 指定了預設環境)。
+ +如果你呼叫了接受 properties 實例的方法,那麼 MyBatis 就會載入這些屬性,並在配置中提供使用。絕大多數場合下,可以用 ${propName} 形式參考這些配置值。
+回想一下,在 mybatis-config.xml 中,可以參考屬性值,也可以直接指定屬性值。因此,理解屬性的優先順序是很重要的。在之前的文件中,我們已經介紹過了相關內容,但為了方便查閱,這裡再重新介紹一下:
+如果一個屬性存在於下面的多個位置,那麼 MyBatis 將按照以下順序來載入它們:
+因此,透過方法引數傳遞的屬性的優先順序最高,resource 或 url 指定的屬性優先順序中等,在 properties 元素體中指定的屬性優先順序最低。 +
+總結一下,前四個方法很大程度上是相同的,但提供了不同的覆蓋選項,允許你可選地指定 environment 和/或 properties。以下給出一個從 mybatis-config.xml 檔案建立 SqlSessionFactory 的示例:
+ + +注意,這裡我們使用了 Resources 工具類別,這個類別在 org.apache.ibatis.io 套件中。Resources 類別正如其名,會幫助你從類別路徑下、檔案系統或一個 web URL 中載入資原始檔。在略讀該類別的原始碼或用 IDE 檢視該類別資訊後,你會發現一整套相當實用的方法。這裡給出一個簡表:
+ +最後一個 build 方法接受一個 Configuration 實例。Configuration 類別包含了對一個 SqlSessionFactory 實例你可能關心的所有內容。在檢查配置時,Configuration 類別很有用,它允許你查詢和操縱 SQL 對映(但當應用開始接收請求時不推薦使用)。你之前學習過的所有配置開關都存在於 Configuration 類別,只不過它們是以 Java API 形式暴露的。以下是一個簡單的示例,示範如何手動配置 Configuration 實例,然後將它傳遞給 build() 方法來建立 SqlSessionFactory。
+ +現在你就獲得一個可以用來建立 SqlSession 實例的 SqlSessionFactory 了。
+ +SqlSessionFactory 有六個方法建立 SqlSession 實例。通常來說,當你選擇其中一個方法時,你需要考慮以下幾點:
+基於以上需求,有下列已過載的多個 openSession() 方法供使用。
+ +預設的 openSession() 方法沒有引數,它會建立具備如下特性的 SqlSession:
+相信你已經能從方法簽名中知道這些方法的區別。向 autoCommit
可選引數傳遞 true
值即可開啟自動提交功能。若要使用自己的 Connection
實例,傳遞一個 Connection
實例給 connection
引數即可。注意,我們沒有提供同時設定 Connection
和 autoCommit
的方法,這是因為 MyBatis 會依據傳入的 Connection 來決定是否啟用 autoCommit。對於事務隔離級別,MyBatis 使用了一個 Java 列舉包裝器來表示,稱為 TransactionIsolationLevel
,事務隔離級別支援 JDBC 的五個隔離級別(NONE
、READ_UNCOMMITTED
、READ_COMMITTED
、REPEATABLE_READ
和 SERIALIZABLE
),並且與預期的行為一致。
你可能對 ExecutorType
引數感到陌生。這個列舉型別定義了三個值:
+
ExecutorType.SIMPLE
:該型別的執行器沒有特別的行為。它為每個語句的執行建立一個新的預處理語句。ExecutorType.REUSE
:該型別的執行器會複用預處理語句。ExecutorType.BATCH
:該型別的執行器會批量執行所有更新語句,如果 SELECT 在多個更新中間執行,將在必要時將多條更新語句分隔開來,以方便理解。提示 在 SqlSessionFactory 中還有一個方法我們沒有提及,就是 getConfiguration()。這個方法會返回一個 Configuration 實例,你可以在執行時使用它來檢查 MyBatis 的配置。 +
+提示 如果你使用過 MyBatis 的舊版本,可能還記得 session、事務和批量操作是相互獨立的。在新版本中則不是這樣。上述三者都包含在 session 作用域內。你不必分別處理事務或批量操作就能得到想要的全部效果。 +
+ +正如之前所提到的,SqlSession 在 MyBatis 中是非常強大的一個類別。它包含了所有執行語句、提交或回滾事務以及獲取對映器實例的方法。
+SqlSession 類別的方法超過了 20 個,為了方便理解,我們將它們分成幾種組別。
+ +這些方法被用來執行定義在 SQL 對映 XML 檔案中的 SELECT、INSERT、UPDATE 和 DELETE 語句。你可以透過名字快速瞭解它們的作用,每一方法都接受語句的 ID 以及引數物件,引數可以是原始型別(支援自動裝箱或包裝類別)、JavaBean、POJO 或 Map。
+ +selectOne 和 selectList 的不同僅僅是 selectOne 必須返回一個物件或 null 值。如果返回值多於一個,就會丟擲異常。如果你不知道返回物件會有多少,請使用 selectList。如果需要檢視某個物件是否存在,最好的辦法是查詢一個 count 值(0 或 1)。selectMap 稍微特殊一點,它會將返回物件的其中一個屬性作為 key 值,將物件作為 value 值,從而將多個結果集轉為 Map 型別值。由於並不是所有語句都需要引數,所以這些方法都具有一個不需要引數的過載形式。 +
+遊標(Cursor)與列表(List)返回的結果相同,不同的是,遊標藉助迭代器實現了資料的延遲載入。
+ +insert、update 以及 delete 方法返回的值表示受該語句影響的行數。
+ +最後,還有 select 方法的三個高階版本,它們允許你限制返回行數的範圍,或是提供自訂結果處理邏輯,通常在資料集非常龐大的情形下使用。 +
+ +RowBounds 引數會告訴 MyBatis 略過指定數量的記錄,並限制返回結果的數量。RowBounds 類別的 offset 和 limit 值只有在建構函式時才能傳入,其它時候是不能修改的。
+ + +資料庫驅動決定了略過記錄時的查詢效率。為了獲得最佳的效能,建議將 ResultSet 型別設定為 SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE(換句話說:不要使用 FORWARD_ONLY)。
+ResultHandler 引數允許自訂每行結果的處理過程。你可以將它新增到 List 中、建立 Map 和 Set,甚至丟棄每個返回值,只保留計算後的統計結果。你可以使用 ResultHandler 做很多事,這其實就是 MyBatis 建構 結果列表的內部實現辦法。
+從版本 3.4.6 開始,ResultHandler
會在儲存過程的 REFCURSOR 輸出引數中傳遞使用的 CALLABLE
語句。
它的介面很簡單:
+ + +ResultContext 引數允許你訪問結果物件和當前已被建立的物件數目,另外還提供了一個返回值為 Boolean 的 stop 方法,你可以使用此 stop 方法來停止 MyBatis 載入更多的結果。
+使用 ResultHandler 的時候需要注意以下兩個限制:
+當你將 ExecutorType
設定為 ExecutorType.BATCH
時,可以使用這個方法清除(執行)快取在 JDBC 驅動類別中的批量更新語句。
+ 有四個方法用來控制事務作用域。當然,如果你已經設定了自動提交或你使用了外部事務管理器,這些方法就沒什麼作用了。然而,如果你正在使用由 Connection 實例控制的 JDBC 事務管理器,那麼這四個方法就會派上用場: +
+ +預設情況下 MyBatis 不會自動提交事務,除非它偵測到呼叫了插入、更新或刪除方法改變了資料庫。如果你沒有使用這些方法提交修改,那麼你可以在 commit 和 rollback 方法引數中傳入 true 值,來保證事務被正常提交(注意,在自動提交模式或者使用了外部事務管理器的情況下,設定 force 值對 session 無效)。大部分情況下你無需呼叫 rollback(),因為 MyBatis 會在你沒有呼叫 commit 時替你完成回滾操作。不過,當你要在一個可能多次提交或回滾的 session 中詳細控制事務,回滾操作就派上用場了。
+提示 MyBatis-Spring 和 MyBatis-Guice 提供了宣告式事務處理,所以如果你在使用 Mybatis 的同時使用了 Spring 或者 Guice,請參考它們的手冊以獲取更多的內容。
+ +Mybatis 使用到了兩種快取:本地快取(local cache)和二級快取(second level cache)。
+每當一個新 session 被建立,MyBatis 就會建立一個與之相關聯的本地快取。任何在 session 執行過的查詢結果都會被儲存在本地快取中,所以,當再次執行引數相同的相同查詢時,就不需要實際查詢資料庫了。本地快取將會在做出修改、事務提交或回滾,以及關閉 session 時清空。
+預設情況下,本地快取資料的生命週期等同於整個 session 的週期。由於快取會被用來解決迴圈參考問題和加快重複巢狀查詢的速度,所以無法將其完全禁用。但是你可以透過設定 localCacheScope=STATEMENT 來只在語句執行時使用快取。
+注意,如果 localCacheScope 被設定為 SESSION,對於某個物件,MyBatis 將返回在本地快取中唯一物件的參考。對返回的物件(例如 list)做出的任何修改將會影響本地快取的內容,進而將會影響到在本次 session 中從快取返回的值。因此,不要對 MyBatis 所返回的物件作出更改,以防後患。
+你可以隨時呼叫以下方法來清空本地快取:
+ +對於你開啟的任何 session,你都要保證它們被妥善關閉,這很重要。保證妥善關閉的最佳程式碼模式是這樣的:
+ +提示 和 SqlSessionFactory 一樣,你可以呼叫當前使用的 SqlSession 的 getConfiguration 方法來獲得 Configuration 實例。
+ + +上述的各個 insert、update、delete 和 select 方法都很強大,但也有些繁瑣,它們並不符合型別安全,對你的 IDE 和單元測試也不是那麼友好。因此,使用對映器類別來執行對映語句是更常見的做法。
+我們已經在之前的入門章節中見到過一個使用對映器的示例。一個對映器類別就是一個僅需宣告與 SqlSession 方法相匹配方法的介面。下面的示例展示了一些方法簽名以及它們是如何對映到 SqlSession 上的。
+ +總之,每個對映器方法簽名應該匹配相關聯的 SqlSession 方法,字串引數 ID 無需匹配。而是由方法名匹配對映語句的 ID。
+此外,返回型別必須匹配期望的結果型別,返回單個值時,返回型別應該是返回值的類別,返回多個值時,則為陣列或集合類別,另外也可以是遊標(Cursor)。所有常用的型別都是支援的,包括:原始型別、Map、POJO 和 JavaBean。
+提示 對映器介面不需要去實現任何介面或繼承自任何類別。只要方法簽名可以被用來唯一識別對應的對映語句就可以了。
+提示 對映器介面可以繼承自其他介面。在使用 XML 來繫結對映器介面時,保證語句處於合適的名稱空間中即可。唯一的限制是,不能在兩個具有繼承關係的介面中擁有相同的方法簽名(這是潛在的危險做法,不可取)。
+你可以傳遞多個引數給一個對映器方法。在多個引數的情況下,預設它們將會以 param 加上它們在引數列表中的位置來命名,比如:#{param1}、#{param2}等。如果你想(在有多個引數時)自訂引數的名稱,那麼你可以在引數上使用 @Param("paramName") 註解。
+你也可以給方法傳遞一個 RowBounds 實例來限制查詢結果。
+ +設計初期的 MyBatis 是一個 XML 驅動的框架。配置資訊是基於 XML 的,對映語句也是定義在 XML 中的。而在 MyBatis 3 中,我們提供了其它的配置方式。MyBatis 3 建構在全面且強大的基於 Java 語言的配置 API 之上。它是 XML 和註解配置的基礎。註解提供了一種簡單且低成本的方式來實現簡單的對映語句。
+提示 不幸的是,Java 註解的表達能力和靈活性十分有限。儘管我們花了很多時間在調查、設計和試驗上,但最強大的 MyBatis 對映並不能用註解來建構——我們真沒開玩笑。而 C# 屬性就沒有這些限制,因此 MyBatis.NET 的配置會比 XML 有更大的選擇餘地。雖說如此,基於 Java 註解的配置還是有它的好處的。
+註解如下表所示:
+註解 | +使用物件 | +XML 等價形式 | +描述 | +
---|---|---|---|
@CacheNamespace |
+ 類別 |
+ <cache> |
+ 為給定的名稱空間(比如類別)配置快取。屬性:implemetation 、eviction 、flushInterval 、size 、readWrite 、blocking 、properties 。 |
+
@Property |
+ N/A | +<property> |
+ 指定引數值或佔位符(placeholder)(該佔位符能被 mybatis-config.xml 內的配置屬性替換)。屬性:name 、value 。(僅在 MyBatis 3.4.2 以上可用) |
+
@CacheNamespaceRef |
+ 類別 |
+ <cacheRef> |
+ 參考另外一個名稱空間的快取以供使用。注意,即使共享相同的全限定類別名稱,在 XML 對映檔案中宣告的快取仍被識別為一個獨立的名稱空間。屬性:value 、name 。如果你使用了這個註解,你應設定 value 或者 name 屬性的其中一個。value 屬性用於指定能夠表示該名稱空間的 Java 型別(名稱空間名就是該 Java 型別的全限定類別名稱),name 屬性(這個屬性僅在 MyBatis 3.4.2 以上可用)則直接指定了名稱空間的名字。 |
+
@ConstructorArgs |
+ 方法 |
+ <constructor> |
+ 收集一組結果以傳遞給一個結果物件的構造方法。屬性:value ,它是一個 Arg 陣列。 |
+
@Arg |
+ N/A | +
+
|
+ ConstructorArgs 集合的一部分,代表一個構造方法引數。屬性:id 、column 、javaType 、jdbcType 、typeHandler 、select 、resultMap 。id 屬性和 XML 元素 <idArg> 相似,它是一個布林值,表示該屬性是否用於唯一標識和比較物件。從版本 3.5.4 開始,該註解變為可重複註解。 |
+
@TypeDiscriminator |
+ 方法 |
+ <discriminator> |
+ 決定使用何種結果對映的一組取值(case)。屬性:column 、javaType 、jdbcType 、typeHandler 、cases 。cases 屬性是一個 Case 的陣列。 |
+
@Case |
+ N/A | +<case> |
+ 表示某個值的一個取值以及該取值對應的對映。屬性:value 、type 、results 。results 屬性是一個 Results 的陣列,因此這個註解實際上和 ResultMap 很相似,由下面的 Results 註解指定。 |
+
@Results |
+ 方法 |
+ <resultMap> |
+ 一組結果對映,指定了對某個特定結果列,對映到某個屬性或欄位的方式。屬性:value 、id 。value 屬性是一個 Result 註解的陣列。而 id 屬性則是結果對映的名稱。從版本 3.5.4 開始,該註解變為可重複註解。 |
+
@Result |
+ N/A | +
+
|
+ 在列和屬性或欄位之間的單個結果對映。屬性:id 、column 、javaType 、jdbcType 、typeHandler 、one 、many 。id 屬性和 XML 元素 <id> 相似,它是一個布林值,表示該屬性是否用於唯一標識和比較物件。one 屬性是一個關聯,和 <association> 類似,而 many 屬性則是集合關聯,和 <collection> 類似。這樣命名是為了避免產生名稱衝突。 |
+
@One |
+ N/A | +<association> |
+ 複雜型別的單個屬性對映。屬性:
+ select ,指定可載入合適型別實例的對映語句(也就是對映器方法)全限定名;
+ fetchType ,指定在該對映中覆蓋全域性配置引數 lazyLoadingEnabled ;
+ resultMap (3.5.5以上可用), 結果集的完全限定名,該結果對映到查詢結果中的集合物件;
+ columnPrefix (3.5.5以上可用),在巢狀的結果集中對所查詢的列進行分組的列字首。
+ 提示 註解 API 不支援聯合對映。這是由於 Java 註解不允許產生迴圈參考。 |
+
@Many |
+ N/A | +<collection> |
+ 複雜型別的集合屬性對映。屬性:
+ select ,指定可載入合適型別實例集合的對映語句(也就是對映器方法)全限定名;
+ fetchType ,指定在該對映中覆蓋全域性配置引數 lazyLoadingEnabled ;
+ resultMap (3.5.5以上可用),結果集的完全限定名,該結果對映到查詢結果中的集合物件;
+ columnPrefix (3.5.5以上可用),在巢狀的結果集中對所查詢的列進行分組的列字首。
+ 提示 註解 API 不支援聯合對映。這是由於 Java 註解不允許產生迴圈參考。 |
+
@MapKey |
+ 方法 |
+ + | 供返回值為 Map 的方法使用的註解。它使用物件的某個屬性作為 key,將物件 List 轉化為 Map。屬性:value ,指定作為 Map 的 key 值的物件屬性名。 |
+
@Options |
+ 方法 |
+ 對映語句的屬性 | +該註解允許你指定大部分開關和配置選項,它們通常在對映語句上作為屬性出現。與在註解上提供大量的屬性相比,Options 註解提供了一致、清晰的方式來指定選項。屬性:useCache=true 、flushCache=FlushCachePolicy.DEFAULT 、resultSetType=DEFAULT 、statementType=PREPARED 、fetchSize=-1 、timeout=-1 、useGeneratedKeys=false 、keyProperty="" 、keyColumn="" 、resultSets="" , databaseId="" 。注意,Java 註解無法指定 null 值。因此,一旦你使用了 Options 註解,你的語句就會被上述屬性的預設值所影響。要注意避免預設值帶來的非預期行為。
+ databaseId (3.5.5以上可用), 如果有一個配置好的 DatabaseIdProvider ,
+ MyBatis 會載入不帶 databaseId 屬性和帶有匹配當前資料庫 databaseId 屬性的所有語句。如果同時存在帶 databaseId 和不帶 databaseId 屬性的相同語句,則後者會被捨棄。+ + 注意: keyColumn 屬性只在某些資料庫中有效(如 Oracle、PostgreSQL 等)。要了解更多關於 keyColumn 和 keyProperty 可選值資訊,請檢視“insert, update 和 delete”一節。 |
+
+
|
+ 方法 |
+
+
|
+
+ 每個註解分別代表將會被執行的 SQL 語句。它們用字串陣列(或單個字串)作為引數。如果傳遞的是字串陣列,字串陣列會被連線成單個完整的字串,每個字串之間加入一個空格。這有效地避免了用 Java 程式碼建構 SQL 語句時產生的“丟失空格”問題。當然,你也可以提前手動連線好字串。屬性:value ,指定用來組成單個 SQL 語句的字串陣列。
+ databaseId (3.5.5以上可用), 如果有一個配置好的 DatabaseIdProvider ,
+ MyBatis 會載入不帶 databaseId 屬性和帶有匹配當前資料庫 databaseId 屬性的所有語句。如果同時存在帶 databaseId 和不帶 databaseId 屬性的相同語句,則後者會被捨棄。
+ |
+
+
|
+ 方法 |
+
+
|
+
+ 允許建構動態 SQL。這些備選的 SQL 註解允許你指定返回 SQL 語句的類別和方法,以供執行時執行。(從 MyBatis 3.4.6 開始,可以使用 CharSequence 代替 String 來作為返回型別)。當執行對映語句時,MyBatis 會實例化註解指定的類別,並呼叫註解指定的方法。你可以透過 ProviderContext 傳遞對映方法接收到的引數、"Mapper interface type" 和 "Mapper method"(僅在 MyBatis 3.4.5 以上支援)作為引數。(MyBatis 3.4 以上支援傳入多個引數)
+ 屬性:value 、type 、method 、databaseId 。
+ value and type 屬性用於指定類別名稱
+ (type 屬性是 value 的別名, 你必須指定任意一個。
+ 但是你如果在全域性配置中指定 defaultSqlProviderType ,兩個屬性都可以忽略)。
+ method 用於指定該類別的方法名(從版本 3.5.1 開始,可以省略 method 屬性,MyBatis 將會使用 ProviderMethodResolver 介面解析方法的具體實現。如果解析失敗,MyBatis 將會使用名為 provideSql 的降級實現)。提示 接下來的“SQL 語句建構器”一章將會討論該話題,以幫助你以更清晰、更便於閱讀的方式建構動態 SQL。
+ databaseId (3.5.5以上可用), 如果有一個配置好的 DatabaseIdProvider ,
+ MyBatis 會載入不帶 databaseId 屬性和帶有匹配當前資料庫 databaseId 屬性的所有語句。如果同時存在帶 databaseId 和不帶 databaseId 屬性的相同語句,則後者會被捨棄。
+ |
+
@Param |
+ 引數 |
+ N/A | +如果你的對映方法接受多個引數,就可以使用這個註解自訂每個引數的名字。否則在預設情況下,除 RowBounds 以外的引數會以 "param" 加引數位置被命名。例如 #{param1} , #{param2} 。如果使用了 @Param("person") ,引數就會被命名為 #{person} 。 |
+
@SelectKey |
+ 方法 |
+ <selectKey> |
+
+ 這個註解的功能與 <selectKey> 標籤完全一致。該註解只能在 @Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider 標註的方法上使用,否則將會被忽略。如果標註了 @SelectKey 註解,MyBatis 將會忽略掉由 @Options 註解所設定的產生主鍵或設定(configuration)屬性。屬性:statement 以字串陣列形式指定將會被執行的 SQL 語句,keyProperty 指定作為引數傳入的物件對應屬性的名稱,該屬性將會更新成新的值,before 可以指定為 true 或 false 以指明 SQL 語句應被在插入語句的之前還是之後執行。resultType 則指定 keyProperty 的 Java 型別。statementType 則用於選擇語句型別,可以選擇 STATEMENT 、PREPARED 或 CALLABLE 之一,它們分別對應於 Statement 、PreparedStatement 和 CallableStatement 。預設值是 PREPARED 。
+ databaseId (3.5.5以上可用), 如果有一個配置好的 DatabaseIdProvider ,
+ MyBatis 會載入不帶 databaseId 屬性和帶有匹配當前資料庫 databaseId 屬性的所有語句。如果同時存在帶 databaseId 和不帶 databaseId 屬性的相同語句,則後者會被捨棄。
+ |
+
@ResultMap |
+ 方法 |
+ N/A | +這個註解為 @Select 或者 @SelectProvider 註解指定 XML 對映中 <resultMap> 元素的 id。這使得註解的 select 可以複用已在 XML 中定義的 ResultMap。如果標註的 select 註解中存在 @Results 或者 @ConstructorArgs 註解,這兩個註解將被此註解覆蓋。 |
+
@ResultType |
+ 方法 |
+ N/A | +在使用了結果處理器的情況下,需要使用此註解。由於此時的返回型別為 void,所以 Mybatis 需要有一種方法來判斷每一行返回的物件型別。如果在 XML 有對應的結果對映,請使用 @ResultMap 註解。如果結果型別在 XML 的 <select> 元素中指定了,就不需要使用其它註解了。否則就需要使用此註解。比如,如果一個標註了 @Select 的方法想要使用結果處理器,那麼它的返回型別必須是 void,並且必須使用這個註解(或者 @ResultMap)。這個註解僅在方法返回型別是 void 的情況下生效。 |
+
@Flush |
+ 方法 |
+ N/A | +如果使用了這個註解,定義在 Mapper 介面中的方法就能夠呼叫 SqlSession#flushStatements() 方法。(Mybatis 3.3 以上可用) |
+
這個例子展示了如何使用 @SelectKey 註解來在插入前讀取資料庫序列的值:
+ +這個例子展示了如何使用 @SelectKey 註解來在插入後讀取資料庫自增列的值:
+ +這個例子展示了如何使用 @Flush
註解來呼叫 SqlSession#flushStatements()
:
這些例子展示了如何透過指定 @Result 的 id 屬性來命名結果集:
+ +這個例子展示了如何使用單個引數的 @SqlProvider 註解:
+ +這個例子展示了如何使用多個引數的 @SqlProvider 註解:
+ + +這是一個在全域性配置下讓所有對映方法在同一個 sql provider 類別裡面的例子(3.5.6 後可用):
+ + + +以下例子展示了 ProviderMethodResolver
(3.5.1 後可用)的預設實現使用方法:
+
這個例子展現瞭如何在宣告註解時使用 databaseId
屬性(3.5.5後可用):
Mybatis 透過使用內建的日誌工廠提供日誌功能。內建日誌工廠將會把日誌工作委託給下面的實現之一:
+MyBatis 內建日誌工廠基於執行時自省機制選擇合適的日誌工具。它會使用第一個查詢得到的工具(按上文列舉的順序查詢)。如果一個都未找到,日誌功能就會被禁用。
+不少應用伺服器(如 Tomcat 和 WebShpere)的類別路徑中已經包含 Commons Logging,所以在這種配置環境下的 MyBatis 會把它作為日誌工具,記住這點非常重要。這將意味著,在諸如 WebSphere 的環境中,它提供了 Commons Logging 的私有實現,你的 Log4J 配置將被忽略。MyBatis 將你的 Log4J 配置忽略掉是相當令人鬱悶的(事實上,正是因為在這種配置環境下,MyBatis 才會選擇使用 Commons Logging 而不是 Log4J)。如果你的應用部署在一個類別路徑已經包含 Commons Logging 的環境中,而你又想使用其它日誌工具,你可以透過在 MyBatis 配置檔案 mybatis-config.xml 裡面新增一項 setting 來選擇別的日誌工具。
+ +logImpl 可選的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是實現了介面 org.apache.ibatis.logging.Log
的,且構造方法是以字串為引數的類別的完全限定名。(譯者注:可以參考org.apache.ibatis.logging.slf4j.Slf4jImpl.java的實現)
+
你也可以呼叫如下任一方法來使用日誌工具: +
+ +如果你決定要呼叫以上某個方法,請在呼叫其它 MyBatis 方法之前呼叫它。另外,僅當執行時類別路徑中存在該日誌工具時,呼叫與該日誌工具對應的方法才會生效,否則 MyBatis 一概忽略。如你環境中並不存在 Log4J2,你卻呼叫了相應的方法,MyBatis 就會忽略這一呼叫,轉而以預設的查詢順序查詢日誌工具。 +
+關於 SLF4J、Apache Commons Logging、Apache Log4J 和 JDK Logging 的 API 介紹不在本文件介紹範圍內。不過,下面的例子可以作為一個快速入門。關於這些日誌框架的更多資訊,可以參考以下連結:
+你可以對包、對映類別的全限定名、名稱空間或全限定語句名開啟日誌功能來檢視 MyBatis 的日誌語句。 +
+再次說明下,具體怎麼做,由使用的日誌工具決定,這裡以 SLF4J(Logback) 為例。配置日誌功能非常簡單:新增一個或多個配置檔案(如 logback.xml
),有時需要新增 jar 套件。下面的例子將使用 SLF4J(Logback) 來配置完整的日誌服務,共兩個步驟:
+
因為我們使用的是 SLF4J(Logback),就要確保它的 jar 包在應用中是可用的。要啟用 SLF4J(Logback),只要將 jar 包新增到應用的類別路徑中即可。SLF4J(Logback) 的 jar 套件可以在上面的連結中下載。 +
+對於 web 應用或企業級應用,則需要將 logback-classic.jar
, logback-core.jar
and slf4j-api.jar
新增到 WEB-INF/lib
目錄下;對於獨立應用,可以將它新增到JVM 的 -classpath
啟動引數中。
+
如果你使用 maven, 你可以透過在 pom.xml
中新增下面的依賴來下載 jar 檔案。
+
配置 Logback 比較簡單,假如你需要記錄這個對映器介面的日誌: +
+ +在應用的類別路徑中建立一個名稱為 logback.xml
的檔案,檔案的具體內容如下:
+
新增以上配置後,SLF4J(Logback) 就會記錄 org.mybatis.example.BlogMapper
的詳細執行操作,且僅記錄應用中其它類別的錯誤資訊(若有)。
你也可以將日誌的記錄方式從介面級別切換到語句級別,從而實現更細粒度的控制。如下配置只對 selectBlog
語句記錄日誌:
+
與此相對,可以對一組對映器介面記錄日誌,只要對對映器介面所在的包開啟日誌功能即可:
+ + + +某些查詢可能會返回龐大的結果集,此時只想記錄其執行的 SQL 語句而不想記錄結果該怎麼辦?為此,Mybatis 中 SQL 語句的日誌級別被設為DEBUG(JDK 日誌設為 FINE),結果的日誌級別為 TRACE(JDK 日誌設為 FINER)。所以,只要將日誌級別調整為 DEBUG 即可達到目的: +
+ + + +要記錄日誌的是類似下面的對映器檔案而不是對映器介面又該怎麼做呢? +
+ + + +如需對 XML 檔案記錄日誌,只要對名稱空間增加日誌記錄功能即可: +
+ + + +要記錄具體語句的日誌可以這樣做:
+ + + +你應該注意到了,為對映器介面和 XML 檔案新增日誌功能的語句毫無差別。 +
+ +注意 如果你使用的是 SLF4J 或 Log4j 2,MyBatis 將以 MYBATIS
這個值進行呼叫。
配置檔案 log4j.properties
的餘下內容是針對日誌輸出源的,這一內容已經超出本文件範圍。關於 Logback 的更多內容,可以參考Logback 的網站。不過,你也可以簡單地做做實驗,看看不同的配置會產生怎樣的效果。
+
pom.xml
log4j2.xml
pom.xml
log4j.properties
logging.properties
MyBatis 的真正強大在於它的語句對映,這是它的魔力所在。由於它的異常強大,對映器的 + XML 檔案就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 程式碼進行對比,你會立即發現省掉了將近 + 95% 的程式碼。MyBatis 致力於減少使用成本,讓使用者能更專注於 SQL 程式碼。
+SQL 對映檔案只有很少的幾個最上層元素(按照應被定義的順序列出):
+cache
+ – 該名稱空間的快取配置。
+ cache-ref
+ – 參考其它名稱空間的快取配置。
+ resultMap
+ – 描述如何從資料庫結果集中載入物件,是最複雜也是最強大的元素。
+ parameterMap
+ – 老式風格的引數對映。此元素已被廢棄,並可能在將來被移除!請使用行內參數對映。文件中不會介紹此元素。
+ sql
+ – 可被其它語句參考的可重用語句塊。
+ insert
+ – 對映插入語句。
+ update
+ – 對映更新語句。
+ delete
+ – 對映刪除語句。
+ select
+ – 對映查詢語句。
+ 下一部分將從語句本身開始來描述每個元素的細節。
+ +查詢語句是 MyBatis 中最常用的元素之一——光能把資料存到資料庫中價值並不大,還要能重新取出來才有用,多數應用也都是查詢比修改要頻繁。 + MyBatis 的基本原則之一是:在每個插入、更新或刪除操作之間,通常會執行多個查詢操作。因此,MyBatis + 在查詢和結果對映做了相當多的改進。一個簡單查詢的 select 元素是非常簡單的。比如: +
+ + + ++ 這個語句名為 selectPerson,接受一個 int(或 Integer)型別的引數,並返回一個 + HashMap 型別的物件,其中的鍵是列名,值便是結果行中的對應值。 +
+ +注意引數符號:
+ + + ++ 這就告訴 MyBatis 建立一個預處理語句(PreparedStatement)引數,在 JDBC + 中,這樣的一個引數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中,就像這樣: +
+ + + ++ 當然,使用 JDBC 就意味著使用更多的程式碼,以便提取結果並將它們對映到物件實例中,而這就是 MyBatis + 的拿手好戲。引數和結果對映的詳細細節會分別在後面單獨的小節中說明。 +
+ ++ select 元素允許你配置很多屬性來配置每條語句的行為細節。 +
+ + + +屬性 | +描述 | +
---|---|
id |
+ + 在名稱空間中唯一的識別符號,可以被用來參考這條語句。 + | +
parameterType |
+ + 將會傳入這條語句的引數的類別全限定名或別名。這個屬性是可選的,因為 + MyBatis 可以透過型別處理器(TypeHandler)推斷出具體傳入語句的引數,預設值為未設定(unset)。 + | +
+ |
+
+ |
+
resultType |
+ + 期望從這條語句中返回結果的類別全限定名或別名。 + 注意,如果返回的是集合,那應該設定為集合包含的型別,而不是集合本身的型別。 + resultType 和 resultMap 之間只能同時使用一個。 + | +
resultMap |
+ + 對外部 resultMap 的命名參考。結果對映是 MyBatis + 最強大的特性,如果你對其理解透徹,許多複雜的對映問題都能迎刃而解。 + resultType 和 resultMap 之間只能同時使用一個。 + | +
flushCache |
+ + 將其設定為 true 後,只要語句被呼叫,都會導致本地快取和二級快取被清空,預設值:false。 + | +
useCache |
+ + 將其設定為 true 後,將會導致本條語句的結果被二級快取快取起來,預設值:對 select 元素為 true。 + | +
timeout |
+ + 這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為未設定(unset)(依賴資料庫驅動)。 + | +
fetchSize |
+ + 這是一個給驅動的建議值,嘗試讓驅動程式每次批量返回的結果行數等於這個設定值。 + 預設值為未設定(unset)(依賴驅動)。 + | +
statementType |
+ + 可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分別使用 + Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。 + | +
resultSetType |
+ + FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 + DEFAULT(等價於 unset) 中的一個,預設值為 unset (依賴資料庫驅動)。 + | +
databaseId |
+ + 如果配置了資料庫廠商標識(databaseIdProvider),MyBatis + 會載入所有不帶 databaseId 或匹配當前 databaseId 的語句;如果帶和不帶的語句都有,則不帶的會被忽略。 + | +
resultOrdered |
+
+ 這個設定僅針對巢狀結果 select 語句:如果為
+ true,則假設結果集以正確順序(排序後)執行對映,當返回新的主結果行時,將不再發生對以前結果行的參考。
+ 這樣可以減少記憶體消耗。預設值:false 。
+ |
+
resultSets |
+ + 這個設定僅適用於多結果集的情況。它將列出語句執行後返回的結果集並賦予每個結果集一個名稱,多個名稱之間以逗號分隔。 + | +
+ 資料變更語句 insert,update 和 delete 的實現非常接近: +
+ + + +屬性 | +描述 | +
---|---|
id |
+ 在名稱空間中唯一的識別符號,可以被用來參考這條語句。 | +
parameterType |
+ + 將會傳入這條語句的引數的類別全限定名或別名。這個屬性是可選的,因為 + MyBatis 可以透過型別處理器(TypeHandler)推斷出具體傳入語句的引數,預設值為未設定(unset)。 + | +
+ parameterMap |
+
+ |
+
flushCache |
+ 將其設定為 true 後,只要語句被呼叫,都會導致本地快取和二級快取被清空,預設值:(對 insert、update 和 delete 語句)true。 + | +
timeout |
+ 這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為未設定(unset)(依賴資料庫驅動)。 + | +
statementType |
+ + 可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分別使用 + Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。 + | +
useGeneratedKeys |
+ + (僅適用於 insert 和 update)這會令 MyBatis 使用 JDBC 的 + getGeneratedKeys 方法來取出由資料庫內部產生的主鍵(比如:像 MySQL 和 SQL Server 這樣的關係型資料庫管理系統的自動遞增欄位),預設值:false。 + | +
keyProperty |
+
+ (僅適用於 insert 和 update)指定能夠唯一識別物件的屬性,MyBatis 會使用
+ getGeneratedKeys 的返回值或 insert 語句的 selectKey 子元素設定它的值,預設值:未設定(unset )。如果產生列不止一個,可以用逗號分隔多個屬性名稱。
+ |
+
keyColumn |
+ + (僅適用於 insert 和 update)設定產生鍵值在表中的列名,在某些資料庫(像 PostgreSQL)中,當主鍵列不是表中的第一列的時候,是必須設定的。如果產生列不止一個,可以用逗號分隔多個屬性名稱。 + | +
databaseId |
+ + 如果配置了資料庫廠商標識(databaseIdProvider),MyBatis 會載入所有不帶 + databaseId 或匹配當前 databaseId 的語句;如果帶和不帶的語句都有,則不帶的會被忽略。 + | +
下面是 insert,update 和 delete 語句的示例:
+ + + +如前所述,插入語句的配置規則更加豐富,在插入語句裡面有一些額外的屬性和子元素用來處理主鍵的產生,並且提供了多種產生方式。
+ +首先,如果你的資料庫支援自動產生主鍵的欄位(比如 MySQL 和 SQL Server),那麼你可以設定 useGeneratedKeys=”true”,然後再把 keyProperty 設定為目標屬性就 OK 了。例如,如果上面的 Author 表已經在 id 列上使用了自動產生,那麼語句可以修改為:
+ + + +
+ 如果你的資料庫還支援多行插入, 你也可以傳入一個 Author
陣列或集合,並返回自動產生的主鍵。
+
+ 對於不支援自動產生主鍵列的資料庫和可能不支援自動產生主鍵的 JDBC 驅動,MyBatis 有另外一種方法來產生主鍵。 +
+ ++ 這裡有一個簡單(也很傻)的示例,它可以產生一個隨機 ID(不建議實際使用,這裡只是為了展示 + MyBatis 處理問題的靈活性和寬容度): +
+ + +在上面的示例中,首先會執行 selectKey 元素中的語句,並設定 Author 的 + id,然後才會呼叫插入語句。這樣就實現了資料庫自動產生主鍵類似的行為,同時保持了 Java 程式碼的簡潔。 +
+selectKey 元素描述如下: +
+ + +屬性 | +描述 | +
---|---|
keyProperty |
+
+ selectKey 語句結果應該被設定到的目標屬性。如果產生列不止一個,可以用逗號分隔多個屬性名稱。
+ |
+
keyColumn |
+ + 返回結果集中產生列屬性的列名。如果產生列不止一個,可以用逗號分隔多個屬性名稱。 + | +
resultType |
+ + 結果的型別。通常 MyBatis 可以推斷出來,但是為了更加準確,寫上也不會有什麼問題。MyBatis + 允許將任何簡單型別用作主鍵的型別,包括字串。如果產生列不止一個,則可以使用包含期望屬性的 + Object 或 Map。 + | +
order |
+
+ 可以設定為 BEFORE 或 AFTER 。如果設定為
+ BEFORE ,那麼它首先會產生主鍵,設定 keyProperty 再執行插入語句。如果設定為
+ AFTER ,那麼先執行插入語句,然後是 selectKey 中的語句 - 這和 Oracle
+ 資料庫的行為相似,在插入語句內部可能有嵌入索引呼叫。
+ |
+
statementType |
+
+ 和前面一樣,MyBatis 支援 STATEMENT ,PREPARED 和 CALLABLE
+ 型別的對映語句,分別代表 Statement , PreparedStatement 和
+ CallableStatement 型別。
+ |
+
+ 這個元素可以用來定義可重用的 SQL 程式碼片段,以便在其它語句中使用。 + 引數可以靜態地(在載入的時候)確定下來,並且可以在不同的 include 元素中定義不同的引數值。比如: +
+ + + ++ 這個 SQL 片段可以在其它語句中使用,例如: +
+ + + ++ 也可以在 include 元素的 refid 屬性或內部語句中使用屬性值,例如: +
+ + ++ 之前見到的所有語句都使用了簡單的引數形式。但實際上,引數是 MyBatis + 非常強大的元素。對於大多數簡單的使用場景,你都不需要使用複雜的引數,比如: +
+ + + +
+ 上面的這個示例說明了一個非常簡單的命名引數對映。鑑於引數型別(parameterType)會被自動設定為
+ int
,這個引數可以隨意命名。原始型別或簡單資料型別(比如
+ Integer
和 String
)因為沒有其它屬性,會用它們的值來作為引數。
+ 然而,如果傳入一個複雜的物件,行為就會有點不一樣了。比如:
+
+ 如果 User 型別的引數物件傳遞到了語句中,會查詢 id、username 和 password + 屬性,然後將它們的值傳入預處理語句的引數中。 +
+ ++ 對傳遞語句引數來說,這種方式真是乾脆利落。不過引數對映的功能遠不止於此。 +
+ ++ 首先,和 MyBatis 的其它部分一樣,引數也可以指定一個特殊的資料型別。 +
+ + + +
+ 和 MyBatis 的其它部分一樣,幾乎總是可以根據引數物件的型別確定 javaType,除非該物件是一個
+ HashMap
。這個時候,你需要顯式指定 javaType
+ 來確保正確的型別處理器(TypeHandler
)被使用。
+
提示 JDBC 要求,如果一個列允許使用 null
+ 值,並且會使用值為 null 的引數,就必須要指定 JDBC 型別(jdbcType)。閱讀
+ PreparedStatement.setNull()
的 JavaDoc 來獲取更多資訊。
+
+ 要更進一步地自訂型別處理方式,可以指定一個特殊的型別處理器類別(或別名),比如: +
+ + + +引數的配置好像越來越繁瑣了,但實際上,很少需要如此繁瑣的配置。
+ +對於數值型別,還可以設定 numericScale
指定小數點後保留的位數。
+ 最後,mode 屬性允許你指定 IN
,OUT
或
+ INOUT
引數。如果引數的 mode
為 OUT
+ 或 INOUT
,將會修改引數物件的屬性值,以便作為輸出引數返回。
+ 如果 mode
為 OUT
(或 INOUT
),而且
+ jdbcType
為 CURSOR
(也就是 Oracle 的
+ REFCURSOR),你必須指定一個 resultMap
參考來將結果集
+ ResultSet
對映到引數的型別上。要注意這裡的
+ javaType
屬性是可選的,如果留空並且 jdbcType 是
+ CURSOR
,它會被自動地被設為 ResultSet
。
+
+ MyBatis 也支援很多進階的資料型別,比如結構體(structs),但是當使用 out + 引數時,你必須顯式設定型別的名稱。比如(再次提示,在實際中要像這樣不能換行): +
+ + + +
+ 儘管上面這些選項很強大,但大多時候,你只須簡單指定屬性名,頂多要為可能為空的列指定
+ jdbcType
,其他的事情交給 MyBatis 自己去推斷就行了。
+
+ 預設情況下,使用 #{}
+ 引數語法時,MyBatis 會建立 PreparedStatement
+ 引數佔位符,並透過佔位符安全地設定引數(就像使用 ? 一樣)。
+ 這樣做更安全,更迅速,通常也是首選做法,不過有時你就是想直接在 SQL 語句中直接插入一個不轉義的字串。
+ 比如 ORDER BY 子句,這時候你可以:
+
這樣,MyBatis 就不會修改或轉義該字串了。
+ +
+ 當 SQL 語句中的元資料(如表名或列名)是動態產生的時候,字串替換將會非常有用。
+ 舉個例子,如果你想 select
一個表任意一列的資料時,不需要這樣寫:
+
+ 而是可以只寫這樣一個方法:
+
+ 其中 ${column}
會被直接替換,而 #{value}
會使用 ?
預處理。
+ 這樣,就能完成同樣的任務:
+
+
+ 這種方式也同樣適用於替換表名的情況。 +
+ ++ 提示 + 用這種方式接受使用者的輸入,並用作語句引數是不安全的,會導致潛在的 SQL + 注入攻擊。因此,要麼不允許使用者輸入這些欄位,要麼自行轉義並檢驗這些引數。 +
+
+ resultMap
元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90%
+ 的 JDBC ResultSets
資料提取程式碼中解放出來,並在一些情形下允許你進行一些
+ JDBC 不支援的操作。實際上,在為一些比如連線的複雜語句編寫對映程式碼的時候,一份
+ resultMap
能夠代替實現同等功能的數千行程式碼。ResultMap
+ 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
+
+ 之前你已經見過簡單對映語句的示例,它們沒有顯式指定 resultMap
。比如:
+
+ 上述語句只是簡單地將所有的列對映到 HashMap
的鍵上,這由 resultType
屬性指定。雖然在大部分情況下都夠用,但是 HashMap 並不是一個很好的領域模型。你的程式更可能會使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 物件)作為領域模型。MyBatis 對兩者都提供了支援。看看下面這個 JavaBean:
+
+ 基於 JavaBean 的規範,上面這個類別有 3 個屬性:id,username 和 + hashedPassword。這些屬性會對應到 select 語句中的列名。 +
+ +
+ 這樣的一個 JavaBean 可以被對映到 ResultSet
,就像對映到
+ HashMap
一樣簡單。
+
+ 型別別名是你的好幫手。使用它們,你就可以不用輸入類別的全限定名了。比如: +
+ + + +
+ 在這些情況下,MyBatis 會在幕後自動建立一個 ResultMap
,再根據屬性名來對映列到
+ JavaBean 的屬性上。如果列名和屬性名不能匹配上,可以在 SELECT
+ 語句中設定列別名(這是一個基本的 SQL 特性)來完成匹配。比如:
+
+ 在學習了上面的知識後,你會發現上面的例子沒有一個需要顯式配置
+ ResultMap
,這就是 ResultMap
+ 的優秀之處——你完全可以不用顯式地配置它們。
+ 雖然上面的例子不用顯式配置 ResultMap
。
+ 但為了講解,我們來看看如果在剛剛的示例中,顯式使用外部的
+ resultMap
會怎樣,這也是解決列名不匹配的另外一種方式。
+
+ 然後在參考它的語句中設定 resultMap
屬性就行了(注意我們去掉了
+ resultType
屬性)。比如:
+
+ 如果這個世界總是這麼簡單就好了。 +
+ ++ MyBatis 建立時的一個思想是:資料庫不可能永遠是你所想或所需的那個樣子。 + 我們希望每個資料庫都具備良好的第三正規化或 BCNF 正規化,可惜它們並不都是那樣。 + 如果能有一種資料庫對映模式,完美適配所有的應用程式,那就太好了,但可惜也沒有。 + 而 ResultMap 就是 MyBatis 對這個問題的答案。 +
+ ++ 比如,我們如何對映下面這個語句? +
+ + + ++ 你可能想把它對映到一個智慧的物件模型,這個物件表示了一篇部落格,它由某位作者所寫,有很多的博文,每篇博文有零或多條的評論和標籤。 + 我們先來看看下面這個完整的例子,它是一個非常複雜的結果對映(假設作者,部落格,博文,評論和標籤都是型別別名)。 + 不用緊張,我們會一步一步地來說明。雖然它看起來令人望而生畏,但其實非常簡單。 +
+ + + +
+ resultMap
元素有很多子元素和一個值得深入探討的結構。
+ 下面是resultMap
元素的概念檢視。
+
constructor
- 用於在實例化類別時,注入結果到構造方法中
+ idArg
- ID 引數;標記出作為 ID 的結果可以幫助提高整體效能arg
- 將被注入到構造方法的一個普通結果id
– 一個 ID 結果;標記出作為 ID 的結果可以幫助提高整體效能result
– 注入到欄位或 JavaBean 屬性的普通結果association
– 一個複雜型別的關聯;許多結果將包裝成這種型別
+ resultMap
元素,或是對其它結果對映的參考collection
– 一個複雜型別的集合
+ resultMap
元素,或是對其它結果對映的參考discriminator
– 使用結果值來決定使用哪個 resultMap
+ case
– 基於某些值的結果對映
+ case
也是一個結果對映,因此具有相同的結構和元素;或者參考其它的結果對映屬性 | +描述 | +
---|---|
id |
+ 當前名稱空間中的一個唯一標識,用於標識一個結果對映。 | +
type |
+ + 類別的完全限定名, 或者一個型別別名(關於內建的型別別名,可以參考上面的表格)。 + | +
autoMapping |
+ + 如果設定這個屬性,MyBatis 將會為本結果對映開啟或者關閉自動對映。 + 這個屬性會覆蓋全域性的屬性 autoMappingBehavior。預設值:未設定(unset)。 + | +
+ 最佳實踐 + 最好逐步建立結果對映。單元測試可以在這個過程中起到很大幫助。 + 如果你嘗試一次性建立像上面示例那麼巨大的結果對映,不僅容易出錯,難度也會直線上升。 + 所以,從最簡單的形態開始,逐步迭代。而且別忘了單元測試! + 有時候,框架的行為像是一個黑盒子(無論是否開源)。因此,為了確保實現的行為與你的期望相一致,最好編寫單元測試。 + 並且單元測試在提交 bug 時也能起到很大的作用。 +
+ ++ 下一部分將詳細說明每個元素。 +
+ ++ 這些元素是結果對映的基礎。id 和 result + 元素都將一個列的值對映到一個簡單資料型別(String, int, double, Date + 等)的屬性或欄位。 +
+ ++ 這兩者之間的唯一不同是,id + 元素對應的屬性會被標記為物件的識別符號,在比較物件實例時使用。 + 這樣可以提高整體的效能,尤其是進行快取和巢狀結果對映(也就是連線對映)的時候。 +
+ ++ 兩個元素都有一些屬性: +
+ +屬性 | +描述 | +
---|---|
property |
+ + 對映到列結果的欄位或屬性。如果 JavaBean + 有這個名字的屬性(property),會先使用該屬性。否則 MyBatis 將會尋找給定名稱的欄位(field)。 + 無論是哪一種情形,你都可以使用常見的點式分隔形式進行復雜屬性導航。 + 比如,你可以這樣對映一些簡單的東西:“username”,或者對映到一些複雜的東西上:“address.street.number”。 + | +
column |
+
+ 資料庫中的列名,或者是列的別名。一般情況下,這和傳遞給
+ resultSet.getString(columnName) 方法的引數一樣。
+ |
+
javaType |
+ + 一個 Java 類別的全限定名,或一個型別別名(關於內建的型別別名,可以參考上面的表格)。 + 如果你對映到一個 JavaBean,MyBatis 通常可以推斷型別。然而,如果你對映到的是 + HashMap,那麼你應該明確地指定 javaType 來保證行為與期望的相一致。 + | +
jdbcType |
+ + JDBC 型別,所支援的 JDBC 型別參見這個表格之後的“支援的 JDBC 型別”。 + 只需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 型別。這是 + JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC + 程式設計,你需要對可以為空值的列指定這個型別。 + | +
typeHandler |
+ + 我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋預設的型別處理器。 + 這個屬性值是一個型別處理器實現類別的全限定名,或者是型別別名。 + | +
+ 為了以後可能的使用場景,MyBatis 透過內建的 jdbcType 列舉型別支援下面的 JDBC 型別。 +
+ +BIT |
+ FLOAT |
+ CHAR |
+ TIMESTAMP |
+ OTHER |
+ UNDEFINED |
+
TINYINT |
+ REAL |
+ VARCHAR |
+ BINARY |
+ BLOB |
+ NVARCHAR |
+
SMALLINT |
+ DOUBLE |
+ LONGVARCHAR |
+ VARBINARY |
+ CLOB |
+ NCHAR |
+
INTEGER |
+ NUMERIC |
+ DATE |
+ LONGVARBINARY |
+ BOOLEAN |
+ NCLOB |
+
BIGINT |
+ DECIMAL |
+ TIME |
+ NULL |
+ CURSOR |
+ ARRAY |
+
+ 透過修改物件屬性的方式,可以滿足大多數的資料傳輸物件(Data Transfer Object, + DTO)以及絕大部分領域模型的要求。但有些情況下你想使用不可變類別。 + 一般來說,很少改變或基本不變的包含參考或資料的表,很適合使用不可變類別。 + 構造方法注入允許你在初始化時為類別設定屬性的值,而不用暴露出公有方法。MyBatis + 也支援私有屬性和私有 JavaBean 屬性來完成注入,但有一些人更青睞於透過構造方法進行注入。 + constructor 元素就是為此而生的。 +
+ ++ 看看下面這個構造方法: +
+ + + +
+ 為了將結果注入構造方法,MyBatis 需要透過某種方式定位相應的構造方法。
+ 在下面的例子中,MyBatis 搜尋一個聲明瞭三個形參的構造方法,引數型別以
+ java.lang.Integer
, java.lang.String
和
+ int
的順序給出。
+
+ 當你在處理一個帶有多個形參的構造方法時,很容易搞亂 arg 元素的順序。
+ 從版本 3.4.3 開始,可以在指定引數名稱的前提下,以任意順序編寫 arg 元素。
+ 為了透過名稱來參考構造方法引數,你可以新增 @Param
註解,或者使用
+ '-parameters' 編譯選項並啟用 useActualParamName
+ 選項(預設開啟)來編譯專案。下面是一個等價的例子,儘管函式簽名中第二和第三個形參的順序與
+ constructor 元素中引數宣告的順序不匹配。
+
+ 如果存在名稱和型別相同的屬性,那麼可以省略 javaType
。
+
+ 剩餘的屬性和規則和普通的 id 和 result 元素是一樣的。 +
+ +屬性 | +描述 | +
---|---|
column |
+
+ 資料庫中的列名,或者是列的別名。一般情況下,這和傳遞給
+ resultSet.getString(columnName) 方法的引數一樣。
+ |
+
javaType |
+ + 一個 Java 類別的完全限定名,或一個型別別名(關於內建的型別別名,可以參考上面的表格)。 + 如果你對映到一個 JavaBean,MyBatis 通常可以推斷型別。然而,如果你對映到的是 + HashMap,那麼你應該明確地指定 javaType 來保證行為與期望的相一致。 + | +
jdbcType |
+ + JDBC 型別,所支援的 JDBC 型別參見這個表格之前的“支援的 JDBC 型別”。 + 只需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 型別。這是 + JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC + 程式設計,你需要對可能存在空值的列指定這個型別。 + | +
typeHandler |
+ + 我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋預設的型別處理器。 + 這個屬性值是一個型別處理器實現類別的完全限定名,或者是型別別名。 + | +
select |
+ + 用於載入複雜型別屬性的對映語句的 ID,它會從 column + 屬性中指定的列檢索資料,作為引數傳遞給此 select 語句。具體請參考關聯元素。 + | +
resultMap |
+
+ 結果對映的 ID,可以將巢狀的結果集對映到一個合適的物件樹中。
+ 它可以作為使用額外 select 語句的替代方案。它可以將多表連線操作的結果對映成一個單一的
+ ResultSet 。這樣的 ResultSet
+ 將會將包含重複或部分資料重複的結果集。為了將結果集正確地對映到巢狀的物件樹中,MyBatis
+ 允許你 “串聯”結果對映,以便解決巢狀結果集的問題。想了解更多內容,請參考下面的關聯元素。
+ |
+
name |
+ + 構造方法形參的名字。從 3.4.3 版本開始,透過指定具體的引數名,你可以以任意順序寫入 + arg 元素。參看上面的解釋。 + | +
+ 關聯(association)元素處理“有一個”型別的關係。
+ 比如,在我們的示例中,一個部落格有一個使用者。關聯結果對映和其它型別的對映工作方式差不多。
+ 你需要指定目標屬性名以及屬性的javaType
(很多時候 MyBatis
+ 可以自己推斷出來),在必要的情況下你還可以設定 JDBC
+ 型別,如果你想覆蓋獲取結果值的過程,還可以設定型別處理器。
+
+ 關聯的不同之處是,你需要告訴 MyBatis 如何載入關聯。MyBatis 有兩種不同的方式載入關聯: +
+ ++ 首先,先讓我們來看看這個元素的屬性。你將會發現,和普通的結果對映相比,它只在 select 和 resultMap 屬性上有所不同。 +
+ +屬性 | +描述 | +
---|---|
property |
+ + 對映到列結果的欄位或屬性。如果用來匹配的 JavaBean + 存在給定名字的屬性,那麼它將會被使用。否則 MyBatis 將會尋找給定名稱的欄位。 + 無論是哪一種情形,你都可以使用通常的點式分隔形式進行復雜屬性導航。 + 比如,你可以這樣對映一些簡單的東西:“username”,或者對映到一些複雜的東西上:“address.street.number”。 + | +
javaType |
+ + 一個 Java 類別的完全限定名,或一個型別別名(關於內建的型別別名,可以參考上面的表格)。 + 如果你對映到一個 JavaBean,MyBatis 通常可以推斷型別。然而,如果你對映到的是 + HashMap,那麼你應該明確地指定 javaType 來保證行為與期望的相一致。 + | +
jdbcType |
+ + JDBC 型別,所支援的 JDBC 型別參見這個表格之前的“支援的 JDBC 型別”。 + 只需要在可能執行插入、更新和刪除的且允許空值的列上指定 JDBC 型別。這是 + JDBC 的要求而非 MyBatis 的要求。如果你直接面向 + JDBC 程式設計,你需要對可能存在空值的列指定這個型別。 + | +
typeHandler |
+ + 我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋預設的型別處理器。 + 這個屬性值是一個型別處理器實現類別的完全限定名,或者是型別別名。 + | +
屬性 | +描述 | +
---|---|
column |
+
+ 資料庫中的列名,或者是列的別名。一般情況下,這和傳遞給
+ resultSet.getString(columnName) 方法的引數一樣。
+ 注意:在使用複合主鍵的時候,你可以使用 column="{prop1=col1,prop2=col2}"
+ 這樣的語法來指定多個傳遞給巢狀 Select 查詢語句的列名。這會使得
+ prop1 和 prop2 作為引數物件,被設定為對應巢狀 Select 語句的引數。
+ |
+
select |
+
+ 用於載入複雜型別屬性的對映語句的 ID,它會從 column
+ 屬性指定的列中檢索資料,作為引數傳遞給目標 select 語句。
+ 具體請參考下面的例子。注意:在使用複合主鍵的時候,你可以使用
+ column="{prop1=col1,prop2=col2}" 這樣的語法來指定多個傳遞給巢狀
+ Select 查詢語句的列名。這會使得 prop1 和 prop2
+ 作為引數物件,被設定為對應巢狀 Select 語句的引數。
+ |
+
fetchType |
+
+ 可選的。有效值為 lazy 和 eager 。
+ 指定屬性後,將在對映中忽略全域性配置引數 lazyLoadingEnabled ,使用屬性的值。
+ |
+
+ 示例: +
+ + + +
+ 就是這麼簡單。我們有兩個 select
+ 查詢語句:一個用來載入部落格(Blog),另外一個用來載入作者(Author),而且部落格的結果對映描述了應該使用
+ selectAuthor
語句載入它的 author 屬性。
+
+ 其它所有的屬性將會被自動載入,只要它們的列名和屬性名相匹配。 +
+ ++ 這種方式雖然很簡單,但在大型資料集或大型資料表上表現不佳。這個問題被稱為“N+1 查詢問題”。 + 概括地講,N+1 查詢問題是這樣子的: +
+ ++ 這個問題會導致成百上千的 SQL 語句被執行。有時候,我們不希望產生這樣的後果。 +
+ ++ 好訊息是,MyBatis 能夠對這樣的查詢進行延遲載入,因此可以將大量語句同時執行的開銷分散開來。 + 然而,如果你載入記錄列表之後立刻就遍歷列表以獲取巢狀的資料,就會觸發所有的延遲載入查詢,效能可能會變得很糟糕。 +
+ ++ 所以還有另外一種方法。 +
+ +屬性 | +描述 | +
---|---|
resultMap |
+
+ 結果對映的 ID,可以將此關聯的巢狀結果集對映到一個合適的物件樹中。
+ 它可以作為使用額外 select 語句的替代方案。它可以將多表連線操作的結果對映成一個單一的
+ ResultSet 。這樣的 ResultSet 有部分資料是重複的。
+ 為了將結果集正確地對映到巢狀的物件樹中, MyBatis
+ 允許你“串聯”結果對映,以便解決巢狀結果集的問題。使用巢狀結果對映的一個例子在表格以後。
+ |
+
columnPrefix |
+
+ 當連線多個表時,你可能會不得不使用列別名來避免在 ResultSet
+ 中產生重複的列名。指定 columnPrefix 列名字首允許你將帶有這些字首的列對映到一個外部的結果對映中。
+ 詳細說明請參考後面的例子。
+ |
+
notNullColumn |
+ + 預設情況下,在至少一個被對映到屬性的列不為空時,子物件才會被建立。 + 你可以在這個屬性上指定非空的列來改變預設行為,指定後,Mybatis + 將只在這些列中任意一列非空時才建立一個子物件。可以使用逗號分隔來指定多個列。預設值:未設定(unset)。 + | +
autoMapping |
+
+ 如果設定這個屬性,MyBatis 將會為本結果對映開啟或者關閉自動對映。
+ 這個屬性會覆蓋全域性的屬性 autoMappingBehavior。注意,本屬性對外部的結果對映無效,所以不能搭配
+ select 或 resultMap 元素使用。預設值:未設定(unset)。
+ |
+
+ 之前,你已經看到了一個非常複雜的巢狀關聯的例子。 + 下面的例子則是一個非常簡單的例子,用於示範巢狀結果對映如何工作。 + 現在我們將部落格表和作者表連線在一起,而不是執行一個獨立的查詢語句,就像這樣: +
+ + + ++ 注意查詢中的連線,以及為確保結果能夠擁有唯一且清晰的名字,我們設定的別名。 + 這使得進行對映非常簡單。現在我們可以對映這個結果: +
+ + + ++ 在上面的例子中,你可以看到,部落格(Blog)作者(author)的關聯元素委託名為 + “authorResult” 的結果對映來載入作者物件的實例。 +
+ ++ 非常重要: + id 元素在巢狀結果對映中扮演著非常重要的角色。你應該總是指定一個或多個可以唯一標識結果的屬性。 + 雖然,即使不指定這個屬性,MyBatis 仍然可以工作,但是會產生嚴重的效能問題。 + 只需要指定可以唯一標識結果的最少屬性。顯然,你可以選擇主鍵(複合主鍵也可以)。 +
+ ++ 現在,上面的示例使用了外部的結果對映元素來對映關聯。這使得 Author 的結果對映可以被重用。 + 然而,如果你不打算重用它,或者你更喜歡將你所有的結果對映放在一個具有描述性的結果對映元素中。 + 你可以直接將結果對映作為子元素巢狀在內。這裡給出使用這種方式的等效例子: +
+ + + ++ 那如果部落格(blog)有一個共同作者(co-author)該怎麼辦?select 語句看起來會是這樣的: +
+ + + ++ 回憶一下,Author 的結果對映定義如下: +
+ + + +
+ 由於結果中的列名與結果對映中的列名不同。你需要指定 columnPrefix
+ 以便重複使用該結果對映來對映 co-author 的結果。
+
屬性 | +描述 | +
---|---|
column |
+
+ 當使用多個結果集時,該屬性指定結果集中用於與 foreignColumn
+ 匹配的列(多個列名以逗號隔開),以識別關係中的父型別與子型別。
+ |
+
foreignColumn |
+
+ 指定外來鍵對應的列名,指定的列將與父型別中 column 的給出的列進行匹配。
+ |
+
resultSet |
+ + 指定用於載入複雜型別的結果集名字。 + | +
從版本 3.2.3 開始,MyBatis 提供了另一種解決 N+1 查詢問題的方法。
+ ++ 某些資料庫允許儲存過程返回多個結果集,或一次性執行多個語句,每個語句返回一個結果集。 + 我們可以利用這個特性,在不使用連線的情況下,只訪問資料庫一次就能獲得相關資料。 +
+ +在例子中,儲存過程執行下面的查詢並返回兩個結果集。第一個結果集會返回部落格(Blog)的結果,第二個則返回作者(Author)的結果。
+ + + +在對映語句中,必須透過 resultSets
屬性為每個結果集指定一個名字,多個名字使用逗號隔開。
+ 現在我們可以指定使用 “authors” 結果集的資料來填充 “author” 關聯: +
+ + + ++ 你已經在上面看到了如何處理“有一個”型別的關聯。但是該怎麼處理“有很多個”型別的關聯呢?這就是我們接下來要介紹的。 +
+ ++ 集合元素和關聯元素幾乎是一樣的,它們相似的程度之高,以致於沒有必要再介紹集合元素的相似部分。 + 所以讓我們來關注它們的不同之處吧。 +
+ ++ 我們來繼續上面的示例,一個部落格(Blog)只有一個作者(Author)。但一個部落格有很多文章(Post)。 + 在部落格類別中,這可以用下面的寫法來表示: +
+ + + ++ 要像上面這樣,對映巢狀結果集合到一個 List 中,可以使用集合元素。 + 和關聯元素一樣,我們可以使用巢狀 Select 查詢,或基於連線的巢狀結果對映集合。 +
+ ++ 首先,讓我們看看如何使用巢狀 Select 查詢來為部落格載入文章。 +
+ + + ++ 你可能會立刻注意到幾個不同,但大部分都和我們上面學習過的關聯元素非常相似。 + 首先,你會注意到我們使用的是集合元素。 + 接下來你會注意到有一個新的 “ofType” 屬性。這個屬性非常重要,它用來將 + JavaBean(或欄位)屬性的型別和集合儲存的型別區分開來。 + 所以你可以按照下面這樣來閱讀對映: +
+ + + ++ 讀作: “posts 是一個儲存 Post 的 ArrayList 集合” +
+ ++ 在一般情況下,MyBatis 可以推斷 javaType 屬性,因此並不需要填寫。所以很多時候你可以簡略成: +
+ + + ++ 現在你可能已經猜到了集合的巢狀結果對映是怎樣工作的——除了新增的 “ofType” 屬性,它和關聯的完全相同。 +
+ +首先, 讓我們看看對應的 SQL 語句:
+ + + ++ 我們再次連線了部落格表和文章表,並且為每一列都賦予了一個有意義的別名,以便對映保持簡單。 + 要對映部落格裡面的文章集合,就這麼簡單: +
+ + + ++ 再提醒一次,要記得上面 id 元素的重要性,如果你不記得了,請閱讀關聯部分的相關部分。 +
+ ++ 如果你喜歡更詳略的、可重用的結果對映,你可以使用下面的等價形式: +
+ + ++ 像關聯元素那樣,我們可以透過執行儲存過程實現,它會執行兩個查詢並返回兩個結果集,一個是部落格的結果集,另一個是文章的結果集: +
+ + + +在對映語句中,必須透過 resultSets
屬性為每個結果集指定一個名字,多個名字使用逗號隔開。
我們指定 “posts” 集合將會使用儲存在 “posts” 結果集中的資料進行填充:
+ + + ++ 注意 + 對關聯或集合的對映,並沒有深度、廣度或組合上的要求。但在對映時要留意效能問題。 + 在探索最佳實踐的過程中,應用的單元測試和效能測試會是你的好幫手。 + 而 MyBatis 的好處在於,可以在不對你的程式碼引入重大變更(如果有)的情況下,允許你之後改變你的想法。 +
+ ++ 高階關聯和集合對映是一個深度話題。文件的介紹只能到此為止。配合少許的實踐,你會很快了解全部的用法。 +
+ ++ 有時候,一個數據函式庫查詢可能會返回多個不同的結果集(但總體上還是有一定的聯絡的)。 + 鑑別器(discriminator)元素就是被設計來應對這種情況的,另外也能處理其它情況,例如類別的繼承層次結構。 + 鑑別器的概念很好理解——它很像 Java 語言中的 switch 語句。 +
+ ++ 一個鑑別器的定義需要指定 column 和 javaType 屬性。column 指定了 MyBatis 查詢被比較值的地方。 + 而 javaType 用來確保使用正確的相等測試(雖然很多情況下字串的相等測試都可以工作)。例如: +
+ + + ++ 在這個示例中,MyBatis 會從結果集中得到每條記錄,然後比較它的 vehicle type 值。 + 如果它匹配任意一個鑑別器的 case,就會使用這個 case 指定的結果對映。 + 這個過程是互斥的,也就是說,剩餘的結果對映將被忽略(除非它是擴充套件的,我們將在稍後討論它)。 + 如果不能匹配任何一個 case,MyBatis 就只會使用鑑別器塊外定義的結果對映。 + 所以,如果 carResult 的宣告如下: +
+ + + + ++ 那麼只有 doorCount 屬性會被載入。這是為了即使鑑別器的 case + 之間都能分為完全獨立的一組,儘管和父結果對映可能沒有什麼關係。在上面的例子中,我們當然知道 + cars 和 vehicles 之間有關係,也就是 Car 是一個 + Vehicle。因此,我們希望剩餘的屬性也能被載入。而這隻需要一個小修改。 +
+ + + ++ 現在 vehicleResult 和 carResult 的屬性都會被載入了。 +
+ ++ 可能有人又會覺得對映的外部定義有點太冗長了。 + 因此,對於那些更喜歡簡潔的對映風格的人來說,還有另一種語法可以選擇。例如: +
+ + + ++ 提示 + 請注意,這些都是結果對映,如果你完全不設定任何的 result 元素,MyBatis + 將為你自動匹配列和屬性。所以上面的例子大多都要比實際的更復雜。 + 這也表明,大多數資料庫的複雜度都比較高,我們不太可能一直依賴於這種機制。 +
+ ++ 正如你在前面一節看到的,在簡單的場景下,MyBatis + 可以為你自動對映查詢結果。但如果遇到複雜的場景,你需要建構一個結果對映。 + 但是在本節中,你將看到,你可以混合使用這兩種策略。讓我們深入瞭解一下自動對映是怎樣工作的。 +
+ ++ 當自動對映查詢結果時,MyBatis 會獲取結果中返回的列名並在 Java + 類別中查詢相同名字的屬性(忽略大小寫)。 這意味著如果發現了 ID 列和 + id 屬性,MyBatis 會將列 ID 的值賦給 id 屬性。 +
+ +
+ 通常資料庫列使用大寫字母組成的單詞命名,單詞間用下劃線分隔;而 Java
+ 屬性一般遵循駝峰命名法約定。為了在這兩種命名方式之間啟用自動對映,需要將
+ mapUnderscoreToCamelCase
設定為 true。
+
+ 甚至在提供了結果對映後,自動對映也能工作。在這種情況下,對於每一個結果對映,在 + ResultSet 出現的列,如果沒有設定手動對映,將被自動對映。在自動對映處理完畢後,再處理手動對映。 + 在下面的例子中,id 和 userName 列將被自動對映,hashed_password 列將根據配置進行對映。 +
+ + + + ++ 有三種自動對映等級: +
+ +NONE
- 禁用自動對映。僅對手動對映的屬性進行對映。
+ PARTIAL
- 對除在內部定義了巢狀結果對映(也就是連線的屬性)以外的屬性進行對映
+ FULL
- 自動對映所有屬性。
+
+ 預設值是 PARTIAL
,這是有原因的。當對連線查詢的結果使用
+ FULL
時,連線查詢會在同一行中獲取多個不同實體的資料,因此可能導致非預期的對映。
+ 下面的例子將展示這種風險:
+
+ 在該結果對映中,Blog 和 Author 均將被自動對映。但是注意
+ Author 有一個 id 屬性,在 ResultSet 中也有一個名為 id
+ 的列,所以 Author 的 id 將填入 Blog 的 id,這可不是你期望的行為。
+ 所以,要謹慎使用 FULL
。
+
+ 無論設定的自動對映等級是哪種,你都可以透過在結果對映上設定 autoMapping
+ 屬性來為指定的結果對映設定啟用/禁用自動對映。
+
+ MyBatis 內建了一個強大的事務性查詢快取機制,它可以非常方便地配置和訂製。 + 為了使它更加強大而且易於配置,我們對 MyBatis 3 中的快取實現進行了許多改進。 +
+ ++ 預設情況下,只啟用了本地的會話快取,它僅僅對一個會話中的資料進行快取。 + 要啟用全域性的二級快取,只需要在你的 SQL 對映檔案中新增一行: +
+ + + ++ 基本上就是這樣。這個簡單語句的效果如下: +
+ ++ 提示 + 快取只作用於 cache 標籤所在的對映檔案中的語句。如果你混合使用 Java API 和 XML + 對映檔案,在共用介面中的語句將不會被預設快取。你需要使用 @CacheNamespaceRef 註解指定快取作用域。 +
+ ++ 這些屬性可以透過 cache 元素的屬性來修改。比如: +
+ + + ++ 這個更進階的配置建立了一個 FIFO 快取,每隔 60 秒重新整理,最多可以儲存結果物件或列表的 512 + 個參考,而且返回的物件被認為是隻讀的,因此對它們進行修改可能會在不同執行緒中的呼叫者產生衝突。 +
+ ++ 可用的清除策略有: +
+ +LRU
– 最近最少使用:移除最長時間不被使用的物件。
+ FIFO
– 先進先出:按物件進入快取的順序來移除它們。
+ SOFT
– 軟參考:基於垃圾回收器狀態和軟參考規則移除物件。
+ WEAK
– 弱參考:更積極地基於垃圾收集器狀態和弱參考規則移除物件。
+ 預設的清除策略是 LRU。
+ ++ flushInterval(重新整理間隔)屬性可以被設定為任意的正整數,設定的值應該是一個以毫秒為單位的合理時間量。 + 預設情況是不設定,也就是沒有重新整理間隔,快取僅僅會在呼叫語句時重新整理。 +
+ ++ size(參考數目)屬性可以被設定為任意正整數,要注意欲快取物件的大小和執行環境中可用的記憶體資源。預設值是 1024。 +
+ ++ readOnly(唯讀)屬性可以被設定為 true 或 false。唯讀的快取會給所有呼叫者返回快取物件的相同實例。 + 因此這些物件不能被修改。這就提供了可觀的效能提升。而可讀寫的快取會(透過序列化)返回快取物件的拷貝。 + 速度上會慢一些,但是更安全,因此預設值是 false。 +
+ ++ 提示 + 二級快取是事務性的。這意味著,當 SqlSession 完成並提交時,或是完成並回滾,但沒有執行 + flushCache=true 的 insert/delete/update 語句時,快取會獲得更新。 +
+ ++ 除了上述自訂快取的方式,你也可以透過實現你自己的快取,或為其他第三方快取方案建立介面卡,來完全覆蓋快取行為。 +
+ + + ++ 這個示例展示了如何使用一個自訂的快取實現。type 屬性指定的類別必須實現 + org.apache.ibatis.cache.Cache 介面,且提供一個接受 String 引數作為 id 的構造器。 + 這個介面是 MyBatis 框架中許多複雜的介面之一,但是行為卻非常簡單。 +
+ + + +
+ 為了對你的快取進行配置,只需要簡單地在你的快取實現中新增公有的 JavaBean 屬性,然後透過
+ cache 元素傳遞屬性值,例如,下面的例子將在你的快取實現上呼叫一個名為
+ setCacheFile(String file)
的方法:
+
+ 你可以使用所有簡單型別作為 JavaBean 屬性的型別,MyBatis 會進行轉換。
+ 你也可以使用佔位符(如 ${cache.file}
),以便替換成在配置檔案屬性中定義的值。
+
+ 從版本 3.4.2 開始,MyBatis 已經支援在所有屬性設定完畢之後,呼叫一個初始化方法。
+ 如果想要使用這個特性,請在你的自訂快取類別裡實現
+ org.apache.ibatis.builder.InitializingObject
介面。
+
提示 + 上一節中對快取的配置(如清除策略、可讀或可讀寫等),不能應用於自訂快取。 +
+ ++ 請注意,快取的配置和快取實例會被繫結到 SQL 對映檔案的名稱空間中。 + 因此,同一名稱空間中的所有語句和快取將透過名稱空間繫結在一起。 + 每條語句可以自訂與快取互動的方式,或將它們完全排除於快取之外,這可以透過在每條語句上使用兩個簡單屬性來達成。 + 預設情況下,語句會這樣來配置: +
+ + + ++ 鑑於這是預設行為,顯然你永遠不應該以這樣的方式顯式配置一條語句。但如果你想改變預設的行為,只需要設定 flushCache 和 useCache 屬性。比如,某些情況下你可能希望特定 select 語句的結果排除於快取之外,或希望一條 select 語句清空快取。類似地,你可能希望某些 update 語句執行時不要重新整理快取。 +
+ ++ 回想一下上一節的內容,對某一名稱空間的語句,只會使用該名稱空間的快取進行快取或重新整理。 + 但你可能會想要在多個名稱空間中共享相同的快取配置和實例。要實現這種需求,你可以使用 + cache-ref 元素來參考另一個快取。 +
+ + +Java 程式設計師面對的最痛苦的事情之一就是在 Java 程式碼中嵌入 SQL 語句。這通常是因為需要動態產生 SQL 語句,不然我們可以將它們放到外部檔案或者儲存過程中。如你所見,MyBatis 在 XML 對映中具備強大的 SQL 動態產生能力。但有時,我們還是需要在 Java 程式碼裡建構 SQL 語句。此時,MyBatis 有另外一個特性可以幫到你,讓你從處理典型問題中解放出來,比如加號、引號、換行、格式化問題、嵌入條件的逗號管理及 AND 連線。確實,在 Java 程式碼中動態產生 SQL 程式碼真的就是一場噩夢。例如: +
+ + +MyBatis 3 提供了方便的工具類別來幫助解決此問題。藉助 SQL 類別,我們只需要簡單地建立一個實例,並呼叫它的方法即可產生 SQL 語句。讓我們來用 SQL 類別重寫上面的例子: +
+ + + +這個例子有什麼特別之處嗎?仔細看一下你會發現,你不用擔心可能會重複出現的 "AND" 關鍵字,或者要做出用 "WHERE" 拼接還是 "AND" 拼接還是不用拼接的選擇。SQL 類別已經為你處理了哪裡應該插入 "WHERE"、哪裡應該使用 "AND" 的問題,並幫你完成所有的字串拼接工作。 +
+ +這裡有一些示例:
+ + + +方法 | +描述 | +
---|---|
+
|
+ 開始新的或追加到已有的 SELECT 子句。可以被多次呼叫,引數會被追加到 SELECT 子句。
+ 引數通常使用逗號分隔的列名和別名列表,但也可以是資料庫驅動程式接受的任意引數。
+ |
+
+
|
+ 開始新的或追加到已有的 SELECT 子句,並新增 DISTINCT 關鍵字到產生的查詢中。可以被多次呼叫,引數會被追加到 SELECT 子句。
+ 引數通常使用逗號分隔的列名和別名列表,但也可以是資料庫驅動程式接受的任意引數。
+ |
+
+
|
+ 開始新的或追加到已有的 FROM 子句。可以被多次呼叫,引數會被追加到
+ FROM 子句。
+ 引數通常是一個表名或別名,也可以是資料庫驅動程式接受的任意引數。
+ |
+
+
|
+ 基於呼叫的方法,新增新的合適型別的 JOIN 子句。
+ 引數可以包含一個由列和連線條件構成的標準連線。
+ |
+
+
|
+ 插入新的 WHERE 子句條件,並使用 AND 拼接。可以被多次呼叫,對於每一次呼叫產生的新條件,會使用 AND 拼接起來。要使用 OR 分隔,請使用 OR() 。
+ |
+
+ OR()
+ |
+ 使用 OR 來分隔當前的 WHERE 子句條件。
+ 可以被多次呼叫,但在一行中多次呼叫會產生錯誤的 SQL 。
+ |
+
+ AND()
+ |
+ 使用 AND 來分隔當前的 WHERE 子句條件。
+ 可以被多次呼叫,但在一行中多次呼叫會產生錯誤的 SQL 。由於 WHERE 和 HAVING 都會自動使用 AND 拼接, 因此這個方法並不常用,只是為了完整性才被定義出來。
+ |
+
+
|
+ 追加新的 GROUP BY 子句,使用逗號拼接。可以被多次呼叫,每次呼叫都會使用逗號將新的條件拼接起來。
+ |
+
+
|
+ 追加新的 HAVING 子句。使用 AND 拼接。可以被多次呼叫,每次呼叫都使用AND 來拼接新的條件。要使用 OR 分隔,請使用 OR() 。
+ |
+
+
|
+ 追加新的 ORDER BY 子句,使用逗號拼接。可以多次被呼叫,每次呼叫會使用逗號拼接新的條件。
+ |
+
+
|
+
+ 追加新的 LIMIT 子句。
+ 僅在 SELECT()、UPDATE()、DELETE() 時有效。
+ 當在 SELECT() 中使用時,應該配合 OFFSET() 使用。(於 3.5.2 引入)
+ |
+
+
|
+
+ 追加新的 OFFSET 子句。
+ 僅在 SELECT() 時有效。
+ 當在 SELECT() 時使用時,應該配合 LIMIT() 使用。(於 3.5.2 引入)
+ |
+
+
|
+
+ 追加新的 OFFSET n ROWS 子句。
+ 僅在 SELECT() 時有效。
+ 該方法應該配合 FETCH_FIRST_ROWS_ONLY() 使用。(於 3.5.2 加入)
+ |
+
+
|
+
+ 追加新的 FETCH FIRST n ROWS ONLY 子句。
+ 僅在 SELECT() 時有效。
+ 該方法應該配合 OFFSET_ROWS() 使用。(於 3.5.2 加入)
+ |
+
+ DELETE_FROM(String)
+ |
+ 開始新的 delete 語句,並指定刪除表的表名。通常它後面都會跟著一個 WHERE 子句! + | +
+ INSERT_INTO(String)
+ |
+ 開始新的 insert 語句,並指定插入資料表的表名。後面應該會跟著一個或多個 VALUES() 呼叫,或 INTO_COLUMNS() 和 INTO_VALUES() 呼叫。 + | +
+
|
+ 對 update 語句追加 "set" 屬性的列表 | +
+ UPDATE(String)
+ |
+ 開始新的 update 語句,並指定更新表的表名。後面都會跟著一個或多個 SET() 呼叫,通常也會有一個 WHERE() 呼叫。 + | +
+ VALUES(String, String)
+ |
+ 追加資料值到 insert 語句中。第一個引數是資料插入的列名,第二個引數則是資料值。 + | +
+ INTO_COLUMNS(String...)
+ |
+ + 追加插入列子句到 insert 語句中。應與 INTO_VALUES() 一同使用。 + | +
+ INTO_VALUES(String...)
+ |
+ + 追加插入值子句到 insert 語句中。應與 INTO_COLUMNS() 一同使用。 + | +
+ ADD_ROW()
+ |
+ + 新增新的一行資料,以便執行批量插入。(於 3.5.2 引入) + | +
+ 提示
+ 注意,SQL 類別將原樣插入 LIMIT
、OFFSET
、OFFSET n ROWS
以及 FETCH FIRST n ROWS ONLY
子句。換句話說,類別函式庫不會為不支援這些子句的資料庫執行任何轉換。
+ 因此,使用者應該要了解目標資料庫是否支援這些子句。如果目標資料庫不支援這些子句,產生的 SQL 可能會引起執行錯誤。
+
從版本 3.4.2 開始,你可以像下面這樣使用可變長度引數:
+ + +從版本 3.5.2 開始,你可以像下面這樣建構批量插入語句:
+ + + +從版本 3.5.2 開始,你可以像下面這樣建構限制返回結果數的 SELECT 語句,:
+ + + ++ 在版本 3.2 之前,我們的實現方式不太一樣,我們利用 ThreadLocal 變數來掩蓋一些對 Java DSL 不太友好的語言限制。現在,現代 SQL 建構框架使用的建構器和匿名內部類別思想已被人們所熟知。因此,我們廢棄了基於這種實現方式的 SelectBuilder 和 SqlBuilder 類別。 +
++ 下面的方法僅僅適用於廢棄的 SqlBuilder 和 SelectBuilder 類別。 +
+方法 | +描述 | +
---|---|
+ BEGIN()
+ /
+ RESET()
+ |
+ 這些方法清空 SelectBuilder 類別的 ThreadLocal 狀態,並準備好建構一個新的語句。開始新的語句時,BEGIN() 是最名副其實的(可讀性最好的)。但如果由於一些原因(比如程式邏輯在某些條件下需要一個完全不同的語句),在執行過程中要重置語句建構狀態,就很適合使用 RESET() 。
+ |
+
+ SQL()
+ |
+ 該方法返回產生的 SQL() 並重置 SelectBuilder 狀態(等價於呼叫了 BEGIN() 或 RESET() )。因此,該方法只能被呼叫一次!
+ |
+
+ SelectBuilder 和 SqlBuilder 類別並不神奇,但最好還是知道它們的工作原理。 + SelectBuilder 以及 SqlBuilder 藉助靜態匯入和 ThreadLocal 變數實現了對插入條件友好的簡潔語法。要使用它們,只需要靜態匯入這個類別的方法即可,就像這樣(只能使用其中的一條,不能同時使用): +
+ + + + +然後就可以像下面這樣建立一些方法:
+ + + + + +