MyBatis Dynamic SQL 入門(mén)指南
MyBatis Dynamic SQL 是一種類(lèi)型安全的 Java 領(lǐng)域特定語(yǔ)言(DSL),用于通過(guò)編程方式構(gòu)建 SQL 查詢,而非編寫(xiě) SQL 字符串或基于 XML 的動(dòng)態(tài)查詢。它在運(yùn)行時(shí)使用流暢的 Java 構(gòu)建器生成 SQL,同時(shí)仍通過(guò)標(biāo)準(zhǔn)的 MyBatis 映射器執(zhí)行。與手動(dòng)拼接字符串或復(fù)雜的 XML 邏輯相比,這使得查詢構(gòu)建更安全、更易于重構(gòu),并且更不容易出錯(cuò)。
由于查詢是用 Java 編寫(xiě)的,列名和表引用通過(guò)強(qiáng)類(lèi)型的元數(shù)據(jù)類(lèi)在編譯時(shí)進(jìn)行驗(yàn)證,這提供了更好的 IDE 支持并減少了運(yùn)行時(shí) SQL 錯(cuò)誤。本文將解釋 MyBatis Dynamic SQL,并展示如何在 Java 應(yīng)用程序中使用它。
1. 使用 MyBatis Dynamic SQL 可以做什么?
MyBatis Dynamic SQL 支持大多數(shù)常見(jiàn)的 SQL 操作,包括 SELECT、INSERT、UPDATE 和 DELETE,以及連接、子查詢、分頁(yè)、排序、條件過(guò)濾和批量操作。它允許我們逐步構(gòu)建查詢,僅在某些參數(shù)存在時(shí)添加條件,這使其成為搜索界面和過(guò)濾 API 的理想選擇。
它直接與 MyBatis 映射器接口集成,意味著我們?nèi)匀豢梢允芤嬗诮Y(jié)果映射、事務(wù)處理和連接管理。由于它生成標(biāo)準(zhǔn)的 SQL,因此適用于 MyBatis 支持的任何數(shù)據(jù)庫(kù),沒(méi)有供應(yīng)商鎖定的問(wèn)題。
1.1 MyBatis Dynamic SQL 的工作原理
Dynamic SQL 基于兩個(gè)組件:表元數(shù)據(jù)類(lèi)和 DSL 構(gòu)建器。元數(shù)據(jù)類(lèi)用 Java 描述表和列。DSL 構(gòu)建器使用這些類(lèi)以流暢、類(lèi)型安全的方式組裝 SQL 語(yǔ)句。
DSL 不直接執(zhí)行 SQL。相反,它生成語(yǔ)句提供者對(duì)象,例如 SelectStatementProvider 或 InsertStatementProvider。這些對(duì)象被傳遞給使用 @SelectProvider、@InsertProvider 及類(lèi)似注解標(biāo)注的映射器方法,然后 MyBatis 使用其正常的執(zhí)行引擎來(lái)執(zhí)行這些語(yǔ)句。
2. 項(xiàng)目設(shè)置與依賴
Maven 依賴
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.15</version> </dependency> <dependency> <groupId>org.mybatis.dynamic-sql</groupId> <artifactId>mybatis-dynamic-sql</artifactId> <version>1.5.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.4.240</version> <scope>runtime</scope> </dependency>
這些依賴項(xiàng)包括 MyBatis 本身、Dynamic SQL DSL 以及一個(gè)用于測(cè)試的嵌入式數(shù)據(jù)庫(kù)。
注意
數(shù)據(jù)庫(kù)驅(qū)動(dòng)可以替換為 MySQL、PostgreSQL 或任何其他支持的數(shù)據(jù)庫(kù)。MyBatis Dynamic SQL 不依賴于數(shù)據(jù)庫(kù)類(lèi)型,僅依賴于標(biāo)準(zhǔn) SQL 生成。
數(shù)據(jù)庫(kù)模式
schema.sql
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT
);
INSERT INTO users(username, email, age) VALUES
('thomas', 'thomas@jcg.com', 30),
('benjamin', 'benjamin@jcg.com', 22),
('charles', 'charles@jcg.com', 17);此腳本創(chuàng)建一個(gè)簡(jiǎn)單的表并插入測(cè)試數(shù)據(jù)。內(nèi)存中的 H2 數(shù)據(jù)庫(kù)將在啟動(dòng)時(shí)執(zhí)行此腳本,因此無(wú)需外部依賴即可測(cè)試查詢。
領(lǐng)域模型
public class User {
private Long id;
private String username;
private String email;
private Integer age;
// Getter 和 Setter 方法...
}這個(gè) POJO 代表數(shù)據(jù)庫(kù)中的一行。MyBatis 會(huì)自動(dòng)使用匹配的字段名將列映射到字段,因此本例不需要額外的結(jié)果映射。
3. 用于 Dynamic SQL 的表元數(shù)據(jù)
下面的類(lèi)以類(lèi)型安全的方式定義數(shù)據(jù)庫(kù)表及其列,允許在構(gòu)建查詢時(shí)被 Dynamic SQL DSL 引用。它充當(dāng) Java 代碼與實(shí)際數(shù)據(jù)庫(kù)結(jié)構(gòu)之間的橋梁,實(shí)現(xiàn)了列名和類(lèi)型的編譯時(shí)驗(yàn)證。
public final class UserDynamicSqlSupport {
public static final User user = new User();
public static final SqlColumn<Long> id = user.id;
public static final SqlColumn<String> username = user.username;
public static final SqlColumn<String> email = user.email;
public static final SqlColumn<Integer> age = user.age;
public static final class User extends SqlTable {
public final SqlColumn<Long> id = column("id", JDBCType.BIGINT);
public final SqlColumn<String> username = column("username", JDBCType.VARCHAR);
public final SqlColumn<String> email = column("email", JDBCType.VARCHAR);
public final SqlColumn<Integer> age = column("age", JDBCType.INTEGER);
public User() {
super("users");
}
}
}這個(gè)類(lèi)為 users 表定義了類(lèi)型安全的元數(shù)據(jù),使 MyBatis Dynamic SQL 可以在不使用原始 SQL 字符串的情況下構(gòu)建查詢。DSL 使用這些 Java 對(duì)象而非按名稱引用列,從而提高了安全性和 IDE 支持。
內(nèi)部類(lèi) User 繼承 SqlTable,這將其標(biāo)記為可用于 from(user) 和連接等子句的數(shù)據(jù)表。構(gòu)造函數(shù)調(diào)用 super("users") 來(lái)告知 MyBatis 要在 SQL 語(yǔ)句(如 FROM users)中呈現(xiàn)的確切表名。
每個(gè)列都使用 SqlTable 中的 column() 方法定義,該方法注冊(cè)列名及其 JDBC 類(lèi)型。這會(huì)產(chǎn)生強(qiáng)類(lèi)型的 SqlColumn<T> 對(duì)象,確保比較和條件在編譯時(shí)使用正確的 Java 類(lèi)型。
外部類(lèi)公開(kāi)了對(duì)表及其列的靜態(tài)引用,以便于靜態(tài)導(dǎo)入,使得查詢讀起來(lái)很自然,例如:select(id, username).from(user),同時(shí)保持完全的類(lèi)型安全和重構(gòu)友好。
映射器接口
@Mapper
public interface UserMapper {
@SelectProvider(type = SqlProviderAdapter.class, method = "select")
List<User> selectMany(SelectStatementProvider selectStatement);
}@Mapper 注解告訴 MyBatis 此接口應(yīng)注冊(cè)為映射器并在運(yùn)行時(shí)進(jìn)行代理。MyBatis 會(huì)自動(dòng)生成實(shí)現(xiàn),因此不需要具體的類(lèi)。
selectMany 方法接受一個(gè) SelectStatementProvider,它封裝了完全呈現(xiàn)的 SQL 語(yǔ)句及其參數(shù)。MyBatis 執(zhí)行該語(yǔ)句并將每個(gè)結(jié)果行映射到 User 對(duì)象,將它們作為 List<User> 返回。
@SelectProvider 注解指定 SQL 將由 MyBatis Dynamic SQL 的一部分 SqlProviderAdapter 動(dòng)態(tài)提供。實(shí)際的 SQL 是在運(yùn)行時(shí)從使用 DSL 構(gòu)建的 SelectStatementProvider 生成的,而不是在注解或 XML 中編寫(xiě) SQL。
4. 構(gòu)建動(dòng)態(tài)查詢
在這里,我們使用流暢的 Dynamic SQL DSL 構(gòu)建 SQL 語(yǔ)句,而不是編寫(xiě)原始 SQL 字符串。
public static void main(String[] args) throws Exception {
MyBatisUtil.runSchema();
try (SqlSession session = MyBatisUtil.getSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
SelectStatementProvider select
= select(id, username, email, age)
.from(user)
.where(age, isGreaterThan(18))
.and(username, isLike("%tho%"))
.orderBy(username)
.build()
.render(RenderingStrategies.MYBATIS3);
List<User> users = mapper.selectMany(select);
users.forEach(u
-> System.out.println(u.getUsername() + " - " + u.getAge()));
}
}此代碼使用流暢的 Dynamic SQL DSL 動(dòng)態(tài)構(gòu)建一個(gè) SELECT 查詢,并將其渲染為與 MyBatis 兼容的語(yǔ)句提供者。通過(guò)以編程方式添加條件,它能夠以類(lèi)型安全且可維護(hù)的方式創(chuàng)建復(fù)雜的過(guò)濾器。在本例中,查詢選擇 age 大于 18 歲且 username 包含 "tho" 的用戶,然后按用戶名字母順序?qū)Y(jié)果進(jìn)行排序。
MyBatis 工具類(lèi)
public class MyBatisUtil {
private static SqlSessionFactory factory;
static {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
factory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static SqlSession getSession() {
return factory.openSession(true);
}
public static void runSchema() throws IOException, SQLException {
try (SqlSession session = getSession()) {
Connection conn = session.getConnection();
Statement stmt = conn.createStatement();
try (InputStream is = Resources.getResourceAsStream("schema.sql")) {
String sql = new String(is.readAllBytes(), StandardCharsets.UTF_8);
stmt.execute(sql);
}
}
}
}此工具類(lèi)加載 MyBatis 配置,構(gòu)建 SqlSessionFactory,并提供對(duì)數(shù)據(jù)庫(kù)會(huì)話的訪問(wèn)。它還通過(guò)執(zhí)行 SQL 腳本(schema.sql)手動(dòng)初始化數(shù)據(jù)庫(kù)模式。
5. 使用 Dynamic SQL 進(jìn)行插入、更新和刪除
MyBatis 中的 Dynamic SQL 允許我們使用流暢的 DSL 以編程方式構(gòu)造 INSERT、UPDATE 和 DELETE 語(yǔ)句。在此,我們演示如何執(zhí)行這些常見(jiàn)的數(shù)據(jù)操作。
// INSERT
User newUser = new User();
newUser.setUsername("andrew");
newUser.setEmail("andrew@jcg.com");
newUser.setAge(28);
InsertStatementProvider<User> insert
= insert(newUser)
.into(user)
.map(username).toProperty("username")
.map(email).toProperty("email")
.map(age).toProperty("age")
.build()
.render(RenderingStrategies.MYBATIS3);
int inserted = mapper.insert(insert);
System.out.println("Rows inserted: " + inserted);
// UPDATE
UpdateStatementProvider update
= update(user)
.set(age).equalTo(35)
.where(username, isEqualTo("thomas"))
.build()
.render(RenderingStrategies.MYBATIS3);
int updated = mapper.update(update);
System.out.println("Rows updated: " + updated);
// DELETE
DeleteStatementProvider delete
= deleteFrom(user)
.where(age, isLessThan(18))
.build()
.render(RenderingStrategies.MYBATIS3);
int deleted = mapper.delete(delete);
System.out.println("Rows deleted: " + deleted);相同的 DSL 風(fēng)格也用于寫(xiě)操作。語(yǔ)句以流暢的方式構(gòu)建、渲染,然后由映射器提供者方法執(zhí)行。
- INSERT:創(chuàng)建一個(gè)新的
User對(duì)象并填充值。使用 Dynamic SQL DSL,我們將其字段映射到表列并生成InsertStatementProvider。映射器執(zhí)行插入操作,返回受影響的行數(shù)。 - UPDATE:DSL 構(gòu)建一個(gè)更新語(yǔ)句,將用戶名為 "thomas" 的用戶的年齡設(shè)置為 35。這確保只修改目標(biāo)行,映射器執(zhí)行更新。
- DELETE:刪除語(yǔ)句移除所有年齡小于 18 歲的用戶。在 DSL 中使用條件保證了類(lèi)型安全并避免了字符串拼接。
更新后的映射器接口
為了支持這些操作,映射器接口必須包含用于 INSERT、UPDATE 和 DELETE 的方法,使用 MyBatis Dynamic SQL 提供者。
// INSERT @InsertProvider(type = SqlProviderAdapter.class, method = "insert") int insert(InsertStatementProvider<User> insertStatement); // UPDATE @UpdateProvider(type = SqlProviderAdapter.class, method = "update") int update(UpdateStatementProvider updateStatement); // DELETE @DeleteProvider(type = SqlProviderAdapter.class, method = "delete") int delete(DeleteStatementProvider deleteStatement);
映射器中的每個(gè)方法處理一個(gè)特定的 DML 操作(插入、更新或刪除),并接受一個(gè)封裝了生成的 SQL 及其參數(shù)的 InsertStatementProvider、UpdateStatementProvider 或 DeleteStatementProvider。這種方法允許所有寫(xiě)操作都在 Java 中以編程方式表達(dá),而無(wú)需手動(dòng)組合 SQL 字符串,同時(shí)仍能利用 MyBatis 高效地執(zhí)行語(yǔ)句和映射結(jié)果。
6. 結(jié)論
在本文中,我們探討了如何在 Java 應(yīng)用程序中使用 MyBatis Dynamic SQL 來(lái)創(chuàng)建類(lèi)型安全、可維護(hù)且可編程的 SQL 查詢。通過(guò)將 SQL 構(gòu)建與執(zhí)行分離,MyBatis Dynamic SQL 簡(jiǎn)化了復(fù)雜查詢邏輯的處理,降低了錯(cuò)誤風(fēng)險(xiǎn),并提高了代碼可讀性。這種方法非常適合查詢需要?jiǎng)討B(tài)變化或經(jīng)常修改的應(yīng)用程序。
到此這篇關(guān)于MyBatis Dynamic SQL 入門(mén)指南的文章就介紹到這了,更多相關(guān)MyBatis Dynamic SQL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JAVA包裝類(lèi)及自動(dòng)封包解包實(shí)例代碼
JAVA包裝類(lèi)及自動(dòng)封包解包實(shí)例代碼,需要的朋友可以參考一下2013-03-03
Java BufferedImage轉(zhuǎn)換為MultipartFile方式
這篇文章主要介紹了Java BufferedImage轉(zhuǎn)換為MultipartFile方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Java實(shí)現(xiàn)Fibonacci(斐波那契)取余的示例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)Fibonacci取余的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
如何基于SpringSecurity的@PreAuthorize實(shí)現(xiàn)自定義權(quán)限校驗(yàn)方法
Maven?依賴沖突調(diào)解與版本控制問(wèn)題記錄
Android設(shè)備如何保證數(shù)據(jù)同步寫(xiě)入磁盤(pán)的實(shí)現(xiàn)
Java實(shí)現(xiàn)默認(rèn)目錄查看與修改的方法

