【Java开发】 Mybatis-Plus 03:IService-CRUD + 性能分析
CRUD 接口 | MyBatis-Plus以下是官网给该接口的官方介绍~通用 Service CRUD 封装IService (opens new window)接口,进一步封装 CRUD采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆, 泛型 T 为任意实体对象。
IService 接口简单来说就是对进 CRUD 的进一步封装,IService 接口与 BaseMapper接口有相同点,也有不同点,相同点在于两者都能够使用条件构造器-Wrapper(之后会介绍~),不同点在于 BaseMapper 接口的CRUD方法与SQL增删改查(insert、delete、update、select)一致,而 IService 接口的 CRUD 方法更易理解(save-增、remove-删、update-改、get-单条查询、list-多条查询、page-分页查询),而且 IService 接口最重要的在于能够使用链式查询/更改(QueryChainWrapper/UpdateChainWrapper),基础配置可见01、02。
目录
2.1 新建 service 软件包及 IBaseService.java 文件
2.2 新建 impl 软件包及 UserServiceImpl.java 文件
源码地址:尹煜 / mybatis_plus_study · GitCode
1 SQL性能分析打印
以下是MybatisPlus官网最新推荐的~
1.1 p6spy 依赖引入
pom.xml文件
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>最新版本</version>
</dependency>
1.2 配置
①配置数据源
#P6Spy配置,联合spy.properties
spring.datasource.url=jdbc:p6spy:mysql://172.0.0.1:3306/mybatis_plus?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver

②配置spy.properties
注意:# 慢SQL记录标准 2 秒
outagedetectioninterval=2,简单来说,如果sql执行时间超过2秒,就会报错
#性能分析拦截器-SQL分析打印,该插件有性能损耗,不建议生产环境使用。
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
1.3 使用
@Test
void textCapability() {
//参数是一个wrapper ,条件构造器,这里我们先不用 null
//查询全部的用户
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}

2 Service CRUD 介绍及准备
以下是官网给该接口的官方介绍~
- 通用 Service CRUD 封装IService (opens new window)接口,进一步封装 CRUD
- 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆, 泛型 T 为任意实体对象
- 如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类 对象 Wrapper 为 条件构造器
在 Spring Boot 项目中我们是这样使用该接口的(省去导入包/类代码):
2.1 新建 service 软件包及 IBaseService.java 文件
路径:src/main/java/com/yinyu/service/IBaseService.java
//如有需要用以重写IService里的抽象方法,如不需要重写也可去掉该文件,将IService<User>写在UserServiceImpl文件
public interface IBaseService extends IService<User> {
}
2.2 新建 impl 软件包及 UserServiceImpl.java 文件
路径:src/main/java/com/yinyu/service/impl/UserServiceImpl.java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IBaseService {
}
2.3 新增 IserviceTset.java 文件
路径:src/test/java/com/yinyu/IserviceTset.java
@SpringBootTest
public class IserviceTset {
@Autowired
private UserServiceImpl userService;
//下边就可以写用例了
}
2.4 Service 实践结构
如此一来,准备工作就已经是完成了~

3 Save
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
3.1 插入一条记录
@Test
public void testSave() {
User user = new User();
user.setName("Mack");
user.setAge(18);
user.setEmail("yinyu@163.com");
Boolean result = userService.save(user);//帮助用户自动生成id
System.out.println(result);//插入是否成功
System.out.println(user);//通过日志发现id会自动回填
}
输出结果:


3.2 builder()方法使用
接下来介绍一个非常好用的 builder() 构造器来高效完成实体类的构建赋值!
①实体类加注解@Builder
路径: src/main/java/com/yinyu/pojo/User.java

②使用build构造器进行赋值
相比之前的构建是不是要清爽许多~
User user1 = User.builder().name("saveBatch1").age(15).email("yinyu@163.com").build();
3.3 批量插入记录
@Test
public void testSaveBatch() {
User user1 = User.builder().name("saveBatch1").age(15).email("yinyu@163.com").build();
User user2 = User.builder().name("saveBatch2").age(16).email("yinyu@163.com").build();
List<User> list = Arrays.asList(user1,user2);
Boolean result = userService.saveBatch(list);//帮助用户自动生成id
System.out.println(result);//插入是否成功
System.out.println(list);//通过日志发现id会自动回填
}
可以看到实质上它是进行了两次插入sql~


4 SaveOrUpdate
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
这个方法的逻辑是:若存在主键,先执行查找,查询失败返回0(查询失败后执行插入),在执行更新,更新失败返回0,若不存在主键,直接执行插入。
主要存在以下三种情况:
4.1 插入的数据不带id
@Test
public void testSaveOrUpdate1() {
User user1 = User.builder().name("testSaveOrUpdate1").age(15).email("yinyu@163.com").build();
Boolean result = userService.saveOrUpdate(user1);//帮助用户自动生成id
System.out.println(result);//SaveOrUpdate是否成功
}
可以看到执行插入 SQL,这是因为 SaveOrUpdate 是根据 id(主键)来进行判断的,如果能在数据表中匹配到对应的id,那么他才会执行更新操作。

![]()
4.2 插入的数据带id且数据库存在
@Test
public void testSaveOrUpdate2() {
User user1 = User.builder().id(1L).name("testSaveOrUpdate2").age(18).email("yinyu@163.com").build();
Boolean result = userService.saveOrUpdate(user1);
System.out.println(result);//SaveOrUpdate是否成功
}
当 id(主键)设为1时,修改成功!可以看到实际上进行了两步操作,查询+更新


4.3 插入的数据带id,且数据库不存在
@Test
public void testSaveOrUpdate3() {
User user1 = User.builder().id(10L).name("testSaveOrUpdate3").age(18).email("yinyu@163.com").build();
Boolean result = userService.saveOrUpdate(user1);
System.out.println(result);//SaveOrUpdate是否成功
}
插入成功,不过也是经历了两步,查询失败+插入~


5 Remove
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
remove()的入参是Wrapper条件构造器,会在下一篇文章04进行介绍~
5.1 根据ID删除
顾名思义,当然Remove方法也支持 idList 进行删除。
@Test
public void testRemove1() {
//删除id=1586599555079684107L的记录
Boolean result = userService.removeById(1586599555079684107L);
System.out.println(result);//SaveOrUpdate是否成功
}
因为之前设置了逻辑删除(在02),因此该记录实质上没有删除,而是进行逻辑删除。


5.2 根据 columnMap 条件删除
@Test
public void testRemove2() {
//删除 name = 尹煜 , age = 3 的记录
HashMap<String, Object> map = new HashMap<>();
map.put("name","尹煜");
map.put("age",3);
Boolean result = userService.removeByMap(map);
System.out.println(result);//SaveOrUpdate是否成功
}
算是多条件删除吧,注意还是逻辑删除哦~


6 Update
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
和remove一样,update()的入参也是Wrapper条件构造器,会在下一篇文章04进行介绍~
需要注意的是,updateById 的入参是实体类而不是 id ,因为更新信息只有一个 id 肯定是不够的嘛,而且 id 是必须加进去的!
updateBatchById 也就是 updateById 升级版,入参是实体类的集合,这边就不多演示了。
@Test
public void testUpdate() {
User user1 = User.builder().id(1L).name("testUpdate").age(18).email("yinyu@163.com").build();
Boolean result = userService.updateById(user1);
System.out.println(result);//SaveOrUpdate是否成功
}
更新执行成功!


7 Get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
get 是查询一条数据,不同于 mapper 的 select,iservice 将其分为了 get-查询1条 和 list-查询多条!
@Test
public void testGet() {
User result = userService.getById(1L); //返回数据是User实体类
System.out.println(result);//SaveOrUpdate是否成功
}

8 List
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
List 方法的功能更为丰富,我贴上了最常用的几个来和大家分享~
8.1 查询所有
@Test
public void testList1() {
List<User> result =userService.list(); //返回数据是实体类集合
System.out.println(result);
}

8.2 根据ID批量查询
查询 id=1 和 id=2 的记录~
@Test
public void testList2() {
List<Long> ids = Arrays.asList(1L,2L);
List<User> result =userService.listByIds(ids); //返回数据是实体类集合
System.out.println(result);
}

8.3 根据 columnMap 条件查询
@Test
public void testList3() {
HashMap<String,Object> map = new HashMap<>();
map.put("name","尹煜");
map.put("age",3);
List<User> result =userService.listByMap(map); //返回数据是实体类集合
System.out.println(result);
}

8.4 查询所有列表-返回map集合
@Test
public void testList4() {
List<Map<String, Object>> result =userService.listMaps(); //返回数据是map集合
System.out.println(result);
}

8.5 查询全部记录-返回id集合
@Test
public void testList5() {
List<Object> result =userService.listObjs(); //返回数据是Object集合
System.out.println(result);
}
可以看到,返回的是主键 id 的集合~

9 Page
前提:已配置分页插件(链接:Mybatis-Plus 02-5.2)~
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
9.1 无条件分页查询-返回实体
@Test
public void testPage1(){
//参数一current:当前页 参数二size:页面大小
//写法1:
Page<User> page1 = new Page<>(2,3);
userService.page(page1,null);
page1.getRecords().forEach(System.out::println);
System.out.println("总页数==>"+page1.getPages());
//写法2:
Page<User> page2 = userService.page(new Page<>(1,2),null);
page2.getRecords().forEach(System.out::println);
System.out.println("总页数==>"+page2.getPages());
}
可以看到实际进行了两步操作,首先是查询全部记录,然后执行LIMIT语句~

9.2 无条件分页查询-返回map
@Test
public void testPage2(){
//参数一current:当前页 参数二size:页面大小
//写法1:
Page<Map<String, Object>> page1 = new Page<>(2,3);
userService.pageMaps(page1,null);
page1.getRecords().forEach(System.out::println);
System.out.println("总页数==>"+page1.getPages());
//写法2:
Page<Map<String, Object>> page2 = userService.pageMaps(new Page<>(1,2),null);
page2.getRecords().forEach(System.out::println);
System.out.println("总页数==>"+page2.getPages());
}
可以看到返回的是map的集合~

10 Count
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
@Test
public void testCount() {
long result =userService.count(); //返回数据是记录数
System.out.println(result);
}

11 Chain 链式查询/更新
这算是 service 和 mapper 比较重要的不同点了,这也是进一步解放生产力的方法~
11.1 query
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
①queryone
以.one结尾,返回的是一条数据,若实际返回多条会报错~
@Test
public void testqueryone() {
User result =userService.query().eq("name", "尹煜").one();; //返回数据是实体类
System.out.println(result);
}
查询成功~

②querylist
@Test
public void testquerylist() {
List<User> result =userService.query().eq("age", 18).isNotNull("name").list();; //返回数据是实体类集合
System.out.println(result);
}
查询成功~

③lambdaQuery
lambda也就是匿名内部类,入参的话是函数式接口,参考如下👇
@Test
public void testlambdaQuery() {
List<User> result =userService.lambdaQuery().eq(User::getAge,18).list();; //返回数据是实体类集合
System.out.println(result);
}

11.2 update
// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
update 和 query 的操作是类似的,一个是更新,一个是查询,最后以 update(实体类)结尾,更新的逻辑和本文4-SaveOrUpdate不一样👇
实体类不论是否存在主键id,那么满足链式条件的所有数据都会更新成实体类的形式👇
①不存在主键
@Test
public void testupdate() {
User user1 = User.builder().name("saveBatch1").age(15).email("yinyu@163.com").build();
Boolean result =userService.update().eq("age", 18).isNotNull("name").update(user1);
System.out.println(result);
}

①存在主键
@Test
public void testupdate2() {
User user1 = User.builder().id(1L).name("testupdate2").age(18).email("yinyu@163.com").build();
Boolean result =userService.update().eq("age", 15).isNotNull("name").update(user1);
System.out.println(result);
}
可以看到,虽然实体类存在主键,但是其他记录(id不等于1)还是改变了,可能user1是作为一个模板来进行更新,是否有id并不重要~

总结
大家如果有疑问都可以评论提出,有不足之处请大家批评指正,希望能多结识这方面的朋友,共同学习、共同进步。
更多推荐

所有评论(0)