mybatis-plus
也是我在項目中發現的一個好用的架構,可能知道的人不多,是以很少看到關于它的介紹。正如官方介紹的:“我們的願景是成為
MyBatis
最好的搭檔,就像魂鬥羅中的
1P
、
2P
,基友搭配,效率翻倍”。目的是增強
MyBatis
,簡化開發、提高效率。
使用
mybatis-plus
可以少寫很多常用的
SQL
,通過繼承
BaseMapper
使用,還可以動态拼接
SQL
。第一眼看到我還以為是
JPA
。對分頁查詢也做了很好的支援,實際上分頁的實作還是基于
mybatis
提供的插件(攔截器)特性去實作的。本篇将介紹如何快速上手
mybatis-plus
,并重點從源碼分析分頁
PaginationInterceptor
的性能。
在項目中添加依賴
// mybatis and mybatis-plus
compile group: 'com.baomidou', name: 'mybatisplus-spring-boot-starter', version: '1.0.5'
compile group: 'com.baomidou', name: 'mybatis-plus', version: '2.2.0'
複制代碼
編寫對應資料庫表的實體類。使用
@TableName
注解聲明對應資料庫中的哪個表,使用
@TableId
注解注釋的字段就是對應資料庫表的主鍵列。如果不開啟駝峰映射,還可以使用
@TableField
注解将字段與表的字段名做映射。
@TableName("company")
public class Company {
@TableId
private Long id;
private String company;
private String email;
private String details;
// @TableField("create_tm")
private Date createTm;
// @TableField("modified_tm")
private Date modifiedTm;
}
複制代碼
如需開啟駝峰映射,隻需在
yml
配置檔案中将
db-column-underline
改為
true
。
mybatis-plus:
global-config:
# 配置開啟駝峰映射
db-column-underline: true
複制代碼
編寫
Mapper
接口,需要繼承
BaseMapper
,泛型指定為對應表的實體類。無需編寫
mapper.xml
檔案,就能使用一些常用的
CRUD
功能,當然,也包括了分頁查詢。
/**
* 需要繼承BaseMapper接口
*/
@Mapper
public interface CompanyMapper extends BaseMapper<Company> {
}
複制代碼
現在就可以使用
CompanyMapper
了。
CompanyMapper companyMapper = applicationContext.getBean(CompanyMapper.class);
companyMapper.selectById(3);
companyMapper.selectBatchIds(Arrays.asList(3,4,5));
複制代碼
複雜一點的查詢需要通過
Wrapper
去構造查詢條件,以分頁查詢為列。
Page<Company> pageResult = this.selectPage(
// 擷取第一頁,頁大小為2
new Page(1,2),
// 查詢id>=1 且 小于等于100的記錄
new EntityWrapper<Company>().between("id", "1", "100"));
// 分頁資訊
System.out.println("current page:"+pageResult.getCurrent());
System.out.println("page size:"+pageResult.getSize());
System.out.println("total records:"+pageResult.getTotal());
System.out.println("page count:"+pageResult.getPages());
// 查詢結果
pageResult.getRecords().stream().forEach(System.out::println);
複制代碼
EntityWrapper
是
Wrapper
的一個子類。
Wrapper
封裝了
where
group by
order by
having
部分
sql
的生成邏輯。提供
between
notIn
in
eq
or
and
le
lt
gt
notExists
exists
isNull
isNotNull
link
等方法指定查詢條件,并支援鍊式調用。
Wrapper
非常适用于動态編寫一些簡單的
sql
,省去在
mapper
接口添加方法、再去
xml
檔案編寫
sql
的工作量,而且使用這種方式的好處還在于,簡化
mapper
接口,但是有利也有弊吧。複雜的
sql
如果這麼寫,開發效率反而會下降,也不易于看懂,是以複雜的
sql
并不推薦這麼寫。
使用分頁查詢要注意,需要配置分頁攔截器
PaginationInterceptor
,用于擷取記錄總數。
@Configuration
public class MybatisplusConfig {
/**
* 要使用分頁查詢功能,就需要配置分頁攔截器
*
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
複制代碼
除此之外,我們還可以在
Service
層通過繼承
ServiceImpl
Mapper
。如果
Service
類有接口,則需要接口也繼承
IService
接口。
// 繼承IService接口,以使用CompanyService時能夠調用方法
public interface CompanyService extends IService<Company> {
}
@Service
public class CompanyServiceImpl extends ServiceImpl<CompanyMapper, Company>
implements CompanyService {
}
// 使用
public static void main(String[] args) {
try {
ApplicationContext applicationContext = SpringApplication.run(MybatisplusStuApplication.class);
CompanyService companyService = applicationContext.getBean(CompanyService.class);
Company company = companyService.selectById(4);
} catch (Exception e) {
e.printStackTrace();
}
}
複制代碼
如果
Service
類沒有接口,直接繼承
ServiceImpl
就可以了。
// 非接口方式,直接繼承ServiceImpl類即可。
@Service
public class CompanyServiceNotInterface extends ServiceImpl<CompanyMapper, Company> {
}
// 使用
public static void main(String[] args) {
try {
ApplicationContext applicationContext = SpringApplication.run(MybatisplusStuApplication.class);
CompanyServiceNotInterface companyServiceNotInterface =
applicationContext.getBean(CompanyServiceNotInterface.class);
Company company1 = companyServiceNotInterface.selectById(3);
} catch (Exception e) {
e.printStackTrace();
}
}
複制代碼
現在來看下,為什麼自己寫的
Service
沒有注入
Mapper
也能使用
Mapper
接口的方法。其實很簡單,就是
ServiceImpl
中巧用泛型聲明了
Mapper
字段,并添加了
@Autowired
注解,由
Spring
自動注入了。
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
@Autowired
protected M baseMapper;
}
複制代碼
直接調用
Mapper
接口的方法與調用
ServiceImpl
的方法最大的差別在于
ServiceImpl
在插入和更新的方法上添加了
@Transactional
注解,支援事務。
最後說下關于分頁的支援。通過分析
PaginationInterceptor
攔截器的源碼,看看它是怎麼去擷取總數的。下面是摘抄
intercept
方法的部分源碼。
// 生成擷取總數的sql
SqlInfo sqlInfo = SqlUtils.getOptimizeCountSql(page.isOptimizeCountSql(), sqlParser, originalSql);
orderBy = sqlInfo.isOrderBy();
// 執行sql,将擷取的總數寫回page
this.queryTotal(overflowCurrent, sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
複制代碼
SqlUtils.getOptimizeCountSql
這句是生成“擷取總數”的
sql
,是以重點也是在這裡。因為生成的擷取總數的
sql
決定了這個分頁插件是否可用,是否隻是簡單的在原來的查詢語句外面去掉
limt
之後包裝一層
select
public static SqlInfo getOptimizeCountSql(boolean optimizeCountSql, ISqlParser sqlParser, String originalSql) {
if (!optimizeCountSql) {
return SqlInfo.newInstance().setSql(getOriginalCountSql(originalSql));
}
// COUNT SQL 解析器
if (null == COUNT_SQL_PARSER) {
if (null != sqlParser) {
// 使用者自定義 COUNT SQL 解析
COUNT_SQL_PARSER = sqlParser;
} else {
// 預設 JsqlParser 優化 COUNT
try {
COUNT_SQL_PARSER = DEFAULT_CLASS.newInstance();
} catch (Exception e) {
throw new MybatisPlusException(e);
}
}
}
// 傳回COUNT SQL 解析器生成的查詢總數的sql
return COUNT_SQL_PARSER.optimizeSql(null, originalSql);
}
複制代碼
optimizeCountSql
為
false
,則隻是簡單的在原來的
sql
外包裝一層
select
擷取總數。該字段在
Page
類中,預設為
true
。不建議設定為
false
/**
* 優化 Count Sql 設定 false 執行 select count(1) from (listSql)
*/
private boolean optimizeCountSql = true;
複制代碼
optimizeCountSql
true
,則會使用
COUNT SQL 解析器
生成優化後的查詢總數的
sql
。如果指定了
COUNT SQL 解析器
則使用指定的解析器,如果沒有則使用預設的解析器。這個就是在建立分頁攔截器時配置的。
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor interceptor = new PaginationInterceptor();
// 自定義count sql解析器
interceptor.setSqlParser(new ISqlParser() {
@Override
public SqlInfo optimizeSql(MetaObject metaObject, String sql) {
return null;
}
});
return interceptor;
}
複制代碼
public static ISqlParser COUNT_SQL_PARSER = null;
private static Class<ISqlParser> DEFAULT_CLASS = null;
static {
try {
DEFAULT_CLASS = (Class<ISqlParser>) Class.forName("com.baomidou.mybatisplus.plugins.pagination.optimize.JsqlParserCountOptimize");
} catch (ClassNotFoundException e) {
}
}
複制代碼