Mybatis Plus版本升级及项目业务开发规范简要说明
码农: Alias
> 版本升级说明:此次将Mybatis Plus的版本由2.x升级到3.1.2版本,最新版本是3.2,由于3.2版本的sql分析打印插件变动需要额外配置,故暂时不升级到3.2版本(因为懒)。一.从Mybatis逆向工程说起
- MyBatis-Plus 的代码生成器可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码。执行代码生成类,生成代码,如图:
- 延用之前项目开发的规范,在我们逆向工程代码生成策略中主要有这两个:
strategy.setSuperEntityClass("xxx.xxx.dao.SuperEntity");
strategy.setSuperMapperClass("xxx.xxx.dao.SuperMapper");
strategy.setSuperServiceImplClass("xxx.xxx.service.SuperService");
前两行代码主要是用来设定生成的实体类与mapper接口的超类。其中SuperMapper继承了MP的BaseMapper类,该类已经封装了常规的CRUD方法(因此,提醒各位,DAO层常规的CRUD接口可以不必再次实现,以达到常规CRUD方法去SQL化、去XML配置化)。第三行代码主要是设置Service实现类的超类SuperService。
使用了3.1.2版本的MP生成的Service接口提供了通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分 Mapper
层避免混淆。如果存在自定义通用 Service 方法的可能,可以创建 IBaseService
继承 Mybatis-Plus
提供的基类。目前CMS项目的SuperService类就是自定义通用Service方法的类,我在该类的基础上继承了ServiceImpl,扩展了ServiceImpl提供的功能。
-
Service样例代码
/** * <p> * 文章内容扩展表,增加扩展字段 服务类 * </p> * * @author alias * @since 2019-12-05 */ public interface IInfoExtendService extends IService<InfoExtend> { }
接着我们看看Service接口实现类,该类继承了SuperService类差不多。该类也聚合BaseMapper、ServiceImpl等类的功能。
- ServiceImpl源码:
@SuppressWarnings("unchecked")
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
protected Log log = LogFactory.getLog(getClass());
@Autowired
protected M baseMapper;
.....
}
-
Service实现类样例代码:
/** * <p> * 文章内容扩展表,增加扩展字段 服务实现类 * </p> * * @author alias * @since 2019-12-05 */ @Service public class InfoExtendServiceImpl extends SuperService<InfoExtendMapper, InfoExtend> implements IInfoExtendService { }
-
SuperService源码:
public abstract class SuperService<M extends BaseMapper<T>,T> extends ServiceImpl<M,T> { private static final Log logger = LogFactory.getLog(SuperService.class); @Autowired protected M superMapper; ...... }
二.版本升级后,从实际业务开发入手
由上面可知,我们的业务接口层继承了IService接口,接口的实现类继承了SuperService类。因此,我们在开发时只需注入接口类即可使用到MP提供的CRUD方法(包括BaseMapper、IService等方法)。推荐业务层之间的CRUD方法调用只通过Servie接口调用,因为注入Service接口后基本无所不能了= =,话不多说,上代码。
1.我们以KnInfoExtend、KnInfo这两张表为例子说明。
KnInfoExtend相关的Service接口以及实现类在上面已经给出了,这里给我KnInfo相关的业务类。
-
KnInfo相关业务类及接口:
public interface InfoService extends IService<Info> { //以下接口是不推荐使用方法示范 public Info saveInfo(InfoVO infoVo); public InfoVO findInfo(String id); } @Service(value = "knInfoService") public class InfoServiceImpl extends SuperService<InfoMapper, Info> implements InfoService { //不提倡的使用方式 @Autowired private InfoExtendMapper InfoExtendMapper; //不提倡的使用方式 @Autowired private InfoExtendServiceImpl InfoExtendServiceImpl; //提倡使用方式 @Autowired private IInfoExtendService infoExtendService; }
以上说的不提倡的使用方式是基于使用Mybatis Plus框架时不提倡的使用方式,因为人家给你一辆法拉利,你用来当拖拉机。
对于纯粹使用mybatis框架的,以上不提倡做法是很常规的,包括我也不是这么做了= =
同学们,上面的不提倡的使用方式你们是否编写过,不管你承不承认,反正老衲年轻的时候确实有这么写过,这么写的原因可能如下:
-
我在我的业务处理中只需要调用InfoMapper底层的CRUD的接口就行了
-
有时候业务聚合需要,不得不依赖InfoExtendServiceImpl来调用其方法。
-
曾有dalao提倡即使数据底层的CRUD的接口,可以在Service实现类依赖Mapper接口,在实现类中提供这个方法。实例如下:
@Mapper public interface XxxMapper { //定义selectByXxxx方法 XXXDTO selectByXxxx(XXXDTO xxx); } // XxxMapper.xml文件巴拉巴拉一堆暴写,自己意会不言传哈 @Service public class XxxServiceImpl implements IXxxService { @Autowired private XxxMapper xxxMapper; public XXXDTO selectByXxxx(XXXDTO xxx) { return xxxMapper.selectByXxxx(xxx); } } @Service public class YyyServiceImpl implements IYyyService { //@Autowired // private XxxMapper xxxMapper; @Autowired private IXxxService xxxService; public XXXDTO selectByXxxx(XXXDTO xxx) { return xxxService.selectByXxxx(xxx); } }
有些同学是不是觉得没必要这样子,觉得这样子繁琐。
-
扯回来,回归正题,基于Mybatis Plus框架开发,建议不要编写以上不推荐的使用方式。人家MP宗旨就是为简化开发、提高效率而生。现在我们看看为何只注入IInfoExtendService就能使用那么多功能的。
- Service实现类提供的CRUD方法,即聚合了IService、ServiceImpl提供的方法。
-
如果IService、ServiceImpl提供的方法不是你想要的,你想直接操作Mapper接口,肯定是可以,毕竟ServiceImpl已经注入了Mapper接口了
-
找茬,同学们肯定是想说,万一我要自定义条件查询呢,换做之前使用mybatis的习惯还是要在Mapper接口编写接口方法,在Xml文件巴拉巴拉写一堆。然后你这里就会想,这种情况怎么处理。方法肯定是有的,那就是使用MP的条件构造器,这里不多说,很好用的一个东西,自己看官方文档
-
接着找茬,用了一段时间,同学发现,复杂查询(比如关联查询)貌似条件构造器也支持不了,只能回归原始,手操sql了。那这时候,又回归了Mybatis使用时的做法,在Mapper接口编写接口方法,在Xml文件巴拉巴拉写一堆。接下来呢?我推荐的做法是只注入Service接口,那我在Mapper接口自定义的方法怎么调用呀?这里确实是找对茬了,不是说只注入Service接口的方式调用不了,只是我提供的方法比较笨重(大佬有好的方法告知一声)。
- 方法一:类型强转
KnInfoExtendMapper baseMapper = (KnInfoExtendMapper) knInfoExtendService.getBaseMapper(); //调用自定义CRUD方法 baseMapper.findKnInfoExtendByInfoId();
- 方法二:在Service实现类,申明该方法,间接调用自定义的Mapper方法
//KnInfoMapper接口: KnInfoExtend findKnInfoExtendByInfoId(String id); //KnInfoExtendServiceImpl类: @Override public KnInfoExtend findKnInfoExtendByInfoId() { KnInfoExtend knInfoExtend = this.superMapper.findKnInfoExtendByInfoId("id"); return knInfoExtend; } //InfoServiceImpl类 knInfoExtendService.findKnInfoExtendByInfoId("");
-
至此,推荐业务层之间的CRUD方法调用只通过Servie接口调用大致有如下原因:
- MP已提供通用的Service CRUD方法
- MP的构造器是我们可以摆脱手操sql的烦恼(复杂查询可能就无法了)
- MP的框架封装能力,MP已在在Service实现类中继承ServiceImpl类,该类内部聚合了BaseMapper,已经自动为你注入了。所以你在业务层再次注入就显得多余了。
三.啰嗦多几句,常规写法规范定义
这里啰嗦多几句,讲讲一些常规的编写规范吧。
1.这里讲自定义分页的写法,分页其实也就是使用MP分页插件的实例的方式来开发吧
- UserMapper.java 方法内容
public interface UserMapper{//可以继承或者不继承BaseMapper
/**
* <p>
* 查询 : 根据state状态查询用户列表,分页显示
* 注意!!: 如果入参是有多个,需要加注解指定参数名才能在xml中取值
* </p>
*
* @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位(你可以继承Page实现自己的分页对象)
* @param state 状态
* @return 分页对象
*/
IPage<User> selectPageVo(Page page, @Param("state") Integer state);
}
- UserMapper.xml 等同于编写一个普通 list 查询,mybatis-plus 自动替你分页
<select id="selectPageVo" resultType="com.baomidou.cloud.entity.UserVo">
SELECT id,name FROM user WHERE state=#{state}
</select>
- UserServiceImpl.java 调用分页方法
public IPage<User> selectUserPage(Page<User> page, Integer state) {
// 不进行 count sql 优化,解决 MP 无法自动优化 SQL 问题,这时候你需要自己查询 count 部分
// page.setOptimizeCountSql(false);
// 当 total 为小于 0 或者设置 setSearchCount(false) 分页插件不会进行 count 查询
// 要点!! 分页返回的对象与传入的对象是同一个
return userMapper.selectPageVo(page, state);
}
2.实体类常量使用
在我们的逆向工程工具中,我在生成策略加了strategy.setEntityColumnConstant(true);
,这个就是在生成我们的实体类的时候,加上每个字段的常量(即数据库定义的colum名称),加上这个也是有用处的,这个举个栗子
- Info实体类
package com.oppein.mtds.cms.info.dao.entity;
import com.oppein.mtds.cms.info.dao.SuperEntity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
@TableName("kn_info")
public class Info extends SuperEntity<Info> {
private static final long serialVersionUID = 1L;
/**
* 栏目id
*/
@TableField("category_id")
private String categoryId;
/**
* 标题
*/
private String title;
/*文章父id*/
@TableField("pid")
private String pid;
/**
* 简短标题
*/
@TableField("short_title")
private String shortTitle;
/**
* tag标签
*/
private String tag;
......
public static final String PID = "pid";
public static final String CATEGORY_ID = "category_id";
public static final String TITLE = "title";
public static final String SHORT_TITLE = "short_title";
public static final String TAG = "tag";
......
}
- 业务实现类业务处理:
public void demo(InfoVO infoVO) {
Wrapper wrapper = new QueryWrapper()
//推荐写法
.eq(Objects.nonNull(infoVO.getId()), Info.ID, infoVO.getId())
//不推荐这样写,具体为啥,不用我解释了
.eq(Objects.nonNull(infoVO.getId()), "id", infoVO.getId());
List list = this.selectObjs(wrapper);
}
上面在使用条件构造器进行查询的时候,eq方法的入参有column名,不推荐直接写column名,而是使用实体类生成常量。