MybatisPlus service接口功能介紹
Service接口
基本用法
MyBatisPlus同時也提供了service接口,繼承后一些基礎(chǔ)的增刪改查的service代碼,也不需要去書寫。
接口名為Iservice
,而Iservice也繼承了IRepository
,這里提供的方法跟BaseMapper相比只多不少,整體還是分為增刪改查這幾大類。只不過查詢的類型占大半。
首先先看新增:
save(T):接收一個泛型參數(shù),
saveBatch():接收一個collection集合,用于批量新增。
saveOrUpdate():接受一個泛型參數(shù),會進行判斷該對象有無ID,,如果有則認為是一個Update操作,反之則為Insert操作,saveOrUpdateBatch():方法支持批量新增及更新。
再看刪除操作:
removeById():只刪除一個
removeByIds():批量刪除,where條件后面用的是in
關(guān)鍵字
修改操作:
剩下的都是查詢操作:
將其分為以下幾類:
如果只查一條數(shù)據(jù),就調(diào)用get開頭的方法:
查詢多條數(shù)據(jù)則為list:
listByIds:傳入一個id的集合,返回一個List集合
list():查詢?nèi)?,或者基于Wrapper做復雜查詢
查詢數(shù)量就調(diào)用count開頭的方法:
分頁查詢就調(diào)用page開頭的方法:
在進行一些復雜查詢時,就需要新建Wrapper,步驟較為繁瑣,因此提供了LambdaQuery()方法,返回LambdaQueryChainWrapper,即鏈式編程Wrapper,調(diào)用該方法就可以直接基于LambdaWrapper做查詢,不需要再次新建。
注意事項:
我們正常開發(fā)過程中,都是先編譯service接口,在編譯接口實現(xiàn)類,然后在接口中添加方法,在實現(xiàn)類中實現(xiàn)方法,但如果service接口去繼承IService,那么Iservice接口中的方法,實現(xiàn)類必須全部實現(xiàn)。這與我們原先的白嫖想法沖突。因此官方為Iservice已經(jīng)提供好了實現(xiàn)類ServiceImpl,所以我們只需要讓我們的實現(xiàn)類去繼承Iservice的實現(xiàn)類。所以我們的業(yè)務(wù)接口繼承Iservice,而接口實現(xiàn)類繼承Iservice的接口實現(xiàn)類。這樣我們就達到了白嫖的目的。
代碼展示:
public interface UserService extends IService<User> { }
@Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }
創(chuàng)建測試類:
@SpringBootTest class UserServiceTest { @Autowired private UserService userService; @Test void testSaveUser(){ User user = new User(); // user.setId(5L); user.setUsername("wew"); user.setPassword("123456"); user.setPhone("12345678901"); user.setBalance(200); user.setInfo("{\"age\":24,\"intro\":\"英文老師\",\"gender\":\"female\"}"); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(LocalDateTime.now()); userService.save(user); } }
測試新增操作:
查詢操作:
小結(jié):
Service接口使用流程:
- 自定義Service接口繼承Iservice接口
- 自定義Service實現(xiàn)類,實現(xiàn)自定義接口不能夠繼承ServiceImpl類。
進階用法
在前面學習完Iservice的基本用法后,發(fā)現(xiàn)MyBatisPlus中的BaseMapper以及Iservice接口有許多相似的功能,那么在實際開發(fā)中應(yīng)該使用哪個接口提供的方法呢?
接下來通過幾個案例去探究實際開發(fā)中如何使用:
案例展示:基于RestFul風格實現(xiàn)下面的接口:
編號 | 接口 | 請求方式 | 請求路徑 | 請求參數(shù) | 返回值 |
---|---|---|---|---|---|
1 | 新增用戶 | POST | /users | 用戶表單實體 | 無 |
2 | 刪除用戶 | DELETE | /users/{id} | 用戶ID | 無 |
3 | 根據(jù)ID查詢用戶 | GET | /users/{id} | 用戶ID | 用戶v0 |
4 | 根基ID批量查詢 | GET | /users | 用戶ID集合 | 用戶v0集合 |
5 | 根基ID扣減余額 | PUT | /users/{id}/deduction/{money} | 用戶id以及扣減金額 | 無 |
前置需求:
引入web與swagger的起步依賴
<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-spring-boot-starter</artifactId> <version>4.5.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-api</artifactId> <version>2.5.0</version>
配置swagger信息:
springdoc: swagger-ui: path: /swagger-ui.html tags-sorter: alpha operations-sorter: alpha api-docs: path: /v3/api-docs group-configs: - group: default paths-to-match: /** packages-to-scan: com.lyc.mybatisplusdemo.controller
@Configuration public class SwaggerConfig { ? @Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title("用戶管理接口文檔") .version("1.0.0") .description("用戶管理接口文檔") .contact(new Contact() .name("lyc") .email("2089371290@qq.com") .url("https://www.dwq.cn"))); } ? @Bean public GroupedOpenApi defaultApi() { return GroupedOpenApi.builder() .group("default") .packagesToScan("com.lyc.mybatisplusdemo.controller") .pathsToMatch("/**") .build(); } }
定義VO包,DTO包以及對應(yīng)的VO類及DTO類、
UserFormDTO.java
@Data @Schema(name = "用戶表單實體") public class UserFormDTO { @Schema(description = "id") private Long id; @Schema(description = "用戶名") private String username; @Schema(description = "密碼") private String password; @Schema(description = "注冊手機號") private String phone; @Schema(description = "詳細信息,JSON風格") private String info; @Schema(description = "賬戶余額") private Integer status;
UserVO
@Data @Schema(name = "用戶VO實體") public class UserVO { @Schema(description = "用戶id") private Long id; @Schema(description = "用戶名") private String username; @Schema(description = "詳細信息") private String info; @Schema(description = "使用狀態(tài)(1正常,2凍結(jié))") private Integer status; @Schema(description = "賬戶余額") private Integer balance;
然后新建controller包編寫UserController。
在UserController類中編寫接口,前四個接口業(yè)務(wù)邏輯較簡單,在conroller層即可完成
//編寫swagger注解 @Tag(name = "用戶管理接口") @RestController @RequestMapping("/users") public class UserController { @Resource private UserService userService; ? @Operation(summary = "新增用戶接口") @PostMapping public void saveUser(@RequestBody UserFormDTO userDTO){ // @RequsetBody 將請求類型定義為json //1.將DTO拷貝到實體中 User user = BeanUtil.copyProperties(userDTO, User.class); //2.新增用戶 userService.save(user); } @Operation(summary = "刪除用戶接口") @DeleteMapping("{id}") public void deleteUser(@Parameter(description = "用戶id") @PathVariable("id") Long id){ userService.removeById(id); } @Operation(summary = "根據(jù)ID查詢用戶接口") @GetMapping("{id}") public UserVO updateUser(@Parameter(description = "用戶id") @PathVariable("id") Long id){ //1.查詢用戶 User user = userService.getById(id); //2.拷貝到VO中并返回 return BeanUtil.copyProperties(user, UserVO.class); } @Operation(summary = "根據(jù)ID批量查詢用戶接口") @PutMapping public List<UserVO> updateUser(@Parameter(description = "用戶id集合") @RequestParam("ids") List<Long> ids){ List<User> users = userService.listByIds(ids); return BeanUtil.copyToList(users, UserVO.class); }
第五個接口:
conroller層:
@Operation(summary = "根據(jù)ID扣減余額") @PutMapping("{id}/deduction/{money}") public void updateBalanceById(@PathVariable("id") Long id, @PathVariable("money") Integer money){ userService.updateBalanceByIds(id, money); } }
service層:
public void updateBalanceByIds(Long id, Integer money) { //1,查詢用戶 User user = getById(id); //2.校驗用戶狀態(tài) if (user.getStatus() == 2 || user == null) { throw new RuntimeException("用戶不存在或者被禁用"); } //3。校驗余額是否充足 if (user.getBalance() < money) { throw new RuntimeException("余額不足"); } //4.更新用戶余額 baseMapper.updateBalanceById(id, money); }
mapper層:
注意事項:在編譯簡單接口時可以直接在controller層調(diào)用MyBatisPlus提供的Iservice接口方法實現(xiàn),但是遇到一些業(yè)務(wù)邏輯復雜的業(yè)務(wù)時,需要編寫自定義的業(yè)務(wù)邏輯時,就需要自定義service方法編寫業(yè)務(wù)邏輯了,當我們的業(yè)務(wù)需要去編寫自定義的SQL語句時,我們還需要去自定義方法,在mapper層實現(xiàn)方法。
啟動:在瀏覽器中進入用戶管理接口文檔
測試新增接口:
測試成功,查看數(shù)據(jù)庫:
測試查詢接口:
測試批量查詢接口:
測試扣減接口:
測試成功。
測試刪除用戶接口:
測試成功。
總結(jié):
對于一些簡單的增刪改查的方法,可以直接在controller層中調(diào)用Iservice接口的方法,無需寫任何的自定義service或者mapper。
只有在業(yè)務(wù)邏輯相對復雜,需要自己寫一些業(yè)務(wù)邏輯,而MyBatisPlus只提供基礎(chǔ)的增刪改查,就需要自定義service方法,在其中編寫業(yè)務(wù)邏輯。
而當BaseMapper中無法提供需要的增刪改查方法時,就需要去自定義SQL語句,在mapper層中去定義方法,實現(xiàn)業(yè)務(wù)邏輯。
Lambda方法
基于案例理解:
需求:實現(xiàn)一個根據(jù)復雜條件查詢用戶的接口,查詢條件如下:
- name: 用戶名關(guān)鍵字,可以為空
- status: 用戶狀態(tài),可以為空
- minBalabce: 最小余額,可以為空
- maxBalance:最大余額,可以為空
就類似于前端頁面中的用戶列表查詢,但是在查詢頂部有幾個過濾狀態(tài),可以對名字過濾,可以對用戶狀態(tài)進行過濾,以及余額的管理。
因此實現(xiàn)該接口就不能直接寫條件,就需要加上判斷,
SQL語句(全手動):
<select id="queryUsers" resultType="com.lyc.mp.domain.po.user"> select * from tb_user <where> <if test="name != null"> and username like #{name} </if> <if test="status != null"> and `status` = #{status} </if> <if test="minBalance != null and MaxBalance != null"> and balance between #{minBalance} and #{maxBalance} and username like #{name} </if> </where> </select>
接下來著手準備編寫接口。
注意事項:在傳入?yún)?shù)較多時,可以將其封裝為對象傳入。
前置代碼:
UserQuery.java
@Data @Schema(name = "用戶查詢條件實體") public class UserQuery { @Schema(description = "用戶名關(guān)鍵字") private String name; @Schema(description = "用戶狀態(tài)") private Integer status; @Schema(description = "余額最小值") private Integer minBalance; @Schema(description = "余額最大值") private Integer maxBalance;
Controller層:
@Operation(summary = "根據(jù)復雜條件查詢用戶接口") @GetMapping("/list") public List<UserVO> getUserList(UserQuery query){ //1.查詢用戶 List<User> users = userService.getUserList(query.getName(), query.getStatus(), query.getMinBalance(), query.getMaxBalance()); //2.拷貝到VO中并返回 return BeanUtil.copyToList(users, UserVO.class); }
Service層:
public List<User> getUserList(String name, Integer status, Integer minBalance, Integer maxBalance) { return lambdaQuery() //相當于 <if test="name != null"> and username like #{name} </if> .like(name != null, User::getUsername, name) //相當于 <if test="status != null"> and status = #{status} </if> .eq(status != null, User::getStatus, status) //相當于 <if test="minBalance != null"> and balance > #{minBalance} </if> .gt(minBalance != null, User::getBalance, minBalance) //相當于 <if test="maxBalance != null"> and balance < #{maxBalance} </if> .lt(maxBalance != null, User::getBalance, maxBalance) .list(); }
測試:
測試成功,
以上演示的是LambdaQuery。
案例展示:Iservice的Lambda更新
需求:改造根據(jù)id修改用戶余額的接口,要求如下
- 完成對用戶的校驗
- 完成對用戶余額校驗
- 如果扣減后余額為0,則將用戶status修改為凍結(jié)狀態(tài)(2)
這與我們前面的扣減余額接口一致,直接在該接口上進行修改。
@Transactional public void updateBalanceByIds(Long id, Integer money) { //1,查詢用戶 User user = getById(id); //2.校驗用戶狀態(tài) if (user.getStatus() == 2 || user == null) { throw new RuntimeException("用戶不存在或者被禁用"); } //3。校驗余額是否充足 if (user.getBalance() < money) { throw new RuntimeException("余額不足"); } //4.更新用戶余額 int remainBalance = user.getBalance() - money; //鏈式函數(shù) 類似于流 需要中間方法 及 結(jié)束方法 lambdaUpdate() // 相當于 set balance = balance - #{money} .set(User::getBalance, remainBalance) // 相當于 <if test="remainBalance == 0"> set status = 2 </if> .set(remainBalance == 0, User::getStatus, 2) // 相當于 where id = #{id} .eq(User::getId, id) .eq(User::getBalance,user.getBalance()) // 樂觀鎖 .update(); }
在這里結(jié)束后會有并發(fā)線程安全問題,如果有多個線程同時訪問,兩個用戶,兩條線程,都來進行對比,最后減去相同的數(shù)據(jù),這樣就會導致兩條線程中只會被減去一個線程。
我們可以采用樂觀鎖(CAS),比較并替換,如果余額不相同,就會回滾
進行測試:
測試成功。
案例展示:Iservice的批量新增
需求:批量插入1萬條用戶數(shù)據(jù),并做出對比:
- 普通for循環(huán)
- Iservice的批量插入
普通for循環(huán)
private User buildUser(int i){ User user = new User(); user.setUsername("user" + i); user.setPassword("123456"); user.setPhone(""+(12345678901L + i)); user.setBalance(2000); user.setInfo("{\"age\":24,\"intro\":\"英文老師\",\"gender\":\"female\"}"); user.setCreateTime(LocalDateTime.now()); user.setUpdateTime(LocalDateTime.now()); return user; } @Test void testSaveBatch(){ long start = System.currentTimeMillis(); for (int i = 0; i < 10000; i++) { userService.save(buildUser(i)); } long end = System.currentTimeMillis(); System.out.println("耗時:" + (end - start)); }
測試結(jié)果:耗時:11148
Iservice的批量插入
void testSaveBatch2(){ //插入100次,每次插入1000條數(shù)據(jù) long start = System.currentTimeMillis(); //準備一個容量為1000的集合 List<User> users = new ArrayList<>(1000); for (int i = 0; i < 10000; i++) { users.add(buildUser(i)); //每1000條數(shù)據(jù)插入一次數(shù)據(jù)庫 if (i % 1000 == 0) { userService.saveBatch(users); //清空集合 users.clear(); } } long end = System.currentTimeMillis(); System.out.println("耗時:" + (end - start)); }
耗時:1790
提升了十倍的效率,但還是不夠快。
性能分析:
在普通for循環(huán)插入是一條一條插入數(shù)據(jù)庫,每一次訪問數(shù)據(jù)庫就是一次IO操作,進行了10000網(wǎng)絡(luò)請求,十分消耗性能
而Iservice的批量插入,MyBatisPlus采用的是JDBC底層的預(yù)編譯方案,Prepare statement 預(yù)編譯:這種方案在便利的過程中把用戶提交的user數(shù)據(jù)對其進行編譯變成SQL語句 。在代碼中就是一千條SQL語句,在執(zhí)行到saveBatch()時一次性提交到數(shù)據(jù)庫,也就是每1000條數(shù)據(jù)進行一次IO操作,只進行了10次網(wǎng)絡(luò)請求。但是這種方案是將數(shù)據(jù)編譯成了SQL語句,數(shù)據(jù)庫在執(zhí)行時還是一條一條執(zhí)行的,因此,性能還是有所損耗。
因此最優(yōu)方案是將1000條數(shù)據(jù)編譯成1條SQL語句,再交于數(shù)據(jù)庫執(zhí)行,這才是批量插入。
兩種方案:
第一種是使用MyBatis使用動態(tài)SQL進行foreach遍歷1000條數(shù)據(jù),在便利的過程中拼接為一條SQL語句,這樣性能最佳,但是需要我們?nèi)ナ謱慡QL語句,還是有些麻煩
第二種:
使用MyBatisPlus的批處理,加入一個參數(shù) ,開啟rewriteBathedStatements = true
參數(shù),(重寫批處理),這并不是MyBatisPlus中的配置,其實是MySQL中的配置。
在數(shù)據(jù)庫配置中添加該參數(shù)即可
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=true username: root password: 123456
再次測試:
耗時:862,有提升了將近一倍,而且數(shù)據(jù)量越大,差距越明顯。
總結(jié):
在批量插入數(shù)據(jù)時,提供了三種方案:
- 普通for循環(huán)逐條插入速度極差,不推薦(原因:每次只提交一條數(shù)據(jù)插入數(shù)據(jù)庫,數(shù)據(jù)庫也是逐條執(zhí)行)
- 默認情況下MyBatisPlus的批量新增,基于預(yù)編譯的批處理,性能良好(原因:一次性提交100條數(shù)據(jù)插入數(shù)據(jù)庫,但數(shù)據(jù)庫依舊是逐條插入)
- 配置JDBC參數(shù):
rewriteBathedStatements = true
,性能最佳(一次性提交100條數(shù)據(jù)插入數(shù)據(jù)庫,數(shù)據(jù)庫也是批量插入)
以上就是service接口的全部用法,讓我們一起加油!
到此這篇關(guān)于MybatisPlus--核心功能--service接口的文章就介紹到這了,更多相關(guān)MybatisPlus service接口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用java代碼實現(xiàn)保留小數(shù)點的位數(shù)
因為個人應(yīng)用的需要,所以就寫個簡單點的了。希望大家都給給建議,共同學習。需要的朋友也可以參考下2013-07-07Java求字符串中出現(xiàn)次數(shù)最多的字符串以及出現(xiàn)次數(shù)
這篇文章主要為大家詳細介紹了Java統(tǒng)計字符串中出現(xiàn)次數(shù)最多的字符串以及出現(xiàn)次數(shù),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04SpringBoot詳解自定義Stater的應(yīng)用
Springboot的出現(xiàn)極大的簡化了開發(fā)人員的配置,而這之中的一大利器便是springboot的starter,starter是springboot的核心組成部分,springboot官方同時也為開發(fā)人員封裝了各種各樣方便好用的starter模塊2022-07-07mybatis新增save結(jié)束后自動返回主鍵id詳解
這篇文章主要介紹了mybatis新增save結(jié)束后自動返回主鍵id詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12spring實現(xiàn)靜態(tài)注入(類或者屬性)操作示例
這篇文章主要為大家介紹了spring實現(xiàn)靜態(tài)注入(類或者屬性)操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07Maven?Web項目使用Cargo插件實現(xiàn)自動化部署的詳細步驟
cargo ,它是一組幫助用戶實現(xiàn)自動化部署,操作Web容器的工具,并且?guī)缀踔С炙械腤eb容器,這篇文章主要介紹了Maven?Web項目使用Cargo實現(xiàn)自動化部署,需要的朋友可以參考下2023-02-02