Mybatis基本用法--中
第四部分 動態 SQL
動態 SQL 元素和使用 JSTL 或其他類似基於 XML 的文本處理器相似。MyBatis 采用功能強大的基於 OGNL 的表達式來消除其他元素。
if
choose (when, otherwise)
trim (where, set)
foreach
4.1 if
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
注意這種情況傳入的title中應該包含%
或者_
,否則,可以利用下面的bind
元素。like模糊查詢中,%
通配任意字符,_
通配一個字符。
4.2 choose, when, otherwise
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
有點類似於Java 中的 switch 語句,但它不需要break也只會執行一種情況或者不執行(沒有匹配的)。
4.3 trim, where, set
對於“if”示例,這次我們將“ACTIVE = 1”也設置成動態的條件,看看會發生什么。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
此時,若一個都沒匹配上
SELECT * FROM BLOG
WHERE
若只匹配第二個條件,則會多一個AND
。此時可以利用<where>
元素。where 元素知道只有在一個以上的if條件有值的情況下才去插入“WHERE”子句。而且,若最后的內容是“AND”或“OR”開頭的,where 元素也知道如何將他們去除。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
類似的用於動態更新語句的解決方案叫做 set。set 元素可以被用於動態包含需要更新的列,而舍去其他的。比如:
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
這里,set 元素會動態前置 SET 關鍵字,同時也會消除無關的逗號,因為用了條件語句之后很可能就會在生成的賦值語句的后面留下這些逗號。
其實我們可以自定義 trim 元素來定制我們想要的功能,比如,和 where 元素等價的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
prefixOverrides 屬性會忽略通過管道分隔的文本序列(注意此例中的空格也是必要的)。它帶來的結果就是所有在 prefixOverrides 屬性中指定的內容將被移除,並且插入 prefix 屬性中指定的內容。
set 元素等價的自定義 trim 元素,注意這里我們忽略的是后綴中的值:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
4.4 foreach
動態 SQL 的另外一個常用的必要操作是需要對一個集合進行遍歷,通常是在構建 IN 條件語句的時候。比如:
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
它也允許你指定開閉匹配的字符串以及在迭代中間放置分隔符。你可以將任何可迭代對象(如列表、集合等)和任何的字典或者數組對象傳遞給foreach作為集合參數。當使用可迭代對象或者數組時,index是當前迭代的次數,item的值是本次迭代獲取的元素。當使用字典(或者Map.Entry對象的集合)時,index是鍵,item是值。
4.5 bind
bind 元素可以從 OGNL 表達式中創建一個變量並將其綁定到上下文。比如:
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
第五部分 Java API
5.1 語句執行方法(不推薦,推薦使用5.6和5.7)
這些方法被用來執行定義在 SQL 映射的 XML 文件中的 SELECT,INSERT,UPDA E T 和 DELETE 語句。它們都會自行解釋,每一句都使用語句的 ID 屬性和參數對象,參數可以 是原生類型(自動裝箱或包裝類) ,JavaBean,POJO 或 Map。
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
selectOne 和 selectList 的不同僅僅是 selectOne 必須返回一個對象。 如果多於一個, 或者沒有返回 (或返回了 null) 那么就會拋出異常。 如果你不知道需要多少對象, 使用 selectList。
如果你想檢查一個對象是否存在,那么最好返回統計數(0 或 1) 。因為並不是所有語句都需 要參數,這些方法都是有不同重載版本的,它們可以不需要參數對象。
<T> T selectOne(String statement)
<E> List<E> selectList(String statement)
<K,V> Map<K,V> selectMap(String statement, String mapKey)
int insert(String statement)
int update(String statement)
int delete(String statement)
最后,還有查詢方法的三個高級版本,它們允許你限制返回行數的范圍,或者提供自定 義結果控制邏輯,這通常用於大量的數據集合。
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
RowBounds 參數會告訴 MyBatis 略過指定數量的記錄,還有限制返回結果的數量。 RowBounds 類有一個構造方法來接收 offset 和 limit,否則是不可改變的。
int offset = 100;
int limit = 25;
RowBounds rowBounds = new RowBounds(offset, limit);
ResultHandler 參數允許你按你喜歡的方式處理每一行。ResultHandler接口很簡單
package org.apache.ibatis.session;
public interface ResultHandler<T> {
void handleResult(ResultContext<? extends T> context);
}
5.2 批量立即更新方法(Flush Method)
刷新(執行)存儲在JDBC驅動類中的批量更新語句
List<BatchResult> flushStatements()
5.3 事務控制方法
控制事務作用域有四個方法。 當然, 如果你已經選擇了自動提交或你正在使用外部事務管理器,這就沒有任何效果了。然而,如果你正在使用 JDBC 事務管理器,由 Connection 實例來控制,那么這四個方法就會派上用場:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
注意MyBatis-Spring和MyBatis-Guice提供了聲明事務處理,所以如果你在使用Mybatis的同時使用了Spring或者Guice,那么請參考它們的手冊以獲取更多的內容。
5.4 清理 Session 級的緩存
void clearCache()
5.5 確保 SqlSession 被關閉
SqlSession session = sqlSessionFactory.openSession();
try {
// following 3 lines pseudocod for "doing some work"
session.insert(...);
session.update(...);
session.delete(...);
session.commit();
} finally {
session.close();
}
5.6 使用映射器
上述的各個 insert,update,delete 和 select 方法都很強大,但也有些繁瑣,沒有類型安全,對於你的 IDE 和可能的單元測試也沒有幫助。因此, 一個更通用的方式來執行映射語句是使用映射器類。推薦使用5.6中的映射器和5.7的映射器注解
一個映射器類就是一個簡單 的接口,其中的方法定義匹配於 SqlSession 方法。
//注釋中的語句對應SqlSession 方法,不推薦!
public interface AuthorMapper {
// (Author) selectOne("selectAuthor",5);
Author selectAuthor(int id);
// (List<Author>) selectList(“selectAuthors”)
List<Author> selectAuthors();
// (Map<Integer,Author>) selectMap("selectAuthors", "id")
@MapKey("id")
Map<Integer, Author> selectAuthors();
// insert("insertAuthor", author)
int insertAuthor(Author author);
// updateAuthor("updateAuthor", author)
int updateAuthor(Author author);
// delete("deleteAuthor",5)
int deleteAuthor(int id);
}
- 方法名必須匹配映射語句的 ID。返回類型必須匹配期望的結果類型。所有常用的類型都是支持的,包括:原生類型,Map,POJO 和 JavaBean。
- 映射器接口不需要去實現任何接口或擴展任何類。
- 你可以傳遞多個參數給一個映射器方法。 如果你這樣做了, 默認情況下它們將會以它們 在參數列表中的位置來命名,比如:#{param1},#{param2}等。如果你想改變參數的名稱(只在多參數情況下) ,可以在參數上使用
@Param("paramName")
注解。 - 你也可以給方法傳遞一個 RowBounds 實例來限制查詢結果。
5.7 映射器注解
注解有下面這些:
注解 | 目標 | 相對應的 XML | 描述 |
---|---|---|---|
@CacheNamespace | 類 | <cache> |
為給定的命名空間 (比如類) 配置緩存。 屬性:implemetation,eviction, flushInterval,size,readWrite,blocking 和 properties。 |
@Property | N/A | <property> |
屬性名和屬性位置 |
@ConstructorArgs | 方法 | <constructor> |
收集一組結果傳遞給一個對象的 構造方法。屬性:value,是數組形式的參數。 |
@Case | N/A | <case> |
值和它對應的映射的簡單情況。屬性: value,type,results。Results 屬性是結 果數組,因此這個注解和實際的 ResultMap 很相似,由下面的 Results 注解指定。 |
@Results | 方法 | <resultMap> |
Result映射的列表, 包含列如何被映射到屬性或字段的詳情。 屬 性:value, id。value 屬性是 Result 注解的數組。這個id的屬性是結果映射的名稱。 |
@Result | N/A | <result> ,<id> |
在列和屬性或字段之間的單獨結果映射。屬 性:id,column, property, javaType ,jdbcType ,type Handler, one,many。id 屬性是一個布爾值,表 示了應該被用於比較(和在 XML 映射 中的
<association> 相 似 , 而 many 屬 性 是 對 集 合 而 言 的 , 和
<collection> 相似。 它們這樣命名是為了 避免名稱沖突。
|
@One | N/A | <association> |
復雜類型的單獨屬性值映射。屬性: select,已映射語句(也就是映射器方 法)的完全限定名,它可以加載合適類 型的實例。 |
@Many | N/A | <collection> |
映射到復雜類型的集合屬性。屬性:select,已映射語句(也就是映射器方法)的全限定名, 它可以加載合適類型的實例的集合 |
@Options | 方法 | 映射語句的屬性 | 它們通常在映射語句上作為 屬性出現。屬性:useCache=true , flushCache=FlushCachePolicy.DEFAULT , resultSetType=FORWARD_ONLY , statementType=PREPARED , fetchSize=-1 , timeout=-1 useGeneratedKeys=false , keyProperty=”id” , keyColumn=”” , resultSets=””。 |
@Insert @Update @Delete @Select |
方法 | <insert> ,<update> ,<delete> ,<select> |
這些注解中的每一個代表了執行的真實 SQL。 |
@Param | Parameter | N/A | 映射器的方法參數命名,默認:#{param1} , #{param2} 等 。 使 用 @Param(“person”) ,參數應該被命名為 #{person}。 |
@ResultMap | 方法 | N/A | 給@Select或者@SelectProvider提供在XML映射中的<resultMap> 的id。 |
@InsertProvider @UpdateProvider @DeleteProvider @SelectProvider |
Method | <insert>``<update>``<delete>``<select> |
允許你指定一個 類名和一個方法在運行時動態創建SQL。 |
但其實:Java 注解限制了它們的表現和靈活,所以簡單的方法使用注解,復雜的方法使用xml配置。
例子:
@Results(id = "userResult", value = {
@Result(property = "id", column = "uid", id = true),
@Result(property = "firstName", column = "first_name"),
@Result(property = "lastName", column = "last_name")
})
@Select("select * from users where id = #{id}")
User getUserById(Integer id);
@Results(id = "companyResults")
@ConstructorArgs({
@Arg(property = "id", column = "cid", id = true),
@Arg(property = "name", column = "name")
})
@Select("select * from company where id = #{id}")
Company getCompanyById(Integer id);
@SelectProvider(type = UserSqlBuilder.class, method = "buildGetUsersByName")
List<User> getUsersByName(
@Param("name") String name, @Param("orderByColumn") String orderByColumn);
class UserSqlBuilder {
// If not use @Param, you should be define same arguments with mapper method
public String buildGetUsersByName(
final String name, final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
// If use @Param, you can define only arguments to be used
public String buildGetUsersByName(@Param("orderByColumn") final String orderByColumn) {
return new SQL(){{
SELECT("*");
FROM("users");
WHERE("name like #{name} || '%'");
ORDER_BY(orderByColumn);
}}.toString();
}
}
注意上面的||
是字符串連接符,在MySQL中,一般用concat函數。
第六部分 SQL語句構建器類
過去我們在Java中寫sql語句:
String sql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, "
"P.LAST_NAME,P.CREATED_ON, P.UPDATED_ON " +
"FROM PERSON P, ACCOUNT A " +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID " +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID " +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) " +
"OR (P.LAST_NAME like ?) " +
"GROUP BY P.ID " +
"HAVING (P.LAST_NAME like ?) " +
"OR (P.FIRST_NAME like ?) " +
"ORDER BY P.ID, P.FULL_NAME";
現在(優先用下面的變長參數方法):
private String selectPersonSql() {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.FIRST_NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}}.toString();
}
給出一些例子:
// With conditionals (note the final parameters, required for the anonymous inner class to access them)
public String selectPersonLike(final String id, final String firstName, final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID like #{id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
public String updatePersonSql() {
return new SQL() {{
UPDATE("PERSON");
SET("FIRST_NAME = #{firstName}");
WHERE("ID = #{id}");
}}.toString();
}
public String deletePersonSql() {
return new SQL() {{
DELETE_FROM("PERSON");
WHERE("ID = #{id}");
}}.toString();
}
public String insertPersonSql() {
return new SQL() {{
INSERT_INTO("PERSON");
VALUES("ID, FIRST_NAME", "#{id}, #{firstName}");
VALUES("LAST_NAME", "#{lastName}");
}}.toString();
}
方法 | 描述 |
---|---|
SELECT(String...) | 參數是使用逗號分隔的列名和別名列表,但也可以是數據庫驅動程序接受的任意類型。 |
FROM(String...) | from |
JOIN(String...) INNER_JOIN(String...) LEFT_OUTER_JOIN(String) LEFT_OUTER_JOIN(String...) RIGHT_OUTER_JOIN(String) RIGHT_OUTER_JOIN(String...) |
參數可以包含由列命和join on條件組合成標准的join。 |
WHERE(String...) | 插入新的 WHERE子句條件,由AND鏈接。可以多次被調用,每次都由AND來鏈接新條件。使用 OR() 來分隔OR。 |
OR() | 使用OR來分隔當前的 WHERE子句條件。 可以被多次調用,但在一行中多次調用或生成不穩定的SQL。 |
AND() | 使用AND來分隔當前的 WHERE子句條件。 可以被多次調用,但在一行中多次調用可能生成不穩定的SQL。因為 WHERE 和 HAVING 二者都會自動鏈接 AND,只是為了完整性才被使用,一般不用主動用。 |
GROUP_BY(String...) | 插入新的 GROUP BY子句元素,由逗號連接。 可以被多次調用,每次都由逗號連接新的條件。 |
HAVING(String...) | 插入新的 HAVING子句條件。 由AND連接。可以被多次調用,每次都由AND來連接新的條件。使用 OR() 來分隔OR. |
ORDER_BY(String...) | 插入新的 ORDER BY子句元素, 由逗號連接。可以多次被調用,每次由逗號連接新的條件。 |
DELETE_FROM(String) | 開始一個delete語句並指定需要從哪個表刪除的表名。通常它后面都會跟着WHERE語句! |
INSERT_INTO(String) | 開始一個insert語句並指定需要插入數據的表名。后面都會跟着一個或者多個VALUES() or INTO_COLUMNS() and INTO_VALUES()。 |
SET(String...) | 針對update語句,插入到"set"列表中 |
UPDATE(String) | 開始一個update語句並指定需要更新的表名。后面都會跟着一個或者多個SET(),通常也會有一個WHERE()。 |
VALUES(String, String) | 插入到insert語句中。第一個參數是要插入的列名,第二個參數則是該列的值。 |
INTO_COLUMNS(String...) | Appends columns phrase to an insert statement. This should be call INTO_VALUES() with together. |
INTO_VALUES(String...) | Appends values phrase to an insert statement. This should be call INTO_COLUMNS() with together. |
從版本3.4.2,可以使用變長參數:
public String selectPersonSql() {
return new SQL()
.SELECT("P.ID", "A.USERNAME", "A.PASSWORD", "P.FULL_NAME", "D.DEPARTMENT_NAME", "C.COMPANY_NAME")
.FROM("PERSON P", "ACCOUNT A")
.INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID", "COMPANY C on D.COMPANY_ID = C.ID")
.WHERE("P.ID = A.ID", "P.FULL_NAME like #{name}")
.ORDER_BY("P.ID", "P.FULL_NAME")
.toString();
}
public String insertPersonSql() {
return new SQL()
.INSERT_INTO("PERSON")
.INTO_COLUMNS("ID", "FULL_NAME")
.INTO_VALUES("#{id}", "#{fullName}")
.toString();
}
public String updatePersonSql() {
return new SQL()
.UPDATE("PERSON")
.SET("FULL_NAME = #{fullName}", "DATE_OF_BIRTH = #{dateOfBirth}")
.WHERE("ID = #{id}")
.toString();
}
第七部分 Logging
Mybatis內置的日志工廠提供日志功能,具體的日志實現有以下幾種工具:
SLF4J
Apache Commons Logging
Log4j 2
Log4j
JDK logging
具體選擇哪個日志實現工具由MyBatis的內置日志工廠確定。它會使用最先找到的(按上文列舉的順序查找)。 如果一個都未找到,日志功能就會被禁用。
你可以通過在MyBatis的配置文件mybatis-config.xml里面添加一項setting(配置)來選擇一個不同的日志實現,logImpl可選的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING 或者是實現了接口org.apache.ibatis.logging.Log的類的完全限定類名。
<configuration>
<settings>
...
<setting name="logImpl" value="LOG4J"/>
...
</settings>
</configuration>
Logging Configuration
步驟1: 添加 Log4J 的 jar 包
步驟2:配置Log4J
在應用的classpath中創建一個名稱為log4j.properties的文件, 文件的具體內容如下:
# Global logging configuration
log4j.rootLogger=ERROR, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
添加以上配置后,Log4J就會把 org.mybatis.example.BlogMapper 的詳細執行日志記錄下來,對於應用中的其它類則僅僅記錄錯誤信息。
也可以將日志從整個mapper接口級別調整到到語句級別,從而實現更細粒度的控制。如下配置只記錄 selectBlog 語句的日志:
log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE
與此相對,可以對一組mapper接口記錄日志,只要對mapper接口所在的包開啟日志功能即可:
log4j.logger.org.mybatis.example=TRACE
某些查詢可能會返回大量的數據,只想記錄其執行的SQL語句該怎么辦?為此,Mybatis中SQL語 句的日志級別被設為DEBUG(JDK Logging中為FINE),結果日志的級別為TRACE(JDK Logging中為FINER)。所以,只要將日志級別調整為DEBUG即可達到目的:
log4j.logger.org.mybatis.example=DEBUG
要記錄日志的是類似下面的mapper文件而不是mapper接口又該怎么呢?
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
對這個文件記錄日志,只要對命名空間增加日志記錄功能即可:
log4j.logger.org.mybatis.example.BlogMapper=TRACE
進一步,要記錄具體語句的日志可以這樣做:
log4j.logger.org.mybatis.example.BlogMapper.selectBlog=TRACE