基于EntityManager的分页查询解决方案

需求:分页查询学生信息

项目环境:Spring Boot 2.0.6.RELEASE

1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

Maven依赖:

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>

分页查询返回体类:

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
public class IPagination<T> {
/** 当前页数 **/
private int pager;
/** 总页数 **/
private int pages;
/** 每页条数 **/
private int size;
/** 总条数 **/
private long total;
/** 忽略数据条数 **/
private int offset;
/** 列表数据 **/
private List<T> list = new ArrayList<>();

public IPagination() {
}

public IPagination(int pager, int size) {
if (pager >= 1 && size >= 1) {
this.pager = pager;
this.size = size;
} else {
throw new RuntimeException("invalid pager: " + pager + " or size: " + size);
}
}

public static IPagination create(int pager, int size) {
return new IPagination(pager, size);
}

public void setTotal(long total) {
this.total = total;
}

public void setList(List<T> list) {
this.list = list;
}

public int getSize() {
return size;
}

public int getPager() {
return pager == 0 ? 1 : pager;
}

public int getOffset() {
return size * (getPager() - 1);
}

public int getPages() {
return Double.valueOf(Math.ceil((double) total / (double) size)).intValue();
}

public long getTotal() {
return total;
}

public List<T> getList() {
return list;
}
}

Controller层:

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/api/student")
public class StudentApiController {

@Autowired
private StudentService studentService;

@PostMapping("/search")
public IPagination<StudentResponse> search(@RequestBody StudentSearchRequest request) {
return studentService.search(request);
}
}

就一个简单的POST请求,请求体有页数、每页条数、查询参数等属性。

Service层:

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
@Service
public class StudentService {

@Autowired
private PaginationMapper paginationMapper;

/**
* 分页查询学生信息
* @param request
* @return
*/
public IPagination<StudentResponse> search(StudentSearchRequest request) {
// 拼接SQL语句
StringBuilder sql = new StringBuilder("SELECT id, name FROM t_galidun_student ");
// 查询需要的参数,先存进Map
Map<String, Object> maps = new HashMap<>();
if (request.name != null) {
sql.append("WHERE name LIKE :name");
maps.put("name", "%" + request.name + "%");
}
// 调用通用方法返回查询结果
return paginationMapper.nativeSearch(request.nowPage, request.pageSize, sql.toString(), maps, StudentResponse.class);
}

}

在这一层主要是拼接sql,提供查询需要的参数,最后调用通用方法返回结果。

Mapper层:

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
@Component
public class PaginationMapper {

@PersistenceContext
private EntityManager entityManager;

/**
* 分页查询通用方法
*
* @param nowPage 当前页
* @param pageSize 每页条数
* @param sql sql语句
* @param maps sql查询参数
* @param clazz 返回类型
* @param <T>
* @return
*/
public <T> IPagination<T> search(Integer nowPage, Integer pageSize, String sql, Map<String, Object> maps, Class<T> clazz) {
// 初始化分页返回体
IPagination pagination = IPagination.create(nowPage, pageSize);
// 查询结果总条数
int total = getQueryWithParameters(entityManager.createNativeQuery(sql), maps).getResultList().size();
pagination.setTotal(total);
if (total == 0) return pagination;
Query query = getQueryWithParameters(entityManager.createNativeQuery(sql), maps);
// 忽略指定条数据,返回一页数据
query.setFirstResult(pagination.getOffset()).setMaxResults(pagination.getSize());
// 指定返回对象类型
query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(clazz));
// 列表数据
pagination.setList(query.getResultList());
return pagination;
}

/**
* 设置查询所需的参数
*
* @param query
* @param maps
* @return
*/
private Query getQueryWithParameters(Query query, Map<String, Object> maps) {
if (maps.size() > 0) {
for (String key : maps.keySet()) {
query.setParameter(key, maps.get(key));
}
}
return query;
}
}

这是个通用的方法,只需要传入查询的页数,每页数据条数,sql语句,查询参数,返回体类型即可。

每个Query只能调用一次getResultList方法,调用之后再次调用就会抛异常,所以方法中有两处entityManager.createNaticeQuery(sql),一次是为了查询总条数,另一次是查询当前页的数据。

查询总条数的时候可以改为使用COUNT(主键或者非NULL索引);读者有其他能提高查询性能的方法,方便的话,分享一下吧。

项目地址:https://github.com/Nguyen-Vm/entity-manager