簡介
Mybatis-Plus(簡稱MP)是一個 Mybatis 的增強工具,在 Mybatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。
使用它可以簡化單表的操作, 節省開發時間, 國人寫的文檔已經非常通俗易懂了, 所以這里只是對其進行一些規范,便於多人協作開發
如果不了解mp, 請先閱讀官方文檔, 大約耗時半小時以內
mp最新的mybatis版本是3.4.6, mybatis-spring版本是1.3.2 比我們的全局配置稍微高了一些, 看了下相關的更新以及自測,發現是兼容的, 但是不排除有些操作不支持, 支持的操作已經足夠簡化開發了
現在mp最新的版本已經升級到3.X, 所以這里2.X的文檔不在維護了
名詞解釋
GlobalConfig -> 指的是mp的全局配置文件, 版本2.X和 3.X的變化還是比較大, 可以直接看源碼的注釋
BaseMapper -> BaseMapper里面維護了許多常用的方法, 例如根據主鍵查詢記錄, 根據根據主鍵修改記錄等等, 普通mapper只需要繼承這個BaseMapper即可獲得通用的方法
Wrapper -> 條件構造抽象類, 用來生成sql的條件
LogicSqlInjector -> 邏輯sql處理器, (邏輯刪除)
PaginationInterceptor -> 分頁插件, 底層是物理分頁
實體類命名
GlobalConfig 中默認表名、字段名、使用下划線命名
eg. TrainCourse 對應的表名為 train_course
成員 courseType 對應表字段名 course_type
這些可以通過注解覆蓋
類 : @TableName("對應的表名")
字段: @TableField("對應的字段名")
@Data
public class Course {
<span class="hljs-comment">/**
* 主鍵
*/</span>
<span class="hljs-meta">@TableId</span>(value = <span class="hljs-string">"course_id"</span>, type = IdType.AUTO)
<span class="hljs-keyword">private</span> Long courseId;
<span class="hljs-keyword">private</span> Integer courseType;
<span class="hljs-keyword">private</span> String title;
<span class="hljs-comment">/**
* 城市
*/</span>
<span class="hljs-keyword">private</span> String city;
<span class="hljs-comment">// 因為mp在使用的時候如果直接使用str, 等於就是使用了魔法值, 不便於維護, 所以統一在實體類中維護</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String COURSE_ID = <span class="hljs-string">"course_id"</span>;
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String COURSE_TYPE = <span class="hljs-string">"course_type"</span>;
}
- 如果是表中不存在的字段 , 務必打上@TableField(exist = false)
- 如果是表示是否刪除的字段, 如deleted, 則也要打上@TableLogic 名稱和全局一樣, 如果和數據庫字段名不符合全局的映射, 則需要手動指定
- 數據庫中只有0,1的值, 使用Boolean類型
XML和接口
xml和接口是完全兼容之前mybatis的寫法, 所以不會影響之前的邏輯。如果xml手動定義的名稱和mp的BaseMapper中定義的一樣, 則手動指定的會覆蓋BaseMapper中的。
即順序為 自己在xml中定義的方法 > BaseMapper中的方法
如果僅僅使用到了BaseMapper中的方法, 則可以不配置XML配置文件
使用示例
在構建好實體類后, 不要在條件查詢器中直接使用魔法值, 不容易維護
// 根據主鍵id查詢
Coursecourse = mCourseMapper.selectById(id);
// 使用查詢器
Wrapper<Course> query = new EntityWrapper<>();
if (courseType != null) {
// 這里的寫法其實有點像jooq, 生成的sql為: where course_type = courseType
query.eq(Course.COURSE_TYPE, courseType);
}
query.eq(Course.NAME, name);
// deleted 使用 true 或者 false , 盡量不要使用0,1 因為0,1是魔法值
query.eq(Course.DELETED, true);
// 查詢count的數量
int result = mCourseMapper.selectCount(query);
邏輯刪除插件
一旦使用了邏輯刪除插件, 以后默認的select都會帶上 deleted = 0, 即只會查出不標記為刪除的數據, 所以不需要手動在寫
Wrapper<Test> query = new EntityWrapper<>();
query.eq(Test.DELETED, false); //這句話是多余的, 因為配置了邏輯刪除插件, 默認在select的時候會加上 deleted = 0 這個條件
// 經過測試發現, 這會帶來一個問題, 比如就是要查詢刪除的記錄, 使用下面的這句話是無效的, 所以一旦使用了邏輯刪除插件, 如果要查詢刪除了的記錄, 請手寫xml
query.eq(Test.DELETED, true); // 效果就是 WHERE deleted=0 AND (deleted = 1) 這樣明顯是查不出任何一條記錄的
2.X版本
示例配置
mp只是用自己的 MybatisSqlSessionFactoryBean 替代了 mybatis-spring的 SqlSessionFactoryBean 其余的配置都不需要改動
再 globalConfig 中, 務必加入邏輯刪除插件, 因為我們的大多數操作都是邏輯刪除。物理刪除請手動寫xml。
3.X 版本
3.X版本和2.X版本的差別還是比較大的, 主要的差別如下
升級指南可以參考 https://my.oschina.net/u/241218/blog/1838534
其中有一點, 就是下面這個方法讓我有點困惑
UpdateWrapper
方法名 | 說明 |
---|---|
set | SQL SET 字段(一個字段使用一次) |
因為發現在使用中並沒有使用到這個方法, 連BaseMapper里面的update, 根據下面的解釋, 也用不到, 猜測是要自己實現一個注入方法了, 這里沒有深究
配置示例
這里采用java config的方式來配置(個人喜好, 加上java config可以做一些環境判斷, 上面那個打印sql每次都需要手動打開注釋, 麻煩)
Spring Boot的方式非常簡單, 參考官網即可, 所以這里給出的是Spring mvc的配置方式, 使用的DataSource是阿里的druid
這里就直接貼配置文件了, 下面的包含了多數據源的配置
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
- @author yanghuan
*/
@Configuration
public class MybatisPlusConfig {
// 環境標志, 區分dev or prod
@Autowired
private String projectStage;
<span class="hljs-comment">/**
* 數據源a 相關信息
*/</span>
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${a.url}"</span>)
<span class="hljs-keyword">private</span> String aUrl;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${a.username}"</span>)
<span class="hljs-keyword">private</span> String aUsername;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${a.password}"</span>)
<span class="hljs-keyword">private</span> String aPassword;
<span class="hljs-comment">/**
* 數據源b 相關信息
*/</span>
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${b.url}"</span>)
<span class="hljs-keyword">private</span> String bUrl;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${b.username}"</span>)
<span class="hljs-keyword">private</span> String bUsername;
<span class="hljs-meta">@Value</span>(<span class="hljs-string">"${b.password}"</span>)
<span class="hljs-keyword">private</span> String bPassword;
<span class="hljs-comment">// 創建數據源a</span>
<span class="hljs-meta">@Bean</span>(initMethod = <span class="hljs-string">"init"</span>, destroyMethod = <span class="hljs-string">"close"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> DruidDataSource <span class="hljs-title">aDataSource</span><span class="hljs-params">()</span></span>{
DruidDataSource d = <span class="hljs-keyword">new</span> DruidDataSource();
d.setUrl(aUrl);
d.setUsername(aUsername);
d.setPassword(aPassword);
<span class="hljs-keyword">return</span> d;
}
<span class="hljs-meta">@Bean</span>(initMethod = <span class="hljs-string">"init"</span>, destroyMethod = <span class="hljs-string">"close"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> DruidDataSource <span class="hljs-title">bDataSource</span><span class="hljs-params">()</span></span>{
DruidDataSource d= <span class="hljs-keyword">new</span> DruidDataSource();
d.setUrl(bUrl);
d.setUsername(bUsername);
d.setPassword(bPassword);
<span class="hljs-keyword">return</span> d;
}
<span class="hljs-comment">// 創建全局配置</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> GlobalConfig <span class="hljs-title">mpGlobalConfig</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 全局配置文件</span>
GlobalConfig globalConfig = <span class="hljs-keyword">new</span> GlobalConfig();
DbConfig dbConfig = <span class="hljs-keyword">new</span> DbConfig();
<span class="hljs-comment">// 默認為自增</span>
dbConfig.setIdType(IdType.AUTO);
<span class="hljs-comment">// 手動指定db 的類型, 這里是mysql</span>
dbConfig.setDbType(DbType.MYSQL);
globalConfig.setDbConfig(dbConfig);
<span class="hljs-keyword">if</span> (!ProjectStageUtil.isProd(projectStage)) {
<span class="hljs-comment">// 如果是dev環境,則使用 reload xml的功能,方便調試</span>
globalConfig.setRefresh(<span class="hljs-keyword">true</span>);
}
<span class="hljs-comment">// 邏輯刪除注入器</span>
LogicSqlInjector injector = <span class="hljs-keyword">new</span> LogicSqlInjector();
globalConfig.setSqlInjector(injector);
<span class="hljs-keyword">return</span> globalConfig;
}
<span class="hljs-meta">@Bean</span>(name = <span class="hljs-string">"aSqlSessionFactory"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> MybatisSqlSessionFactoryBean <span class="hljs-title">aSqlSessionFactory</span><span class="hljs-params">(
DruidDataSource aDataSource,
GlobalConfig globalConfig)</span> </span>{
<span class="hljs-keyword">return</span> getSessionFactoryBean(aDataSource, globalConfig);
}
<span class="hljs-comment">/**
* MapperScannerConfigurer 是 BeanFactoryPostProcessor 的一個實現,如果配置類中出現 BeanFactoryPostProcessor ,會破壞默認的
* post-processing, 如果不加static, 會導致整個都提前加載, 這時候, 取不到projectStage的值
*
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MapperScannerConfigurer <span class="hljs-title">aMapperScannerConfigurer</span><span class="hljs-params">()</span> </span>{
MapperScannerConfigurer configurer = <span class="hljs-keyword">new</span> MapperScannerConfigurer();
configurer.setBasePackage(<span class="hljs-string">"com.a"</span>);
<span class="hljs-comment">// 設置為上面的 factory name</span>
configurer.setSqlSessionFactoryBeanName(<span class="hljs-string">"bSqlSessionFactory"</span>);
<span class="hljs-keyword">return</span> configurer;
}
<span class="hljs-meta">@Bean</span>(name = <span class="hljs-string">"bSqlSessionFactory"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> MybatisSqlSessionFactoryBean <span class="hljs-title">bSqlSessionFactory</span><span class="hljs-params">(
DruidDataSource bDataSource,
GlobalConfig mpGlobalConfig)</span> </span>{
<span class="hljs-keyword">return</span> getSessionFactoryBean(bDataSource, mpGlobalConfig);
}
<span class="hljs-function"><span class="hljs-keyword">private</span> MybatisSqlSessionFactoryBean <span class="hljs-title">getSessionFactoryBean</span><span class="hljs-params">(
aDataSource aDataSource,
GlobalConfig globalConfig)</span> </span>{
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = <span class="hljs-keyword">new</span> MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(aDataSource);
sqlSessionFactoryBean.setGlobalConfig(globalConfig);
<span class="hljs-comment">// 源碼里面如果有configuration, 不會注入BaseMapper里面的方法, 所以這里要這樣寫</span>
MybatisConfiguration configuration = <span class="hljs-keyword">new</span> MybatisConfiguration().init(globalConfig);
configuration.setMapUnderscoreToCamelCase(<span class="hljs-keyword">true</span>);
sqlSessionFactoryBean.setConfiguration(configuration);
List<Interceptor> interceptors = <span class="hljs-keyword">new</span> ArrayList<>();
PaginationInterceptor paginationInterceptor = <span class="hljs-keyword">new</span> PaginationInterceptor();
<span class="hljs-comment">// 設置分頁插件</span>
interceptors.add(paginationInterceptor);
<span class="hljs-keyword">if</span> (!ProjectStageUtil.isProd(projectStage)) {
<span class="hljs-comment">// 如果是dev環境,打印出sql, 設置sql攔截插件, prod環境不要使用, 會影響性能</span>
PerformanceInterceptor performanceInterceptor = <span class="hljs-keyword">new</span> PerformanceInterceptor();
interceptors.add(performanceInterceptor);
}
sqlSessionFactoryBean.setPlugins(interceptors.toArray(<span class="hljs-keyword">new</span> Interceptor[<span class="hljs-number">0</span>]));
<span class="hljs-keyword">return</span> sqlSessionFactoryBean;
}
<span class="hljs-comment">/**
* b 的mapperscan
* <span class="hljs-doctag">@return</span>
*/</span>
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> MapperScannerConfigurer <span class="hljs-title">bMapperScannerConfigurer</span><span class="hljs-params">()</span> </span>{
MapperScannerConfigurer configurer = <span class="hljs-keyword">new</span> MapperScannerConfigurer();
configurer.setBasePackage(<span class="hljs-string">"com.b"</span>);
<span class="hljs-comment">// 設置為上面的 factory name</span>
configurer.setSqlSessionFactoryBeanName(<span class="hljs-string">"bSqlSessionFactory"</span>);
<span class="hljs-keyword">return</span> configurer;
}
}
使用方式和之前的類似, 只不過 EntityWrapper 改為了 QueryWrapper
以及有一些新的lambda方法
- 不需要定義一個靜態變量了, 如果在成員變量上打了@TableField注解, 這里會取注解中的值, 否則就是globalConfig里面的
@Test
public void testPluralLambda() {
TableInfoHelper.initTableInfo(null, BdSystemUser.class);
QueryWrapper<BdSystemUser> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(BdSystemUser::getName,"sss");
queryWrapper.lambda().eq(BdSystemUser::getUserId,1);
BdSystemUser user = mBdSystemUserMapper.selectOne(queryWrapper);
}
- 之前2.X版本對復雜的or查詢不能兼容, 即使用 Wrapper 的or方法, 默認是不加括號的
不過對於負責的查詢, 還是推薦手寫sql, 這里展示一下如何使用mp來構建
@Test
public void test() {
Wrapper<BdSystemUser> wrapper = new QueryWrapper<BdSystemUser>().lambda().eq(BdSystemUser::getName, 123)
.or(c -> c.eq(BdSystemUser::getExternalStaff, 1).eq(BdSystemUser::getUserId, 2))
.eq(BdSystemUser::getUserId, 1);
BdSystemUser user = mBdSystemUserMapper.selectOne(wrapper);
// 對應的sql: SELECT * FROM bdsystem_user WHERE deleted = 0 AND NAME = ? OR ( externalstaff = ? AND userid = ? ) AND userid = ?
// 可以看到, or中的語句是在括號里的
}
3.可以把where 后的條件防止在一個map中
@Test
public void testCompare() {
Map<String, Object> map = new HashMap<>();
map.put("userid", 1);
map.put("ldap", "luoshu");
QueryWrapper<BdSystemUser> queryWrapper = new QueryWrapper<BdSystemUser>()
.allEq(true, map, false);
BdSystemUser user = mBdSystemUserMapper.selectOne(queryWrapper);
// SELECT userid,name,ldap,stafftype,leader,departmenttype,groupid,externalstaff,mobile,created,lastmodified,deleted FROM bdsystem_user WHERE deleted=0 AND ldap = ? AND userid = ?
// 可以看到, map中對應的值, 是where后的條件
}
更詳細的demo可以參照 demo
3.X 注意事項
- BaseMapper的selectOne() 現在在返回多條語句的時候,會拋異常, 需要手動在條件中處理, 使用wrapper.last()方法.
- 如果mp和tddl這樣的中間件進行集成, 在分頁插件的創建時候, 需要指定數據庫方言, 之前可以不指定的原因是mp通過解析jdbc url的時候可以獲取到對應的方言
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 這邊因為接了tddl, 必須手動設置方言, 否則分頁會拋異常
paginationInterceptor.setDialectType(DbType.MYSQL.getDb());
原文地址:https://www.cnblogs.com/hinsy/p/9668684.html