MyBatis-Plus 簡介
一、簡介
MyBatis 是一個流行的開源持久性框架,它提供了 JDBC 和 Hibernate 的替代方案。
在本文中,我們將討論 MyBatis 的一個名為MyBatis-Plus的擴展,它包含許多方便的功能,可提供快速開發和更高的效率。
2.MyBatis-Plus 設置
2.1. Maven依賴
首先,我們將以下 Maven 依賴項新增到pom.xml中。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
可以在此處找到最新版本的 Maven 依賴項。由於這是基於 Spring Boot 3 的 Maven 依賴項,因此我們也需要將spring-boot-starter依賴項新增至pom.xml中。
或者,我們可以在使用 Spring Boot 2 時新增以下相依性:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version>
</dependency>
接下來,我們將 H2 依賴項新增至記憶體資料庫的pom.xml中,以驗證 MyBatis-Plus 的特性和功能。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.230</version>
</dependency>
同樣,在此處找到最新版本的 Maven 依賴項。我們還可以使用 MySQL 進行整合。
2.2. Client
一旦我們的設定準備就緒,讓我們建立具有一些屬性的Client實體,例如id 、 firstName 、 lastName和email :
@TableName("client")
public class Client {
@TableId(type = IdType.AUTO)
private Long id;
private String firstName;
private String lastName;
private String email;
// getters and setters ...
}
在這裡,我們使用了 MyBatis-Plus 的自解釋註釋(如@TableName和@TableId來快速與底層資料庫中的client表整合。
2.3. ClientMapper
然後,我們將為Client實體建立映射器介面 – ClientMapper ,它擴展了 MyBatis-Plus 提供的 BaseMapper 介面:
@Mapper
public interface ClientMapper extends BaseMapper<Client> {
}
BaseMapper介面提供了許多用於 CRUD 操作的預設方法,例如insert() 、 selectOne() 、 updateById() 、 insertOrUpdate() 、 deleteById()和deleteByIds() 。
2.4. ClientService
接下來,讓我們建立擴展IService介面的ClientService服務介面:
public interface ClientService extends IService<Client> {
}
IService介面封裝了CRUD操作的預設實現,並使用BaseMapper介面提供簡單且可維護的基本資料庫操作。
2.5. ClientServiceImpl
最後,我們將建立ClientServiceImpl類別:
@Service
public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> implements ClientService {
@Autowired
private ClientMapper clientMapper;
}
它是Client實體的服務實現,注入了ClientMapper相依性。
3.增刪改查操作
3.1.創造
現在我們已經準備好了所有實用程式介面和類,讓我們使用ClientService介面來建立Client物件:
Client client = new Client();
client.setFirstName("Anshul");
client.setLastName("Bansal");
client.setEmail("[email protected]");
clientService.save(client);
assertNotNull(client.getId());
一旦我們將package com.baeldung.mybatisplus:的日誌記錄等級設定為DEBUG我們就可以在儲存client物件時觀察到以下日誌:
16:07:57.404 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Preparing: INSERT INTO client ( first_name, last_name, email ) VALUES ( ?, ?, ? )
16:07:57.414 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Parameters: Anshul(String), Bansal(String), [email protected](String)
16:07:57.415 [main] DEBUG cbmmapper.ClientMapper.insert - <== Updates: 1
ClientMapper 介面產生的日誌ClientMapper帶有參數的insert查詢以及資料庫中插入的最終行數。
3.2.讀
接下來,讓我們來看看一些方便的讀取方法,例如getById()和list() :
assertNotNull(clientService.getById(2));
assertEquals(6, clientService.list())
同樣,我們可以在日誌中觀察到以下SELECT語句:
16:07:57.423 [main] DEBUG cbmmapper.ClientMapper.selectById - ==> Preparing: SELECT id,first_name,last_name,email,creation_date FROM client WHERE id=?
16:07:57.423 [main] DEBUG cbmmapper.ClientMapper.selectById - ==> Parameters: 2(Long)
16:07:57.429 [main] DEBUG cbmmapper.ClientMapper.selectById - <== Total: 1
16:07:57.437 [main] DEBUG cbmmapper.ClientMapper.selectList - ==> Preparing: SELECT id,first_name,last_name,email FROM client
16:07:57.438 [main] DEBUG cbmmapper.ClientMapper.selectList - ==> Parameters:
16:07:57.439 [main] DEBUG cbmmapper.ClientMapper.selectList - <== Total: 6
此外,MyBatis-Plus 框架還附帶了一些方便的包裝類,例如QueryWrapper 、 LambdaQueryWrapper和QueryChainWrapper :
Map<String, Object> map = Map.of("id", 2, "first_name", "Laxman");
QueryWrapper<Client> clientQueryWrapper = new QueryWrapper<>();
clientQueryWrapper.allEq(map);
assertNotNull(clientService.getBaseMapper().selectOne(clientQueryWrapper));
LambdaQueryWrapper<Client> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Client::getId, 3);
assertNotNull(clientService.getBaseMapper().selectOne(lambdaQueryWrapper));
QueryChainWrapper<Client> queryChainWrapper = clientService.query();
queryChainWrapper.allEq(map);
assertNotNull(clientService.getBaseMapper().selectOne(queryChainWrapper.getWrapper()));
在這裡,我們使用ClientService介面的getBaseMapper()方法來利用包裝類別讓我們直觀地編寫複雜的查詢。
3.3.更新
然後,讓我們來看看執行更新的幾種方法:
Client client = clientService.getById(2);
client.setEmail("[email protected]");
clientService.updateById(client);
assertEquals("[email protected]", clientService.getById(2).getEmail());
依照控制台查看以下日誌:
16:07:57.440 [main] DEBUG cbmmapper.ClientMapper.updateById - ==> Preparing: UPDATE client SET email=? WHERE id=?
16:07:57.441 [main] DEBUG cbmmapper.ClientMapper.updateById - ==> Parameters: [email protected](String), 2(Long)
16:07:57.441 [main] DEBUG cbmmapper.ClientMapper.updateById - <== Updates: 1
同樣,我們可以使用LambdaUpdateWrapper類別來更新Client物件:
LambdaUpdateWrapper<Client> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.set(Client::getEmail, "[email protected]");
assertTrue(clientService.update(lambdaUpdateWrapper));
QueryWrapper<Client> clientQueryWrapper = new QueryWrapper<>();
clientQueryWrapper.allEq(Map.of("email", "[email protected]"));
assertThat(clientService.list(clientQueryWrapper).size()).isGreaterThan(5);
更新客戶端物件後,我們使用QueryWrapper類別來確認操作。
3.4.刪除
同樣,我們可以使用removeById()或removeByMap()方法來刪除記錄:
clientService.removeById(1);
assertNull(clientService.getById(1));
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("email", "[email protected]");
clientService.removeByMap(columnMap);
assertEquals(0, clientService.list().size());
刪除操作的日誌如下所示:
21:55:12.938 [main] DEBUG cbmmapper.ClientMapper.deleteById - ==> Preparing: DELETE FROM client WHERE id=?
21:55:12.938 [main] DEBUG cbmmapper.ClientMapper.deleteById - ==> Parameters: 1(Long)
21:55:12.938 [main] DEBUG cbmmapper.ClientMapper.deleteById - <== Updates: 1
21:57:14.278 [main] DEBUG cbmmapper.ClientMapper.delete - ==> Preparing: DELETE FROM client WHERE (email = ?)
21:57:14.286 [main] DEBUG cbmmapper.ClientMapper.delete - ==> Parameters: [email protected](String)
21:57:14.287 [main] DEBUG cbmmapper.ClientMapper.delete - <== Updates: 5
與更新日誌類似,這些日誌顯示delete查詢以及從資料庫中刪除的參數和總行數。
4. 額外功能
讓我們討論一下 MyBatis-Plus 中作為 MyBatis 擴充的一些方便的功能。
4.1.批量操作
首先也是最重要的是能夠批次執行常見的 CRUD 操作,從而提高效能和效率:
Client client2 = new Client();
client2.setFirstName("Harry");
Client client3 = new Client();
client3.setFirstName("Ron");
Client client4 = new Client();
client4.setFirstName("Hermione");
// create in batches
clientService.saveBatch(Arrays.asList(client2, client3, client4));
assertNotNull(client2.getId());
assertNotNull(client3.getId());
assertNotNull(client4.getId());
同樣,讓我們檢查日誌以查看批量插入的實際情況:
16:07:57.419 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Preparing: INSERT INTO client ( first_name ) VALUES ( ? )
16:07:57.419 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Parameters: Harry(String)
16:07:57.421 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Parameters: Ron(String)
16:07:57.421 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Parameters: Hermione(String)
此外,我們還有updateBatchById() 、 saveOrUpdateBatch(), and removeBatchByIds()等方法來批次對物件集合執行儲存、更新或刪除操作。
4.2.分頁
MyBatis-Plus 框架提供了一種直觀的方式對查詢結果進行分頁。
我們所需要做的就是將MyBatisPlusInterceptor類別聲明為 Spring Bean,並添加使用資料庫類型定義的PaginationInnerInterceptor類別作為內部攔截器:
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}
然後,我們可以使用Page類別對記錄進行分頁。例如,讓我們取得包含三個結果的第二頁:
Page<Client> page = Page.of(2, 3);
clientService.page(page, null).getRecords();
assertEquals(3, clientService.page(page, null).getRecords().size());
那麼,我們可以觀察到上述操作的日誌如下:
16:07:57.487 [main] DEBUG cbmmapper.ClientMapper.selectList - ==> Preparing: SELECT id,first_name,last_name,email FROM client LIMIT ? OFFSET ?
16:07:57.487 [main] DEBUG cbmmapper.ClientMapper.selectList - ==> Parameters: 3(Long), 3(Long)
16:07:57.488 [main] DEBUG cbmmapper.ClientMapper.selectList - <== Total: 3
同樣,這些日誌顯示帶有從資料庫中選擇的參數和總行數select查詢。
4.3.串流查詢
MyBatis-Plus 透過selectList() 、 selectByMap()和selectBatchIds()等方法提供對串流查詢的支持,讓我們能夠處理大數據並滿足效能目標。
例如,讓我們檢查一下透過ClientService介面可用的selectList()方法:
clientService.getBaseMapper()
.selectList(Wrappers.emptyWrapper(), resultContext ->
assertNotNull(resultContext.getResultObject()));
在這裡,我們使用getResultObject()方法從資料庫中取得每筆記錄。
同樣,我們有getResultCount()方法,用於傳回正在處理的結果的計數,以及stop()方法,用於停止結果集的處理。
4.4.自動填充
作為一個相當固執和智慧的框架,MyBatis-Plus 還支援自動填入插入和更新操作的欄位。
例如,我們可以使用@TableField註解來設定插入新記錄時的creationDate和更新時的lastModifiedDate :
public class Client {
// ...
@TableField(fill = FieldFill.INSERT)
private LocalDateTime creationDate;
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime lastModifiedDate;
// getters and setters ...
}
現在,MyBatis-Plus 將在每次insert和update查詢時自動填入creation_date和last_modified_date欄位。
4.5.邏輯刪除
MyBatis-Plus 框架提供了一個簡單而有效率的策略,讓我們透過在資料庫中標記記錄來邏輯刪除記錄。
我們可以透過在deleted屬性上使用@TableLogic註解來啟用該功能:
@TableName("client")
public class Client {
// ...
@TableLogic
private Integer deleted;
// getters and setters ...
}
現在,框架將在執行資料庫操作時自動處理記錄的邏輯刪除。
因此,讓我們刪除Client物件並嘗試讀取相同的內容:
clientService.removeById(harry);
assertNull(clientService.getById(harry.getId()));
觀察以下日誌以檢查update查詢將deleted屬性的值設為1並在資料庫上執行select查詢時使用0值:
15:38:41.955 [main] DEBUG cbmmapper.ClientMapper.deleteById - ==> Preparing: UPDATE client SET last_modified_date=?, deleted=1 WHERE id=? AND deleted=0
15:38:41.955 [main] DEBUG cbmmapper.ClientMapper.deleteById - ==> Parameters: null, 7(Long)
15:38:41.957 [main] DEBUG cbmmapper.ClientMapper.deleteById - <== Updates: 1
15:38:41.957 [main] DEBUG cbmmapper.ClientMapper.selectById - ==> Preparing: SELECT id,first_name,last_name,email,creation_date,last_modified_date,deleted FROM client WHERE id=? AND deleted=0
15:38:41.957 [main] DEBUG cbmmapper.ClientMapper.selectById - ==> Parameters: 7(Long)
15:38:41.958 [main] DEBUG cbmmapper.ClientMapper.selectById - <== Total: 0
此外,也可以透過application.yml修改預設配置:
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
上面的配置允許我們使用刪除和活動值來更改刪除欄位的名稱。
4.6.程式碼產生器
MyBatis-Plus 提供自動程式碼產生功能,以避免手動建立冗餘程式碼,如實體、映射器和服務介面。
首先,讓我們將最新的mybatis-plus-generator相依性新增至pom.xml :
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.7</version>
</dependency>
此外,我們還需要模板引擎(例如 Velocity 或 Freemarker)的支援。
然後,我們可以使用MyBatis-Plus的FastAutoGenerator類,並將FreemarkerTemplateEngine類別設定為模板引擎來連接底層資料庫,掃描所有現有表,並產生實用程式程式碼:
FastAutoGenerator.create("jdbc:h2:file:~/mybatisplus", "sa", "")
.globalConfig(builder -> {
builder.author("anshulbansal")
.outputDir("../tutorials/mybatis-plus/src/main/java/")
.disableOpenDir();
})
.packageConfig(builder -> builder.parent("com.baeldung.mybatisplus").service("ClientService"))
.templateEngine(new FreemarkerTemplateEngine())
.execute();
因此,當上面的程式運行時,它將在com.baeldung.mybatisplus套件中產生輸出檔:
List<String> codeFiles = Arrays.asList("src/main/java/com/baeldung/mybatisplus/entity/Client.java",
"src/main/java/com/baeldung/mybatisplus/mapper/ClientMapper.java",
"src/main/java/com/baeldung/mybatisplus/service/ClientService.java",
"src/main/java/com/baeldung/mybatisplus/service/impl/ClientServiceImpl.java");
for (String filePath : codeFiles) {
Path path = Paths.get(filePath);
assertTrue(Files.exists(path));
}
在這裡,我們斷言自動產生的類別/介面(如Client 、 ClientMapper 、 ClientService,和ClientServiceImpl存在於對應的路徑中。
4.7.自訂 ID 產生器
MyBatis-Plus 框架允許使用IdentifierGenerator介面實作自訂 ID 產生器。
例如,讓我們建立TimestampIdGenerator類別並實作IdentifierGenerator介面的nextId()方法以傳回系統的奈秒:
@Component
public class TimestampIdGenerator implements IdentifierGenerator {
@Override
public Long nextId(Object entity) {
return System.nanoTime();
}
}
現在,我們可以使用timestampIdGenerator bean 來建立設定自訂 ID 的Client物件:
Client client = new Client();
client.setId(timestampIdGenerator.nextId(client));
client.setFirstName("Harry");
clientService.save(client);
assertThat(timestampIdGenerator.nextId(harry)).describedAs(
"Since we've used the timestampIdGenerator, the nextId value is greater than the previous Id")
.isGreaterThan(harry.getId());
日誌將顯示TimestampIdGenerator類別產生的自訂 ID 值:
16:54:36.485 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Preparing: INSERT INTO client ( id, first_name, creation_date ) VALUES ( ?, ?, ? )
16:54:36.485 [main] DEBUG cbmmapper.ClientMapper.insert - ==> Parameters: 678220507350000(Long), Harry(String), null
16:54:36.485 [main] DEBUG cbmmapper.ClientMapper.insert - <== Updates: 1
參數中顯示的id的long值是系統時間(以奈秒為單位)。
4.8.資料庫遷移
MyBatis-Plus 提供了一種自動機制來處理 DDL 遷移。
我們只需擴充SimpleDdl類別並重寫getSqlFiles()方法即可傳回包含資料庫遷移語句的 SQL 檔案的路徑清單:
@Component
public class DBMigration extends SimpleDdl {
@Override
public List<String> getSqlFiles() {
return Arrays.asList("db/db_v1.sql", "db/db_v2.sql");
}
}
底層IdDL介面建立ddl_history表來保存在架構上執行的 DDL 語句的歷史記錄:
CREATE TABLE IF NOT EXISTS `ddl_history` (`script` varchar(500) NOT NULL COMMENT '脚本',`type` varchar(30) NOT NULL COMMENT '类型',`version` varchar(30) NOT NULL COMMENT '版本',PRIMARY KEY (`script`)) COMMENT = 'DDL 版本'
alter table client add column address varchar(255)
alter table client add column deleted int default 0
注意:此功能適用於大多數資料庫,例如 MySQL 和 PostgreSQL,但不適用於 H2。
5. 結論
在這篇介紹文章中,我們探討了 MyBatis-Plus——流行的 MyBatis 框架的擴展,加載了許多開發人員友好的固執方法來對資料庫執行 CRUD 操作。
此外,我們還看到了一些方便的功能,例如批次作業、分頁、ID 產生和資料庫遷移。
本文的完整程式碼可在 GitHub 上取得。