在MyBatis的世界里,原生的分页基于RowBounds
,虽然能达到分页的目的,但是使用起来还是不那么方便.后面开发者又添加了流式查询方式,虽然可以解决部分问题,但感觉还是不是想要的分页方式.另一方面,Java界程序员大佬给出了很多优秀的分页方案,比如Mybatis-PageHelper. 世界因不一样而精彩,今天带来一种新的分页方式.希望带给使用者一种新的api使用方式.
mybatis-nosugar
得益于JDK1.8的函数式接口,新的分页方式才得以使用.
分页Api
- Api定义最多支持五个参数,多于5个的可以使用类型强制转换.或者自己扩展.
- 支持原Mapper已定义的查询方法.
Page<T> page = mapper.selectPagePX(Page<T>, mapper::原查询方法的lambda引用, 原参数列表...);
简单示例
Page<Movie> page = mapper.selectPageP2(new PageImpl<>(2, 5), mapper::findByLocationAndScoreGreaterThan, "US", 9.0);
使用方式
- 引入 Maven依赖
<dependency>
<groupId>com.nosugarice</groupId>
<artifactId>mybatis-nosugar-spring</artifactId>
<version>${nosugar.version}</version>
</dependency>
- 在原来
Mybatis-Spring
配置的基础上把factoryBean
替换成NoSugar中的MybatisMapperFactoryBean
@MapperScan(basePackages = {"com.xxx"}, factoryBean = MybatisMapperFactoryBean.class)
public class MyBatisConfiguration {
}
- 继承接口
BaseMapper<T, ID>
public interface MovieMapper extends BaseMapper<Movie, Integer> {
}
示例数据
实体类
@Table(name = "movie")
public class Movie implements Serializable {
private static final long serialVersionUID = -8793166370987026047L;
/** 主键 */
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/** 名称 */
@Column(name = "name", nullable = false)
private String name;
/** 地区 */
@Column(name = "location", nullable = false)
private String location;
/** 时长 */
@Column(name = "length", nullable = false)
private Integer length;
/** 发行日期 */
@Column(name = "release_date", nullable = false)
private LocalDate releaseDate;
/** 名次 */
@Column(name = "ordinal", nullable = false)
private Integer ordinal;
/** 评分 */
@Column(name = "score", nullable = false)
private Double score;
/** 类别 */
@Column(name = "category", nullable = false)
private String category;
...
}
Mapper接口
public interface MovieMapper extends BaseMapper<Movie, Integer> {
List<Movie> findByCategoryIsNotNull();
List<Movie> findByLocation(String location);
List<Movie> findByLocationAndScoreGreaterThan(String location, Double score);
List<Movie> findByLocationAndScoreGreaterThanAndCategoryContains(String location, Double score, String category);
}
数据库初始化SQL
DROP TABLE movie IF EXISTS;
CREATE TABLE movie
(
id INTEGER GENERATED BY DEFAULT AS IDENTITY,
name VARCHAR(100) NOT NULL,
location VARCHAR(100) NOT NULL,
length INTEGER NOT NULL,
release_date DATE NOT NULL,
ordinal INTEGER NOT NULL,
score DOUBLE NOT NULL,
category VARCHAR(20) NULL,
PRIMARY KEY (id)
);
INSERT INTO movie VALUES (0, '肖申克的救赎', 'US', 142, '1994-09-23', 1, 9.7, '剧情');
INSERT INTO movie VALUES (1, '霸王别姬', 'CHN', 171, '1993-07-26', 2, 9.6, '剧情');
INSERT INTO movie VALUES (2, '阿甘正传 ', 'US', 142, '1994-06-23', 3, 9.5, '剧情');
INSERT INTO movie VALUES (3, '美丽人生 ', 'IT', 116, '1997-12-20', 4, 9.6, '');
INSERT INTO movie VALUES (4, '千与千寻 ', 'JP', 125, '2001-07-20', 5, 9.4, '奇幻');
INSERT INTO movie VALUES (5, '泰坦尼克号 ', 'US', 194, '1997-12-19', 6, 9.4, '爱情');
INSERT INTO movie VALUES (6, '这个杀手不太冷', 'FR', 133, '1994-09-14', 7, 9.4, '犯罪');
INSERT INTO movie VALUES (7, '星际穿越', 'US', 169, '2014-11-12', 8, 9.4, '科幻');
INSERT INTO movie VALUES (8, '无间道', 'CHN', 101, '2003-09-05', 9, 9.3, '犯罪');
INSERT INTO movie VALUES (9, '疯狂动物城', 'US', 109, '2016-03-04', 10, 9.2, '喜剧');
INSERT INTO movie VALUES (10, '机器人总动员', 'US', 98, '2008-06-27', 11, 9.3, '动画');
INSERT INTO movie VALUES (11, '罗马假日', 'US', 118, '1953-08-20', 12, 9.1, '爱情');
INSERT INTO movie VALUES (12, '大话西游之大圣娶亲', 'CHN', 95, '1995-02-04', 13, 9.2, '剧情');
INSERT INTO movie VALUES (13, '大话西游之月光宝盒', 'CHN', 87, '1995-01-21', 14, 9.0, '喜剧');
INSERT INTO movie VALUES (14, '指环王3', 'US', 201, '2003-12-17', 15, 9.3, null);
基于0个参数的分页示例
Page<Movie> page = mapper.selectPageP0(new PageImpl<>(2, 5), mapper::findByCategoryIsNotNull);
MovieMapper.adapterCount : ==> Preparing: SELECT COUNT(*) FROM movie WHERE category IS NOT NULL
MovieMapper.adapterCount : ==> Parameters:
MovieMapper.adapterCount : <== Total: 1
MovieMapper.adapterPage : ==> Preparing: SELECT id AS "id", name AS "name", location AS "location", length AS "length", release_date AS "releaseDate", ordinal AS "ordinal", score AS "score", category AS "category" FROM movie WHERE category IS NOT NULL OFFSET 5 LIMIT 5
MovieMapper.adapterPage : ==> Parameters:
MovieMapper.adapterPage : <== Total: 5
基于三个参数的分页示例
Page<Movie> page = mapper.selectPageP3(new PageImpl<>(2, 2), mapper::findByLocationAndScoreGreaterThanAndCategoryContains, "US", 9.0, "情");
MovieMapper.adapterCount : ==> Preparing: SELECT COUNT(*) FROM movie WHERE location = ? AND score > ? AND category LIKE ?
MovieMapper.adapterCount : ==> Parameters: US(String), 9.0(Double), %情%(String)
MovieMapper.adapterCount : <== Total: 1
MovieMapper.adapterPage : ==> Preparing: SELECT id AS "id", name AS "name", location AS "location", length AS "length", release_date AS "releaseDate", ordinal AS "ordinal", score AS "score", category AS "category" FROM movie WHERE location = ? AND score > ? AND category LIKE ? OFFSET 2 LIMIT 2
MovieMapper.adapterPage : ==> Parameters: US(String), 9.0(Double), %情%(String)
MovieMapper.adapterPage : <== Total: 2
全新Count查询
基于SelectMapper#adapterCount
在原查询方法上开启Count查询.
示例
long count=mapper.countP3(mapper::findByLocationAndScoreGreaterThanAndCategoryContains, "US", 9.0, "情");
注意
调用的方法除第一个方法引用参数外其他的参数类型要和原方法对应.
全新exists查询
基于SelectMapper#adapterExists
在原查询方法上开启Exists查询.
示例
Optional<Integer> optional = mapper.existsP3(mapper::findByLocationAndScoreGreaterThanAndCategoryContains, "US", 9.0, "情");
Assertions.assertTrue(optional.isPresent());
不足
分页语句的SQl优化只是简单处理,没有做过多的优化,使用者可以引入其他库解析SQL分析语义优化Count
语句.实现Dialect#optimizationCountSql
即可优化.这块因为交给使用者.
NoSugar不仅于此
- 无糖配方
- 开放大量接口给与开发者很大的自由度,让开发者根据自己的程序适配.不必千篇一律
- 性能非常丝滑,大部分功能超越动态标签,参数越多性能提升越明显
- 使用简单,不影响原有项目,无需修改原Mybatis类声明,没有重构任何Mybatis基础配置类,只需增加一个属性配置即可开启
- 无缝增强现有Mybatis项目(+功能),即使现在的项目在使用其他Mybatis框架依旧可增强
- 部分功能如分页,Count查询,JPA方式的根据方法名查询,可以单独选用
- 基础的增删改查
- 条件构造
- 插入时主键策略
- 批处理增强模式
- 全新的通用分页方式,无需插件
- 全新的通用Count查询方法
- 软删除
- 乐观锁
- 动态表名
- 更易用的值处理器
- Jpa式根据方法名查询,删除