MybatisPlus--核心功能--service接口

article/2025/6/8 9:59:39
Service接口
基本用法

MyBatisPlus同时也提供了service接口,继承后一些基础的增删改查的service代码,也不需要去书写。

接口名为Iservice,而Iservice也继承了IRepository,这里提供的方法跟BaseMapper相比只多不少,整体还是分为增删改查这几大类。只不过查询的类型占大半。

首先先看新增:

image-20250603142307119

save(T):接收一个泛型参数,

saveBatch():接收一个collection集合,用于批量新增。

saveOrUpdate():接受一个泛型参数,会进行判断该对象有无ID,,如果有则认为是一个Update操作,反之则为Insert操作,saveOrUpdateBatch():方法支持批量新增及更新。

再看删除操作:

image-20250603142904405

removeById():只删除一个

removeByIds():批量删除,where条件后面用的是in关键字

修改操作:

image-20250603143337614

剩下的都是查询操作:

将其分为以下几类:

如果只查一条数据,就调用get开头的方法:

image-20250603143520016

查询多条数据则为list:

image-20250603143704842

listByIds:传入一个id的集合,返回一个List集合

list():查询全部,或者基于Wrapper做复杂查询

查询数量就调用count开头的方法:

image-20250603143920148

分页查询就调用page开头的方法:

image-20250603144022020

image-20250603144112647

在进行一些复杂查询时,就需要新建Wrapper,步骤较为繁琐,因此提供了LambdaQuery()方法,返回LambdaQueryChainWrapper,即链式编程Wrapper,调用该方法就可以直接基于LambdaWrapper做查询,不需要再次新建。

注意事项:

我们正常开发过程中,都是先编译service接口,在编译接口实现类,然后在接口中添加方法,在实现类中实现方法,但如果service接口去继承IService,那么Iservice接口中的方法,实现类必须全部实现。这与我们原先的白嫖想法冲突。因此官方为Iservice已经提供好了实现类ServiceImpl,所以我们只需要让我们的实现类去继承Iservice的实现类。所以我们的业务接口继承Iservice,而接口实现类继承Iservice的接口实现类。这样我们就达到了白嫖的目的。

代码展示:

public interface UserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}

创建测试类:

 @SpringBootTestclass UserServiceTest {@Autowiredprivate UserService userService;@Testvoid 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);}}

测试新增操作:

image-20250603150339707

查询操作:

image-20250603150616380

小结:

Service接口使用流程:

  • 自定义Service接口继承Iservice接口

  • 自定义Service实现类,实现自定义接口不能够继承ServiceImpl类。

进阶用法

在前面学习完Iservice的基本用法后,发现MyBatisPlus中的BaseMapper以及Iservice接口有许多相似的功能,那么在实际开发中应该使用哪个接口提供的方法呢?

接下来通过几个案例去探究实际开发中如何使用:

案例展示:基于RestFul风格实现下面的接口:

编号接口请求方式请求路径请求参数返回值
1新增用户POST/users用户表单实体
2删除用户DELETE/users/{id}用户ID
3根据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.htmltags-sorter: alphaoperations-sorter: alphaapi-docs:path: /v3/api-docsgroup-configs:- group: defaultpaths-to-match: /**packages-to-scan: com.lyc.mybatisplusdemo.controller
 @Configurationpublic class SwaggerConfig {​@Beanpublic 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")));}​@Beanpublic GroupedOpenApi defaultApi() {return GroupedOpenApi.builder().group("default").packagesToScan("com.lyc.mybatisplusdemo.controller").pathsToMatch("/**").build();}}

定义VO包,DTO包以及对应的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 = "使用状态(1正常,2冻结)")private Integer status;@Schema(description = "账户余额")private Integer balance;

然后新建controller包编写UserController。

在UserController类中编写接口,前四个接口业务逻辑较简单,在conroller层即可完成

 //编写swagger注解@Tag(name = "用户管理接口")@RestController@RequestMapping("/users")public class UserController {@Resourceprivate  UserService  userService;​@Operation(summary = "新增用户接口")@PostMappingpublic 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 = "根据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 = "根据ID批量查询用户接口")@PutMappingpublic 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 = "根据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.校验用户状态if (user.getStatus() == 2 || user == null) {throw new RuntimeException("用户不存在或者被禁用");}//3。校验余额是否充足if (user.getBalance() < money) {throw new RuntimeException("余额不足");}//4.更新用户余额baseMapper.updateBalanceById(id, money);}

mapper层:

 @Update("update tb_user set balance = balance - #{money} where id = #{id}")void updateBalanceById(@Param("id") Long id, @Param("money") Integer money);

注意事项:在编译简单接口时可以直接在controller层调用MyBatisPlus提供的Iservice接口方法实现,但是遇到一些业务逻辑复杂的业务时,需要编写自定义的业务逻辑时,就需要自定义service方法编写业务逻辑了,当我们的业务需要去编写自定义的SQL语句时,我们还需要去自定义方法,在mapper层实现方法。

启动:在浏览器中进入用户管理接口文档

image-20250603191321677

测试新增接口:

image-20250603192901557

测试成功,查看数据库:

image-20250603192934344

测试查询接口:

image-20250603193143298

测试批量查询接口:

image-20250603193247302

测试扣减接口:

image-20250603193341376

测试成功。

测试删除用户接口:

image-20250603193433212

测试成功。

总结:

对于一些简单的增删改查的方法,可以直接在controller层中调用Iservice接口的方法,无需写任何的自定义service或者mapper。

只有在业务逻辑相对复杂,需要自己写一些业务逻辑,而MyBatisPlus只提供基础的增删改查,就需要自定义service方法,在其中编写业务逻辑。

而当BaseMapper中无法提供需要的增删改查方法时,就需要去自定义SQL语句,在mapper层中去定义方法,实现业务逻辑。

Lambda方法

基于案例理解:

需求:实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name: 用户名关键字,可以为空

  • status: 用户状态,可以为空

  • minBalabce: 最小余额,可以为空

  • maxBalance:最大余额,可以为空

就类似于前端页面中的用户列表查询,但是在查询顶部有几个过滤状态,可以对名字过滤,可以对用户状态进行过滤,以及余额的管理。

因此实现该接口就不能直接写条件,就需要加上判断,

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>

接下来着手准备编写接口。

注意事项:在传入参数较多时,可以将其封装为对象传入。

前置代码:

UserQuery.java

 @Data@Schema(name = "用户查询条件实体")public class UserQuery {@Schema(description = "用户名关键字")private String name;@Schema(description = "用户状态")private Integer status;@Schema(description = "余额最小值")private Integer minBalance;@Schema(description = "余额最大值")private Integer maxBalance;

Controller层:

 @Operation(summary = "根据复杂条件查询用户接口")@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();}

测试:

image-20250603201647021

测试成功,

以上演示的是LambdaQuery。

案例展示:Iservice的Lambda更新

需求:改造根据id修改用户余额的接口,要求如下

  • 完成对用户的校验

  • 完成对用户余额校验

  • 如果扣减后余额为0,则将用户status修改为冻结状态(2)

这与我们前面的扣减余额接口一致,直接在该接口上进行修改。

  @Transactionalpublic void updateBalanceByIds(Long id, Integer money) {//1,查询用户User user = getById(id);//2.校验用户状态if (user.getStatus() == 2 || user == null) {throw new RuntimeException("用户不存在或者被禁用");}//3。校验余额是否充足if (user.getBalance() < money) {throw new RuntimeException("余额不足");}//4.更新用户余额int remainBalance = user.getBalance() - money;//链式函数 类似于流 需要中间方法 及 结束方法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();}

在这里结束后会有并发线程安全问题,如果有多个线程同时访问,两个用户,两条线程,都来进行对比,最后减去相同的数据,这样就会导致两条线程中只会被减去一个线程。

我们可以采用乐观锁(CAS),比较并替换,如果余额不相同,就会回滚

进行测试:

image-20250603203619872

测试成功。

案例展示:Iservice的批量新增

需求:批量插入1万条用户数据,并做出对比:

  • 普通for循环

  • Iservice的批量插入

普通for循环

 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;}@Testvoid 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));}

测试结果:耗时:11148

Iservice的批量插入

 void testSaveBatch2(){//插入100次,每次插入1000条数据long  start = System.currentTimeMillis();//准备一个容量为1000的集合List<User> users = new ArrayList<>(1000);for (int i = 0; i < 10000; i++) {users.add(buildUser(i));//每1000条数据插入一次数据库if (i % 1000 == 0) {userService.saveBatch(users);//清空集合users.clear();}}long  end = System.currentTimeMillis();System.out.println("耗时:" + (end - start));}

耗时:1790

提升了十倍的效率,但还是不够快。

性能分析:

在普通for循环插入是一条一条插入数据库,每一次访问数据库就是一次IO操作,进行了10000网络请求,十分消耗性能

而Iservice的批量插入,MyBatisPlus采用的是JDBC底层的预编译方案,Prepare statement 预编译:这种方案在便利的过程中把用户提交的user数据对其进行编译变成SQL语句 。在代码中就是一千条SQL语句,在执行到saveBatch()时一次性提交到数据库,也就是每1000条数据进行一次IO操作,只进行了10次网络请求。但是这种方案是将数据编译成了SQL语句,数据库在执行时还是一条一条执行的,因此,性能还是有所损耗。

因此最优方案是将1000条数据编译成1条SQL语句,再交于数据库执行,这才是批量插入。

两种方案:

第一种是使用MyBatis使用动态SQL进行foreach遍历1000条数据,在便利的过程中拼接为一条SQL语句,这样性能最佳,但是需要我们去手写SQL语句,还是有些麻烦

第二种:

使用MyBatisPlus的批处理,加入一个参数 ,开启rewriteBathedStatements = true参数,(重写批处理),这并不是MyBatisPlus中的配置,其实是MySQL中的配置。

在数据库配置中添加该参数即可

 spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=trueusername: rootpassword: 123456

再次测试:

耗时:862,有提升了将近一倍,而且数据量越大,差距越明显。

总结:

在批量插入数据时,提供了三种方案:

  • 普通for循环逐条插入速度极差,不推荐(原因:每次只提交一条数据插入数据库,数据库也是逐条执行)

  • 默认情况下MyBatisPlus的批量新增,基于预编译的批处理,性能良好(原因:一次性提交100条数据插入数据库,但数据库依旧是逐条插入)

  • 配置JDBC参数:rewriteBathedStatements = true,性能最佳(一次性提交100条数据插入数据库,数据库也是批量插入)

以上就是service接口的全部用法,让我们一起加油!

山不让尘,川不辞盈。


http://www.hkcw.cn/article/YxBwshZHie.shtml

相关文章

Silky-CTF: 0x02靶场

Silky-CTF: 0x02 来自 <Silky-CTF: 0x02 ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.128&#xff0c;靶场IP192.168.23.131 3&#xff0c;对靶机进…

图片组件|纯血鸿蒙组件库AUI

摘要&#xff1a; 图片组件(A_Image)&#xff1a;可设置图片地址、图片宽度、图片高度、圆角类型及是否显示外框线。圆角类型支持普通圆角、圆形及无圆角。 一、组件调用方式 1.极简调用 只需要输入A_Image&#xff0c;然后给src&#xff08;图片地址&#xff09;属性赋值即可…

tGSSA-LSTM多输入回归|改进麻雀优化-长短期记忆神经网络|Matlab回归通用

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、方法原理介绍&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编译…

【吾爱】逆向实战crackme160破解记录(二)

前言 最近在拿吾爱上的crackme程序练练手&#xff0c;发现论坛上已经有pk8900总结好的160个crackme&#xff0c;非常方便&#xff0c;而且有很多厉害的前辈已经写好经验贴和方法了&#xff0c;我这里只是做一下自己练习的记录&#xff0c;欢迎讨论学习&#xff0c;感谢吾爱论坛…

C# Onnx 动漫人物人脸检测

目录 效果 模型信息 项目 代码 下载 参考 效果 模型信息 Model Properties ------------------------- stride&#xff1a;32 names&#xff1a;{0: face} --------------------------------------------------------------- Inputs ------------------------- name&am…

使用cmd命令行创建数据库和表-简单步骤记录

前提&#xff1a; 已安装MySQL 步骤&#xff1a; 1.WinR&#xff0c;回车&#xff0c;输入cmd&#xff0c;回车 2.输入 mysql -u root -p 后&#xff0c;输入自己的密码&#xff0c;看到welcome等字样就是成功登录了MySQL 3.创建数据库 create database success; &#xff0…

Centos7使用rpm升级glibc2.28

Centos7使用rpm升级glibc2.28 检查glibc版本下载glibc2.28的rpm包使用rpm包升级到glibc-2.28结果验证 检查glibc版本 ldd --version下载glibc2.28的rpm包 参考&#xff1a; https://www.cnblogs.com/caya-yuan/p/10561439.html 下载 glibc、make 的 feroda29(fc29)系统 rpm包…

堆叠弹窗 VS 队列弹窗之争

前言 如果一个页面上有多个弹窗&#xff0c;设计上是把前一个弹窗暂时隐藏还是盖住前一个弹窗多一点&#xff1f; 在多弹窗设计的情境下&#xff0c;最佳实践通常倾向于以下两种处理方式&#xff1a; 1、堆叠弹窗 新弹窗覆盖旧弹窗&#xff0c;但每个弹窗保持完整显示&#…

刷leetcode hot100返航必胜版--链表6/3

链表初始知识 链表种类&#xff1a;单链表&#xff0c;双链表&#xff0c;循环链表 链表初始化 struct ListNode{ int val; ListNode* next; ListNode(int x): val&#xff08;x&#xff09;,next(nullptr) {} }; //初始化 ListNode* head new ListNode(5); 删除节点、添加…

[概率论基本概念4]什么是无偏估计

关键词&#xff1a;Unbiased Estimation 一、说明 对于无偏和有偏估计&#xff0c;需要了解其叙事背景&#xff0c;是指整体和抽样的关系&#xff0c;也就是说整体的叙事是从理论角度的&#xff0c;而估计器原理是从实践角度说事&#xff1b;为了表明概率理论&#xff08;不可…

React-native之Flexbox

本文总结: 我们学到了 React Native 的 Flexbox 布局&#xff0c;它让写样式变得更方便啦&#xff01;&#x1f60a; Flexbox 就像一个有弹性的盒子&#xff0c;有主轴和交叉轴&#xff08;行或列&#xff09;。 在 RN 里写样式要用 StyleSheet.create 对象&#xff0c;属性名…

学习日记-day21-6.3

完成目标&#xff1a; 目录 知识点&#xff1a; 1.集合_哈希表存储过程说明 2.集合_哈希表源码查看 3.集合_哈希表无索引&哈希表有序无序详解 4.集合_TreeSet和TreeMap 5.集合_Hashtable和Vector&Vector源码分析 6.集合_Properties属性集 7.集合_集合嵌套 8.…

ABP-Book Store Application中文讲解 - Part 6: Authors: Domain Layer

ABP-Book Store Application中文讲解 - Part 6: Authors: Domain Layer 1. 汇总 ABP-Book Store Application中文讲解-汇总-CSDN博客 2. 前一章 ABP-Book Store Application中文讲解 - Part 5: Authorization-CSDN博客 项目之间的引用关系。 ​ BookAppService利用的是Cu…

智慧高铁站:数字时代交通枢纽的标杆

智慧高铁站作为现代综合交通体系的核心节点&#xff0c;通过数字技术与基础设施的深度融合&#xff0c;正在重塑旅客出行体验与车站运营模式。这一转型不仅体现在技术应用层面&#xff0c;更代表着交通服务理念的根本性变革&#xff0c;为现代交通枢纽建设树立了全新标杆。 一、…

ARM架构推理Stable Diffusiond

代码仓库&#xff1a; https://github.com/siutin/stable-diffusion-webui-docker.git Docker容器地址&#xff1a; https://hub.docker.com/r/siutin/stable-diffusion-webui-docker/tags git clone https://github.com/siutin/stable-diffusion-webui-docker.git cd stabl…

关于 KWDB 数据存储的几件事儿

邻近粽子节&#xff0c;KWDB 的朋友给我发消息&#xff0c;问我吃过红茶味的粽子没&#xff0c;作为北方人的我一般只吃蜜枣白粽&#xff0c;还没见过茶香粽子&#xff0c;顶多泡碗祁红&#xff0c;就着茶水吃粽子。 她又问道&#xff0c;两个月时间到了&#xff0c;你准备好了…

酵母杂交那些事儿(一)

酵母单杂、酵母双杂、酵母三杂&#xff0c;仅仅一个字的区别&#xff0c;你对它们了解吗&#xff1f;这些经常用到的实验&#xff0c;它们的原理你确定都搞清楚了吗&#xff1f;如果没有&#xff0c;那么今天你就来对地方了&#xff0c;因为伯远生物&#xff08;https://plant.…

sqlite3 命令行工具详细介绍

一、启动与退出 启动数据库连接 sqlite3 [database_file] # 打开/创建数据库文件&#xff08;如 test.db&#xff09; sqlite3 # 启动临时内存数据库 (:memory:) sqlite3 :memory: # 显式启动内存数据库文件不存在时自动创建不指定文件名则使用临时内…

项目开发:【悟空博客】基于SSM框架的博客平台

目录 一.导入 1.Spirng框架 2.SpirngMVC 二.项目介绍 &#xff08;一&#xff09;项目功能 &#xff08;二&#xff09;页面展示 1.注册页面 2.登录页面 3.列表页面 4.详情页面 5.编辑页面 三.准备工作 1.用户表——userinfo 2.文章表——articleinfo 3.插入数…

大话软工笔记—分离之组织和物品

一. 组织 组织在架构中既不属于“业务架构”&#xff0c;也不属于“管理架构”&#xff0c;它是由组织结构、角色、权限等要素构成。 1. 组织的概念 组织&#xff08;名词&#xff09;&#xff0c;将资源按照某个目标构建出一个有层次的集合体&#xff0c;即组织结构。 组织…