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名,而是使用實體類生成常量。