MyBatis-Plus是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。使用MyBatis-Plus能够极大地提升开发效率。
IService和BaseMapper辨析
查看MyBatis-Plus的文档,我们可以发现,在其内部存在着两种CRUD操作接口,Iservice和BaseMapper,如果只是用增删改查,会发现除了方法名称不同外,两者的功能是一致的。那如何在开发中进行合理的选择?
我们先来看一下官网的描述:
Service CRUD 接口
- 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用
get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分 Mapper
层避免混淆,
- 泛型
T
为任意实体对象
- 建议如果存在自定义通用 Service 方法的可能,请创建自己的
IBaseService
继承 Mybatis-Plus
提供的基类
- 对象
Wrapper
为 条件构造器
Mapper CRUD接口
- 通用 CRUD 封装BaseMapper接口,为
Mybatis-Plus
启动时自动解析实体表关系映射转换为 Mybatis
内部对象注入容器
- 泛型
T
为任意实体对象
- 参数
Serializable
为任意类型主键 Mybatis-Plus
不推荐使用复合主键约定每一张表都有自己的唯一 id
主键
- 对象
Wrapper
为 条件构造器
MyBatis-Plus 提供两种包含预定义增删改查操作的接口和一个类:
BaseMapper
接口:针对dao/mapper层的方法封装 CRUD
IService<T>
接口:针对业务逻辑层的封装需要指定Dao层类和对应的实体类 是在BaseMapper基础上的加强
ServiceImpl<M extends BaseMapper<T>, T>
类:针对业务逻辑层的实现
我们一般开发的模板代码如下:
1 2 3 4 5 6 7 8 9 10
| @Repository public class ArticleDao extends ServiceImpl<ArticleMapper, ArticleDO> { @Resource private ArticleMapper articleMapper; }
public interface ArticleMapper extends BaseMapper<ArticleDO> { }
mapper/ArticleMapper.xml
|
我们来看一下继承结构:
是不是感觉很奇怪,我们明明只写Mapper就能够实现CRUD的,而继承了ServiceImpl
,依旧实现了IService
接口,同时依赖BaseMapper
,有点混乱了。
我们来看一下这两个接口中的方法:
可以发现,IService
是BaseMapper
的扩充,不但包含了所有基本方法,还加入了很多批处理功能。另外,也有一些差异:
- 方法命名差异:比如,插入数据:在BaseMapper使用 insert,在IService使用 save ;
- IService 提供了批处理操作的方法,而 BaseMapper 没有提供相应的批处理操作;
- IService 依赖于 Spring 容器,而 BaseMapper 不依赖;
- 如果既要使用批处理操作,又要添加自己的数据库操作,那就必须两个接口一起用。
可以这样认为:
Mapper简化了单表的SQL操作步骤(CRUD),而Serivce实现的了更加丰富的CRUD,加入了很多批处理功能。但是必须依赖Mapper,因此需要编写Mapper接口。
以下是通义千问的回答:
在MyBatis Plus框架中,IService
接口和BaseMapper
接口分别服务于不同的层次和目的。
BaseMapper:
- BaseMapper是一个抽象的接口,它定义了一系列基本的CRUD操作方法,这些方法对应于数据库的基本操作,如查询、插入、更新和删除等。
- BaseMapper并不直接依赖Spring容器,因为它只定义了数据库操作的契约,具体的数据库交互逻辑是由MyBatis框架通过XML映射文件或者注解的方式来实现的。
- MyBatis的Mapper接口可以直接由MyBatis框架处理,无需Spring容器进行实例化和依赖注入,Mapper接口的实现是由MyBatis在运行时动态生成的代理对象。
IService:
- IService是MyBatis Plus提供的一个服务层接口,它扩展了BaseMapper的基础CRUD功能,通常包含一些业务逻辑相关的操作,例如事务处理、组合查询以及其他领域逻辑等。
- IService接口的设计意图是将其服务类对接到Spring容器中,以便利用Spring的依赖注入机制管理服务类的生命周期,以及注入其他的依赖,如事务管理器、DAO层对象(也就是实现了BaseMapper接口的对象)等。
- 默认实现
com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
通过继承BaseMapper,并在Spring容器中注册,这样@Service注解标注的ServiceImpl类就能够被Spring容器管理,从而具备了依赖注入的能力。
总结来说,**BaseMapper
关注于数据库操作的具体接口定义,而IService
则是更高一层的服务接口,它依赖Spring容器来获取依赖关系并参与到Spring框架所管理的整个应用上下文中。通过这样的设计,开发者可以在服务层实现复杂的业务逻辑,并利用Spring容器进行统一的依赖管理和生命周期管理。**
关于IService和BaseMapper的一些说明
以下内容转载自https://cloud.tencent.com/developer/article/2362497
Service CRUD 接口
- 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行、 remove 删除 、list 查询集合 、page 分页 前缀命名方式区分 Mapper 层避免混淆
- 泛型 T 为任意实体对象
- 建议如果存在自定义通用 Service 方法的可能,就创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
- 对象 Wrapper 为条件构造器
1 2 3 4 5 6
|
public interface IOrderService extends IService<Order> { }
|
IOrderService 接口扩展了 MyBatis-Plus 提供的 IService<Order>
接口,表示它定义了与 Order 实体相关的业务逻辑方法。IService<Order>
接口是 MyBatis-Plus 的一部分,提供了一组通用的服务方法,包括常见的 CRUD(创建、读取、更新、删除)操作。
Mapper CRUD 接口
- 通用 CRUD 封装BaseMapper接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
- 泛型 T 为任意实体对象
- 参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
- 对象 Wrapper 为条件构造器
1 2 3 4 5 6
|
public interface OrderMapper extends BaseMapper<Order> { }
|
OrderMapper 接口继承了 MyBatis-Plus 提供的 BaseMapper<Order>
接口,这意味着它会继承一系列通用的数据库操作方法,包括常见的查询、插入、更新、删除等 CRUD 操作。这样的设计遵循了 MyBatis-Plus 的规范,使得开发者无需手动实现这些通用的数据库操作,而是可以直接在 OrderMapper 接口中使用这些方法。
在ServiceImpl<M extends BaseMapper<T>, T>
类中,M 是mapper对象,T 是实体。
ServiceImpl 是 MyBatis-Plus 提供的通用 Service 实现类。它已经实现了 IService 接口,包含了通用的 CRUD 方法的实现。在你的业务 Service 实现类中,可以直接继承 ServiceImpl,从而获得这些通用的数据库操作方法。
1 2 3 4 5 6 7 8 9
|
@Service @RequiredArgsConstructor public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService { }
|
OrderServiceImpl 是 IOrderService
接口的实现类,通过继承 ServiceImpl<OrderMapper, Order>,该类直接继承了 MyBatis-Plus 提供的默认 Service 实现,并指定了泛型参数为 OrderMapper 和 Order。因此,OrderServiceImpl 中无需额外编写方法,即可直接使用 ServiceImpl 中提供的通用的 CRUD 方法。
再根据项目看一下对应的继承关系图
既然ServiceImpl类也实现了IService接口,那么如果UserServiceImpl直接继承ServiceImpl类不就行了吗?为何还要自定义一个继承了IService接口的IUserService接口?
这是因为Spring自动注入要求是以接口为标准,在Controller里注入的Service要是一个接口才符合Spring的规范(当然注入类也行)!
Mapper和IService的使用场景
使用 Mapper的场景:
- Mapper 主要用于执行数据库的 CRUD 操作,包括查询、插入、更新和删除等底层数据库访问操作。
- 定如果你有一些定制化的 SQL 需求,或者需要使用 MyBatis 的 XML 映射文件,那么直接使用 Mapper 可能更合适。你可以在 Mapper 接口中定义自己的 SQL 方法,并在 XML 文件中编写相应的 SQL 语句。
- 底层数据库访问: 如果你的操作更偏向于底层的数据库访问,例如需要直接操作数据库中的某个字段,或者使用一些特殊的 SQL 查询,那么直接使用 Mapper 会更直观和方便。使用 IService的场景:
- IService 主要用于定义业务逻辑层的接口,包括业务相关的操作方法。它提供了一些通用的业务逻辑方法,如保存、查询、更新等,更适用于业务操作。
- 如果你的操作涉及到事务,IService 提供了一些事务控制的方法,例如 saveOrUpdate,适合在业务逻辑层进行事务控制。
- IService 更抽象,更适用于高层次的业务操作。它对业务逻辑进行了封装,使得业务代码更清晰,易于维护。
组合使用:
在项目的一般开发流程中,先定义Mapper接口和对应的XML文件实现对数据库的操作,然后在Service层中注入Mapper接口的实例,并调用Mapper的方法来实现业务逻辑,提供更高层次的抽象和封装。
因此在项目开发中,通常会同时使用 Mapper 和 IService,将数据访问层和业务逻辑层分离。Mapper 用于处理底层数据库访问,而 IService 用于封装业务逻辑。这种组合使用的方式能够更好地利用 MyBatis-Plus 提供的功能,使代码结构更清晰,同时也便于单元测试和维护。
总结:
IService简直是BaseMapper的大扩充,不但包含了所有基本方法,还加入了很多批处理功能。
关于改进 IService 和 ServiceImpl 的建议
大家看了上面的分析,会不会还有点凌乱?Mybatis-Plus明明是一个Dao层的工具,为什么要提供 IService 呢?。应该是MP想让大家在写ServiceImpl
的时候,直接继承MP的,如果项目的CRUD很简单,就不需要写DAO层代码了。有人在Github上提了issue https://github.com/baomidou/mybatis-plus/issues/5764,我个人认为是比较有道理的。
MyBatis-Plus的基本使用
为了避免和Service混淆的问题,我们在项目中如下命名ArticleDao
,依然视为Dao层,而不是service层!
1 2 3 4 5 6 7 8 9 10 11
| @Repository public class ArticleDao extends ServiceImpl<ArticleMapper, ArticleDO> { @Resource private ArticleMapper articleMapper; }
public interface ArticleMapper extends BaseMapper<ArticleDO> { }
mapper/ArticleMapper.xml
|
代码结构如下:
DAO层提供的方法
以ArticleDAO为例,其他类似,不再赘述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public ArticleDTO queryArticleDetail(Long articleId);
public Long saveArticleContent(Long articleId, String content);
public void updateArticleContent(Long articleId, String content, boolean update);
public List<ArticleDO> listArticlesByUserId(Long userId, PageParam pageParam);
public List<ArticleDO> listArticlesByCategoryId(Long categoryId, PageParam pageParam);
public Long countArticleByCategoryId(Long categoryId);
public Map<Long, Long> countArticleByCategoryId();
public List<ArticleDO> listArticlesBySearchKey(String key, PageParam pageParam);
public List<ArticleDO> listSimpleArticlesByBySearchKey(String key);
public int incrReadCount(Long articleId);
public int countArticleByUser(Long userId);
public List<SimpleArticleDTO> listHotArticles(PageParam pageParam);
public List<SimpleArticleDTO> listAuthorHotArticles(long userId, PageParam pageParam);
public List<YearArticleDTO> listYearArticleByUserId(Long userId);
|
如何分页
新建一个PageParam
类,作为数据库分页参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import io.swagger.annotations.ApiModelProperty; import lombok.Data;
@Data public class PageParam {
public static final Long DEFAULT_PAGE_NUM = 1L; public static final Long DEFAULT_PAGE_SIZE = 10L;
public static final Long TOP_PAGE_SIZE = 4L;
@ApiModelProperty("请求页数,从1开始计数") private long pageNum;
@ApiModelProperty("请求页大小,默认为 10") private long pageSize; private long offset; private long limit;
public static PageParam newPageInstance() { return newPageInstance(DEFAULT_PAGE_NUM, DEFAULT_PAGE_SIZE); }
public static PageParam newPageInstance(Integer pageNum, Integer pageSize) { return newPageInstance(pageNum.longValue(), pageSize.longValue()); }
public static PageParam newPageInstance(Long pageNum, Long pageSize) { if (pageNum == null || pageSize == null) { return null; }
final PageParam pageParam = new PageParam(); pageParam.pageNum = pageNum; pageParam.pageSize = pageSize;
pageParam.offset = (pageNum - 1) * pageSize; pageParam.limit = pageSize;
return pageParam; }
public static String getLimitSql(PageParam pageParam) { return String.format("limit %s,%s", pageParam.offset, pageParam.limit); }
}
|
辅助方法:
1 2 3 4 5 6 7 8 9 10
| public PageParam buildPageParam(Long page, Long size) { if (page <= 0) { page = PageParam.DEFAULT_PAGE_NUM; } if (size == null || size > PageParam.DEFAULT_PAGE_SIZE) { size = PageParam.DEFAULT_PAGE_SIZE; } return PageParam.newPageInstance(page, size); }
|
返回对象:PageVo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;
import java.util.List;
@Data @AllArgsConstructor @NoArgsConstructor public class PageVo<T> {
private List<T> list;
private long pageSize;
private long pageNum;
private long pageTotal;
private long total;
@Deprecated public PageVo(List<T> list, int pageSize, int pageNum, int total) { this.list = list; this.total = total; this.pageSize = pageSize; this.pageNum = pageNum; this.pageTotal = (int) Math.ceil((double) total / pageSize); }
public PageVo(List<T> list, long pageSize, long pageNum, long total) { this.list = list; this.total = total; this.pageSize = pageSize; this.pageNum = pageNum; this.pageTotal = (long) Math.ceil((double) total / pageSize); }
@Deprecated public static <T> PageVo<T> build(List<T> list, int pageSize, int pageNum, int total) { return new PageVo<>(list, pageSize, pageNum, total); }
public static <T> PageVo<T> build(List<T> list, long pageSize, long pageNum, long total) { return new PageVo<>(list, pageSize, pageNum, total); } }
|
返回的结果对象格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import io.swagger.annotations.ApiModelProperty; import lombok.Data;
import java.io.Serializable;
@Data public class ResVo<T> implements Serializable { private static final long serialVersionUID = -510306209659393854L; @ApiModelProperty(value = "返回结果说明", required = true) private Status status;
@ApiModelProperty(value = "返回的实体结果", required = true) private T result;
public ResVo() { }
public ResVo(Status status) { this.status = status; }
public ResVo(T t) { status = Status.newStatus(StatusEnum.SUCCESS); this.result = t; }
public static <T> ResVo<T> ok(T t) { return new ResVo<T>(t); }
@SuppressWarnings("unchecked") public static <T> ResVo<T> fail(StatusEnum status, Object... args) { return new ResVo<>(Status.newStatus(status, args)); }
public static <T> ResVo<T> fail(Status status) { return new ResVo<>(status); } }
|
下面以获取tag列表为例:
调用入口:
1 2 3 4 5 6 7 8 9 10
|
@GetMapping(path = "tag/list") public ResVo<PageVo<TagDTO>> queryTags(@RequestParam(name = "key", required = false) String key,@RequestParam(name = "pageNumber", required = false, defaultValue = "1") Integer pageNumber,@RequestParam(name = "pageSize", required = false, defaultValue = "10") Integer pageSize) { PageVo<TagDTO> tagDTOPageVo = tagService.queryTags(key, PageParam.newPageInstance(pageNumber, pageSize)); return ResVo.ok(tagDTOPageVo); }
|
调用service层:
1 2 3 4 5 6 7
| public PageVo<TagDTO> queryTags(String key, PageParam pageParam) { List<TagDTO> tagDTOS = tagDao.listOnlineTag(key, pageParam); Integer totalCount = tagDao.countOnlineTag(key); return PageVo.build(tagDTOS, pageParam.getPageSize(), pageParam.getPageNum(), totalCount); }
|
调用DAO层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public List<TagDTO> listOnlineTag(String key, PageParam pageParam) { LambdaQueryWrapper<TagDO> query = Wrappers.lambdaQuery(); query.eq(TagDO::getStatus, PushStatusEnum.ONLINE.getCode()) .eq(TagDO::getDeleted, YesOrNoEnum.NO.getCode()) .and(StringUtils.isNotBlank(key), v -> v.like(TagDO::getTagName, key)) .orderByDesc(TagDO::getId); if (pageParam != null) { query.last(PageParam.getLimitSql(pageParam)); } List<TagDO> list = baseMapper.selectList(query); return ArticleConverter.toDtoList(list); }
public Integer countOnlineTag(String key) { return lambdaQuery() .eq(TagDO::getStatus, PushStatusEnum.ONLINE.getCode()) .eq(TagDO::getDeleted, YesOrNoEnum.NO.getCode()) .and(!StringUtils.isEmpty(key), v -> v.like(TagDO::getTagName, key)) .count() .intValue(); }
|
参考
- https://juejin.cn/post/6844904096898482189
- https://cloud.tencent.com/developer/article/2362497
- https://github.com/baomidou/mybatis-plus/issues/5764
- https://blog.csdn.net/weixin_42516475/article/details/130115388
- https://baomidou.com/