service接口
基本用法
mybatisplus同时也提供了service接口,继承后一些基础的增删改查的service代码,也不需要去书写。
接口名为iservice
,而iservice也继承了irepository
,这里提供的方法跟basemapper相比只多不少,整体还是分为增删改查这几大类。只不过查询的类型占大半。
首先先看新增:
save(t):接收一个泛型参数,
savebatch():接收一个collection集合,用于批量新增。
saveorupdate():接受一个泛型参数,会进行判断该对象有无id,,如果有则认为是一个update操作,反之则为insert操作,saveorupdatebatch():方法支持批量新增及更新。
再看删除操作:
removebyid():只删除一个
removebyids():批量删除,where条件后面用的是in
关键字
修改操作:
剩下的都是查询操作:
将其分为以下几类:
如果只查一条数据,就调用get开头的方法:
查询多条数据则为list:
listbyids:传入一个id的集合,返回一个list集合
list():查询全部,或者基于wrapper做复杂查询
查询数量就调用count开头的方法:
分页查询就调用page开头的方法:
在进行一些复杂查询时,就需要新建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 { }
创建测试类:
@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); } }
测试新增操作:
查询操作:
小结:
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.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包以及对应的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 { @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 = "根据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批量查询用户接口") @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 = "根据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层实现方法。
启动:在浏览器中进入用户管理接口文档
测试新增接口:
测试成功,查看数据库:
测试查询接口:
测试批量查询接口:
测试扣减接口:
测试成功。
测试删除用户接口:
测试成功。
总结:
对于一些简单的增删改查的方法,可以直接在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(); }
测试:
测试成功,
以上演示的是lambdaquery。
案例展示:iservice的lambda更新
需求:改造根据id修改用户余额的接口,要求如下
- 完成对用户的校验
- 完成对用户余额校验
- 如果扣减后余额为0,则将用户status修改为冻结状态(2)
这与我们前面的扣减余额接口一致,直接在该接口上进行修改。
@transactional 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.更新用户余额 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),比较并替换,如果余额不相同,就会回滚
进行测试:
测试成功。
案例展示: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; } @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)); }
测试结果:耗时: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.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,有提升了将近一倍,而且数据量越大,差距越明显。
总结:
在批量插入数据时,提供了三种方案:
- 普通for循环逐条插入速度极差,不推荐(原因:每次只提交一条数据插入数据库,数据库也是逐条执行)
- 默认情况下mybatisplus的批量新增,基于预编译的批处理,性能良好(原因:一次性提交100条数据插入数据库,但数据库依旧是逐条插入)
- 配置jdbc参数:
rewritebathedstatements = true
,性能最佳(一次性提交100条数据插入数据库,数据库也是批量插入)
以上就是service接口的全部用法,让我们一起加油!
到此这篇关于mybatisplus--核心功能--service接口的文章就介绍到这了,更多相关mybatisplus service接口内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论