SpringBoot Jpa 我已知的两种分页多条件的查询方式

前言

对于 SpringBoot 自带的 Spring JPA 方式的分页多条件查询, 目前我只掌握了两种,一种貌似还不支持条件的嵌套,下面就开始说明。

表结构得表现一下吧

SQL太长了,放到最后。

JPA 的 repo 操作类

1
2
3
4
5
6
7
8
9
/**
* JpaSpecificationExecutor 这是为了实现第二种查询方式
* @author maxzhao
* @date 2019-05-06
*/
@Repository(value = "appTableRepository")
public interface AppTableRepository extends JpaRepository<AppTable, String>, JpaSpecificationExecutor<AppTable> {

}

测试代码

备注:把 application.*的文件拷贝到 test目录的 resources下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* 测试
* @author maxzhao
* @date 2019-06-06
*/
@RunWith(SpringRunner.class)
// BasicApplication 是启动类
@SpringBootTest(classes = BasicApplication.class)
public class AppColumnServiceTest {
// 如果没有日志可以删掉
private final static Logger logger = LoggerFactory.getLogger(AppTablesRepositoryTest.class);
@Resource(name = "appTableRepository")
private AppTableRepository appTableRepository;

@Before
public void startInit() {
logger.info("---------------- {} -------------------", "AppColumnServiceTest");
}

@After
public void endDestory() { }

@Test
public void listTableTest() {
//Sort.Direction是个枚举有ASC(升序)和DESC(降序)
Sort.Direction sort = Sort.Direction.ASC;
//PageRequest继承于AbstractPageRequest并且实现了Pageable
//获取PageRequest对象 index:页码 从0开始 size每页容量 sort排序方式 "tab_name"->properties 以谁为准排序
// 这里的tabName是实体的属性,不是数据库字段的名称
Pageable pageable = PageRequest.of(0, 5, sort, "tabName");

//region 查询条件设置方式一:
//要匹配的实例对象
AppTable appTable = new AppTable();
appTable.setTabName("app_");
//定义匹配规则 匹配"vendorId"这个属性 exact 精准匹配
// 这里的tabName是实体的属性,不是数据库字段的名称
ExampleMatcher exampleMatcher = ExampleMatcher
.matching()
.withMatcher("tabName", ExampleMatcher.GenericPropertyMatchers.contains());
Example<AppTable> example = Example.of(appTable, exampleMatcher);
//下面加断点,查询结果集
Page<AppTable> page = appTableRepository.findAll(example, pageable);
//获取总页数
page.getTotalPages();
//获取总元素个数
page.getTotalElements();
//获取该分页的列表
page.getContent();
page.getSize();
//endregion

//查询条件设置方式二:
Specification<AppTable> specification = (Specification<AppTable>) (root, query, criteriaBuilder) -> {
// 这里的tabName是实体的属性,不是数据库字段的名称
Path<String> name = root.get("tabName");
//Path<String> alias = root.get("alias");
//查询条件1
Predicate p1 = criteriaBuilder.like(name, "%" + "app_" + "%");
//查询条件2
//Predicate p2 = criteriaBuilder.lt(alias param.getAge());
//查询条件1和2的关系
//Predicate p = criteriaBuilder.and(p1, p2);
return p1;
};
//下面加断点,查询结果集
Page<AppTable> page2 = appTableRepository.findAll(specification,pageable);
//endregion
}
}

代码中需要加断点的地方数据是一样的,如果不一样,设置了排序规则就没有如果。

上面注释也写的很清楚了,这两种查询方式第一种应该是没有嵌套查询的(我没有翻到源码,网上也搜不到案例),第二种也不麻烦,就多继承一个接口,可以实现嵌套查询。

查询方式二的第二种查询方式(单表多条件)

(网上找的案例,不记得地址了,我也只是 cp了这部分代码,上面的测试一下,这个就懂了 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb
List<Predicate> predicates = new ArrayList<>();
if (user.getUserId() != null && !user.getUserId().equals("")) {
predicates.add(cb.like(root.get("userId").as(String.class), "%" + user.getUserId() + "%"));
}
if (user.getUserName() != null && !user.getUserName().equals("")) {
predicates.add(cb.like(root.get("userName").as(String.class), "%" + user.getUserName() + "%"));
}
if (user.getGender() != null && !user.getGender().equals("")) {
predicates.add(cb.like(root.get("gender").as(String.class), "%" + user.getGender() + "%"));
}
if (user.getAge() != null && !user.getAge().equals("")) {
predicates.add(cb.like(root.get("age").as(String.class), "%" + user.getAge() + "%"));
}
Predicate[] pre = new Predicate[predicates.size()];
criteriaQuery.where(predicates.toArray(pre));
return cb.and(predicates.toArray(pre));

查询方式二的第二种查询方式(多表、多条件)

备注:记得设置 @ManyToOne或者 @OneToMany之类的外键关联哦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// (Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb)
List<Predicate> list = new ArrayList<>();
//根据userId 查询user
if (StringUtils.isNotBlank(params.getUserId())) {
list.add(cb.equal(root.get("userId").as(String.class), params.getUserId()));
}
//根据userName 模糊查询user
if (StringUtils.isNotBlank(params.getUserName())) {
list.add(cb.like(root.get("userName").as(String.class), "%" + params.getUserName() + "%"));
}
//根据gender 查询user
if (StringUtils.isNotBlank(params.getGender())) {
list.add(cb.equal(root.get("gender").as(String.class), params.getGender()));
}
//根据age>? 查询user
if (StringUtils.isNotBlank(params.getAge())) {
list.add(cb.gt(root.get("age").as(Integer.class), Integer.valueOf(params.getAge())));
}
//根据gradeName 查询user
if (StringUtils.isNotBlank(params.getGradeName())) {
Join<Grade, User> join = root.join("grade", JoinType.LEFT);
list.add(cb.equal(join.get("gradeName"), params.getGradeName()));
}
//根据schoolName 查询user
if (StringUtils.isNotBlank(params.getSchoolName())) {
Join<School, User> join = root.join("grade", JoinType.LEFT);
list.add(cb.equal(join.get("school").get("schoolName"), params.getSchoolName()));
}
Predicate[] pre = new Predicate[list.size()];
criteriaQuery.where(list.toArray(pre));
return cb.and(list.toArray(pre));

不推荐上面这种用法,这么用让微服务颜面何存?

附录:

数据库SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-- MySql
create table app_table
(
tab_name varchar(255) not null comment '表名主键'
primary key,
alias varchar(255) null comment '别名',
can_del_data int null comment '是否可以清除数据0否1是',
id_cols varchar(255) null comment '主键列名',
remark varchar(255) null comment '备注',
tab_type int null comment '物理表类型(app 1应用级;cf_ 2配置级;bn 3 业务级;tmp 4临时;5其它数据表)'
)comment '应用表信息' charset = utf8;
-- 数据
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_area', '地区编码表', 0, 'id', '地区编码表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_column', '应用表字段信息', 0, 'id', '应用表字段信息', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_dept', '部门管理', 0, 'id', '部门管理', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_dict', '应用字典表', 0, 'id', '应用字典表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_dict_value', '应用字典属性表', 0, 'id', '应用字典属性表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_log', '系统日志', 0, 'id', '系统日志', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_menu', '菜单表', 0, 'id', '菜单表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role', '角色表', 0, 'id', '角色表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role_group', '角色组表', 0, 'id', '角色组表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role_group_link', '角色与角色组表', 0, 'id', '角色与角色组表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_role_menu', '角色菜单表', 0, 'id', '角色菜单表', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_table', '应用表信息', 0, 'tab_name', '应用表信息', 1);
INSERT INTO app_table (tab_name, alias, can_del_data, id_cols, remark, tab_type) VALUES ('app_user', '用户表', 0, 'id', '用户表', 1);

create table app_column
(
id varchar(255) not null comment '主键'
primary key,
alias varchar(255) null comment '别名',
app_dict_id bigint null comment '绑定字典主键',
char_length int null comment '内容长度',
col_default varchar(255) null comment '默认值',
col_name varchar(255) null comment '字段名',
col_type varchar(255) null comment '字段类型(用于与使用控件相关)',
col_type_alias varchar(255) null comment '字段类型描述',
is_null int default 1 null comment '是否允许空(1 是 0 否)',
remark varchar(255) null comment '备注',
scale int null comment '精度',
tab_name varchar(255) null comment '表名',
time_format varchar(255) null comment '时间格式'
)comment '应用表字段信息' charset = utf8;


specificationin语句

1
2
3
4
5
6
Path<String> path = root.get("tabName");
CriteriaBuilder.In<String> in = criteriaBuilder.in(path);
in.value("app_table");
in.value("app_column");
criteriaBuilder.and(in);
return criteriaBuilder.and(p1, in);

其它方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   @PersistenceContext
private EntityManager entityManager;
// 查询
entityManager.find(AppTable.class, id);
// 查询列表
entityManager.createQuery(hql).getResultList();
// 查询列表数量,有参数
entityManager.createQuery(hql)
.setParameter(0, tabName)
.setParameter(1, alias).getResultList().size();
// 更新
AppTable appTable = entityManager.find(AppTable.class, id);
appTable.setTabName("X");
entityManager.flush();
// 删除
entityManager.remove(entityManager.find(AppTable.class, id));

本文地址:Spring Boot Jpa 我已知的两种分页多查询条件的查询方式

本文地址: https://github.com/maxzhao-it/blog/post/17224/