Java 程序員面對(duì)的最痛苦的事情之一就是在 Java 代碼中嵌入 SQL 語(yǔ)句。這通常是因?yàn)樾枰獎(jiǎng)討B(tài)生成 SQL 語(yǔ)句,不然我們可以將它們放到外部文件或者存儲(chǔ)過(guò)程中。如你所見(jiàn),MyBatis 在 XML 映射中具備強(qiáng)大的 SQL 動(dòng)態(tài)生成能力。但有時(shí),我們還是需要在 Java 代碼里構(gòu)建 SQL 語(yǔ)句。此時(shí),MyBatis 有另外一個(gè)特性可以幫到你,讓你從處理典型問(wèn)題中解放出來(lái),比如加號(hào)、引號(hào)、換行、格式化問(wèn)題、嵌入條件的逗號(hào)管理及 AND 連接。確實(shí),在 Java 代碼中動(dòng)態(tài)生成 SQL 代碼真的就是一場(chǎng)噩夢(mèng)。例如:
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";
MyBatis 3 提供了方便的工具類來(lái)幫助解決此問(wèn)題。借助 SQL 類,我們只需要簡(jiǎn)單地創(chuàng)建一個(gè)實(shí)例,并調(diào)用它的方法即可生成 SQL 語(yǔ)句。讓我們來(lái)用 SQL 類重寫上面的例子:
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();
}
這個(gè)例子有什么特別之處嗎?仔細(xì)看一下你會(huì)發(fā)現(xiàn),你不用擔(dān)心可能會(huì)重復(fù)出現(xiàn)的 "AND" 關(guān)鍵字,或者要做出用 "WHERE" 拼接還是 "AND" 拼接還是不用拼接的選擇。SQL 類已經(jīng)為你處理了哪里應(yīng)該插入 "WHERE"、哪里應(yīng)該使用 "AND" 的問(wèn)題,并幫你完成所有的字符串拼接工作。
這里有一些示例:
// 匿名內(nèi)部類風(fēng)格
public String deletePersonSql() {
return new SQL() {{
DELETE_FROM("PERSON");
WHERE("ID = #{id}");
}}.toString();
}
// Builder / Fluent 風(fēng)格
public String insertPersonSql() {
String sql = new SQL()
.INSERT_INTO("PERSON")
.VALUES("ID, FIRST_NAME", "#{id}, #{firstName}")
.VALUES("LAST_NAME", "#{lastName}")
.toString();
return sql;
}
// 動(dòng)態(tài)條件(注意參數(shù)需要使用 final 修飾,以便匿名內(nèi)部類對(duì)它們進(jìn)行訪問(wèn))
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 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();
}
public String updatePersonSql() {
return new SQL() {{
UPDATE("PERSON");
SET("FIRST_NAME = #{firstName}");
WHERE("ID = #{id}");
}}.toString();
}
方法 | 描述 |
---|---|
|
開(kāi)始新的或追加到已有的 SELECT 子句??梢员欢啻握{(diào)用,參數(shù)會(huì)被追加到 SELECT 子句。 參數(shù)通常使用逗號(hào)分隔的列名和別名列表,但也可以是數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序接受的任意參數(shù)。 |
|
開(kāi)始新的或追加到已有的 SELECT 子句,并添加 DISTINCT 關(guān)鍵字到生成的查詢中??梢员欢啻握{(diào)用,參數(shù)會(huì)被追加到 SELECT 子句。 參數(shù)通常使用逗號(hào)分隔的列名和別名列表,但也可以是數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序接受的任意參數(shù)。 |
|
開(kāi)始新的或追加到已有的 FROM 子句??梢员欢啻握{(diào)用,參數(shù)會(huì)被追加到 FROM 子句。 參數(shù)通常是一個(gè)表名或別名,也可以是數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序接受的任意參數(shù)。 |
|
基于調(diào)用的方法,添加新的合適類型的 JOIN 子句。 參數(shù)可以包含一個(gè)由列和連接條件構(gòu)成的標(biāo)準(zhǔn)連接。 |
|
插入新的 WHERE 子句條件,并使用 AND 拼接??梢员欢啻握{(diào)用,對(duì)于每一次調(diào)用產(chǎn)生的新條件,會(huì)使用 AND 拼接起來(lái)。要使用 OR 分隔,請(qǐng)使用 OR() 。 |
OR()
|
使用 OR 來(lái)分隔當(dāng)前的 WHERE 子句條件。 可以被多次調(diào)用,但在一行中多次調(diào)用會(huì)生成錯(cuò)誤的 SQL 。 |
AND()
|
使用 AND 來(lái)分隔當(dāng)前的 WHERE 子句條件。 可以被多次調(diào)用,但在一行中多次調(diào)用會(huì)生成錯(cuò)誤的 SQL 。由于 WHERE 和 HAVING 都會(huì)自動(dòng)使用 AND 拼接, 因此這個(gè)方法并不常用,只是為了完整性才被定義出來(lái)。 |
|
追加新的 GROUP BY 子句,使用逗號(hào)拼接??梢员欢啻握{(diào)用,每次調(diào)用都會(huì)使用逗號(hào)將新的條件拼接起來(lái)。 |
|
追加新的 HAVING 子句。使用 AND 拼接。可以被多次調(diào)用,每次調(diào)用都使用AND 來(lái)拼接新的條件。要使用 OR 分隔,請(qǐng)使用 OR() 。 |
|
追加新的 ORDER BY 子句,使用逗號(hào)拼接??梢远啻伪徽{(diào)用,每次調(diào)用會(huì)使用逗號(hào)拼接新的條件。 |
|
追加新的 LIMIT 子句。 僅在 SELECT()、UPDATE()、DELETE() 時(shí)有效。 當(dāng)在 SELECT() 中使用時(shí),應(yīng)該配合 OFFSET() 使用。(于 3.5.2 引入) |
|
追加新的 OFFSET 子句。 僅在 SELECT() 時(shí)有效。 當(dāng)在 SELECT() 時(shí)使用時(shí),應(yīng)該配合 LIMIT() 使用。(于 3.5.2 引入) |
|
追加新的 OFFSET n ROWS 子句。 僅在 SELECT() 時(shí)有效。 該方法應(yīng)該配合 FETCH_FIRST_ROWS_ONLY() 使用。(于 3.5.2 加入) |
|
追加新的 FETCH FIRST n ROWS ONLY 子句。 僅在 SELECT() 時(shí)有效。 該方法應(yīng)該配合 OFFSET_ROWS() 使用。(于 3.5.2 加入) |
DELETE_FROM(String)
|
開(kāi)始新的 delete 語(yǔ)句,并指定刪除表的表名。通常它后面都會(huì)跟著一個(gè) WHERE 子句! |
INSERT_INTO(String)
|
開(kāi)始新的 insert 語(yǔ)句,并指定插入數(shù)據(jù)表的表名。后面應(yīng)該會(huì)跟著一個(gè)或多個(gè) VALUES() 調(diào)用,或 INTO_COLUMNS() 和 INTO_VALUES() 調(diào)用。 |
|
對(duì) update 語(yǔ)句追加 "set" 屬性的列表 |
UPDATE(String)
|
開(kāi)始新的 update 語(yǔ)句,并指定更新表的表名。后面都會(huì)跟著一個(gè)或多個(gè) SET() 調(diào)用,通常也會(huì)有一個(gè) WHERE() 調(diào)用。 |
VALUES(String, String)
|
追加數(shù)據(jù)值到 insert 語(yǔ)句中。第一個(gè)參數(shù)是數(shù)據(jù)插入的列名,第二個(gè)參數(shù)則是數(shù)據(jù)值。 |
INTO_COLUMNS(String...)
|
追加插入列子句到 insert 語(yǔ)句中。應(yīng)與 INTO_VALUES() 一同使用。 |
INTO_VALUES(String...)
|
追加插入值子句到 insert 語(yǔ)句中。應(yīng)與 INTO_COLUMNS() 一同使用。 |
ADD_ROW()
|
添加新的一行數(shù)據(jù),以便執(zhí)行批量插入。(于 3.5.2 引入) |
注意,SQL 類將原樣插入 ?LIMIT
?、?OFFSET
?、?OFFSET n ROWS
? 以及 ?FETCH FIRST n ROWS ONLY
? 子句。換句話說(shuō),類庫(kù)不會(huì)為不支持這些子句的數(shù)據(jù)庫(kù)執(zhí)行任何轉(zhuǎn)換。 因此,用戶應(yīng)該要了解目標(biāo)數(shù)據(jù)庫(kù)是否支持這些子句。如果目標(biāo)數(shù)據(jù)庫(kù)不支持這些子句,產(chǎn)生的 SQL 可能會(huì)引起運(yùn)行錯(cuò)誤。
從版本 3.4.2 開(kāi)始,你可以像下面這樣使用可變長(zhǎng)度參數(shù):
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();
}
從版本 3.5.2 開(kāi)始,你可以像下面這樣構(gòu)建批量插入語(yǔ)句:
public String insertPersonsSql() {
// INSERT INTO PERSON (ID, FULL_NAME)
// VALUES (#{mainPerson.id}, #{mainPerson.fullName}) , (#{subPerson.id}, #{subPerson.fullName})
return new SQL()
.INSERT_INTO("PERSON")
.INTO_COLUMNS("ID", "FULL_NAME")
.INTO_VALUES("#{mainPerson.id}", "#{mainPerson.fullName}")
.ADD_ROW()
.INTO_VALUES("#{subPerson.id}", "#{subPerson.fullName}")
.toString();
}
從版本 3.5.2 開(kāi)始,你可以像下面這樣構(gòu)建限制返回結(jié)果數(shù)的 SELECT 語(yǔ)句,:
public String selectPersonsWithOffsetLimitSql() {
// SELECT id, name FROM PERSON
// LIMIT #{limit} OFFSET #{offset}
return new SQL()
.SELECT("id", "name")
.FROM("PERSON")
.LIMIT("#{limit}")
.OFFSET("#{offset}")
.toString();
}
public String selectPersonsWithFetchFirstSql() {
// SELECT id, name FROM PERSON
// OFFSET #{offset} ROWS FETCH FIRST #{limit} ROWS ONLY
return new SQL()
.SELECT("id", "name")
.FROM("PERSON")
.OFFSET_ROWS("#{offset}")
.FETCH_FIRST_ROWS_ONLY("#{limit}")
.toString();
}
在版本 3.2 之前,我們的實(shí)現(xiàn)方式不太一樣,我們利用 ?ThreadLocal
?變量來(lái)掩蓋一些對(duì) Java DSL 不太友好的語(yǔ)言限制。現(xiàn)在,現(xiàn)代 SQL 構(gòu)建框架使用的構(gòu)建器和匿名內(nèi)部類思想已被人們所熟知。因此,我們廢棄了基于這種實(shí)現(xiàn)方式的 ?SelectBuilder
?和 ?SqlBuilder
?類。
下面的方法僅僅適用于廢棄的 ?SqlBuilder
?和 ?SelectBuilder
?類。
方法 | 描述 |
---|---|
BEGIN() / RESET()
|
這些方法清空 SelectBuilder 類的 ThreadLocal 狀態(tài),并準(zhǔn)備好構(gòu)建一個(gè)新的語(yǔ)句。開(kāi)始新的語(yǔ)句時(shí),BEGIN() 是最名副其實(shí)的(可讀性最好的)。但如果由于一些原因(比如程序邏輯在某些條件下需要一個(gè)完全不同的語(yǔ)句),在執(zhí)行過(guò)程中要重置語(yǔ)句構(gòu)建狀態(tài),就很適合使用 RESET() 。 |
SQL()
|
該方法返回生成的 SQL() 并重置 SelectBuilder 狀態(tài)(等價(jià)于調(diào)用了 BEGIN() 或 RESET() )。因此,該方法只能被調(diào)用一次! |
?SelectBuilder
?和 ?SqlBuilder
?類并不神奇,但最好還是知道它們的工作原理。 ?SelectBuilder
?以及 ?SqlBuilder
?借助靜態(tài)導(dǎo)入和 ?ThreadLocal
?變量實(shí)現(xiàn)了對(duì)插入條件友好的簡(jiǎn)潔語(yǔ)法。要使用它們,只需要靜態(tài)導(dǎo)入這個(gè)類的方法即可,就像這樣(只能使用其中的一條,不能同時(shí)使用):
import static org.apache.ibatis.jdbc.SelectBuilder.*;
import static org.apache.ibatis.jdbc.SqlBuilder.*;
然后就可以像下面這樣創(chuàng)建一些方法:
/* 已被廢棄 */
public String selectBlogsSql() {
BEGIN(); // 重置 ThreadLocal 狀態(tài)變量
SELECT("*");
FROM("BLOG");
return SQL();
}
/* 已被廢棄 */
private String selectPersonSql() {
BEGIN(); // 重置 ThreadLocal 狀態(tài)變量
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");
return SQL();
}
更多建議: