• spring data jpa在mysql分页中的实例(一次访问同时获取数据和总数)


    ~ 前言

    起因:

    这几天被spring jpa的分页折磨,鄙人就爱用sql,以前都是@Query("原生sql语句", native=true)解决问题。但是涉及到大数据量的多条件查询拼接sql就GG了,最开始使用...where 1=1 and IF(:snYear='', 1=1, sn_year=:snYear)...这种方式拼接,但是数据量一大上来(700多万条),发现居然不走sn_year索引(/心碎)。上网看了看索引失效情况,大概原因是参与函数计算的索引字段会导致该字段的索引失效。

    另寻它法:

    JPA自带的各种类(JPA有好几种方式来拼接sql)的拼接sql真的是恶心至极!代码量不仅远远超过sql原代码,而且很不清晰。
    像我这种sql原驻民(偏爱手写sql,一目了然,好控制),还得学习他的语法,真是看了就烦!!

    经不断的研究发现,JPA其中的EntityManger类的方式还是很清晰的!完美满足我的想法。

    一、原生sql语句

    mysql中语句如下

    select SQL_CALC_FOUND_ROWS 
     sn,max(count) as active_count
    from sn_state_changed
    where sn_year = 'zz' 
    group by sn 
    limit 0,10;
        
    select FOUND_ROWS() as total;

    解释:

    SQL_CALC_FOUND_ROWS

    供后面的查询总数sql语句使用(select found_rows()

    1. 如果不加这个,后面的select found_rows()返回的数量为limit后的数量,而不是总数。
    2. 注意使用空格隔开,没有逗号

    FOUND_ROWS()

    查询总数sql语句

    二、正式查询

    这两个语句必须同一个查询的session,否则查询的出来的数量是错误的。

    JPA中使用@PersistenceContext EntityManage em来处理。
     

    解释:
    @PersistenceContext就是用来为每个线程创建一个EntityManager的,每个线程会生成新的session,所以保证了一个方法中多次调用执行em也是在同一个session中

    所以Navicat查询(一个session中可以执行多句sql,返回多个结果N):

     查询结果(注意结果有两个): 

    三、Spring Data JPA中的实例应用

    1. ...
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.apache.commons.lang3.StringUtils;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.stereotype.Service;
    6. import javax.persistence.EntityManager;
    7. import javax.persistence.PersistenceContext;
    8. import javax.persistence.Query;
    9. import java.math.BigInteger;
    10. import java.util.*;
    11. @Slf4j
    12. @Service
    13. public class SnService {
    14. @PersistenceContext
    15. private EntityManager em;
    16. // @Modifying
    17. // @Transactional
    18. public PageResultVO getSnActiveCount(String snChannels, String snVersion, String snYear, Integer fromCount, Integer toCount, Integer page, Integer size) {
    19. List snChannelList = Arrays.asList(snChannels.split(","));
    20. // 原来的方式过慢,所以放弃(原因:sql中的where条件中的IF(...)导致不走索引,导致查询过慢
    21. // List leaderboards = snDTORepository.getSnActiveCountForLeaderboard(snChannelList, snVersion, snYear, fromCount, toCount);
    22. Map params = new HashMap();
    23. StringBuilder sqlWhere = new StringBuilder();
    24. if(StringUtils.isNotBlank(snChannels)){
    25. //if(snChannelList.size()!=0){ // 始终有值size()=1
    26. sqlWhere.append("and sn_channel in (:snChannels) ");
    27. params.put("snChannels", snChannelList);
    28. }
    29. if(StringUtils.isNotBlank(snVersion)){
    30. sqlWhere.append("and sn_version = :snVersion ");
    31. params.put("snVersion", snVersion);
    32. }
    33. if(StringUtils.isNotBlank(snYear)){
    34. sqlWhere.append("and sn_year = :snYear ");
    35. params.put("snYear", snYear);
    36. }
    37. if(fromCount==0 && toCount==0){
    38. // sqlWhere.append("and count!=0"); // !=号在mysql5.6中不走索引,8.0才开始走索引
    39. sqlWhere.append("and count>0 ");
    40. }else {
    41. if (fromCount != 0) {
    42. sqlWhere.append("and count >= :fromCount ");
    43. params.put("fromCount", fromCount);
    44. }
    45. if (toCount != 0) {
    46. sqlWhere.append("and count < :toCount ");
    47. params.put("toCount", toCount);
    48. }
    49. }
    50. /**
    51. * 条件尽量不放在Having里面,要放在where中,减少聚合数据量
    52. */
    53. //StringBuilder sqlHaving = new StringBuilder();
    54. //if(fromCount!=0){
    55. // sqlHaving.append("and active_count >= :fromCount ");
    56. // params.put("fromCount", fromCount);
    57. //}
    58. //if(toCount!=0){
    59. // sqlHaving.append("and active_count < :toCount ");
    60. // params.put("toCount", toCount);
    61. //}
    62. StringBuilder sql = new StringBuilder();
    63. sql.append("select " +
    64. "SQL_CALC_FOUND_ROWS " + // 供查询总数使用(如果不加这个,后面的select found_rows()返回的数量为limit后的数量,而不是总数。注意使用空格隔开没有逗号)
    65. "sn as name, max(count) as active_count from sn_state_changed " +
    66. "where 1=1 ");
    67. sql.append("and sn not like 'FF%' and sn not like 'ZZ%' "); // 排除无效数据
    68. sql.append(sqlWhere);
    69. sql.append( "group by sn ");
    70. // sql.append( "having active_count!=0 and sn not like 'FF%' and sn not like 'ZZ%' ");
    71. // sql.append(sqlHaving);
    72. sql.append("order by active_count desc");
    73. Query nativeQuery = em.createNativeQuery(sql.toString(), SnStateChangedVO.class);
    74. for (String key : params.keySet()) {
    75. nativeQuery.setParameter(key, params.get(key));
    76. }
    77. nativeQuery.setFirstResult((page-1)*size);
    78. nativeQuery.setMaxResults(size);
    79. List resultList = nativeQuery.getResultList();
    80. StringBuilder sqlTotalCount = new StringBuilder();
    81. sqlTotalCount.append("select FOUND_ROWS() as total;"); // 查询总数(注意务必放在同一个session中执行(这里的@PersistenceContext entityManage每次载入就是一个session,所以当前这一个方法中是同一个session))
    82. Query nativeQuery2 = em.createNativeQuery(sqlTotalCount.toString());
    83. Long total = ((BigInteger)nativeQuery2.getSingleResult()).longValue();
    84. return PageResultVO.builder().list(resultList).total(total).build();
    85. }
    86. }

     其中包含两个实体类PageResultVOSnStateChangedVO

    1. import lombok.Builder;
    2. import lombok.Data;
    3. import java.util.List;
    4. @Builder
    5. @Data
    6. public class PageResultVO {
    7. private List list;
    8. private Long total;
    9. }
    1. import lombok.Data;
    2. import javax.persistence.Entity;
    3. import javax.persistence.Id;
    4. @Entity
    5. @Data
    6. public class SnStateChangedVO implements Comparable{
    7. @Id
    8. private String name;
    9. private Integer activeCount;
    10. @Override
    11. public int compareTo(SnStateChangedVO o) {
    12. // 升序(由小到大)
    13. return this.getName().compareTo(o.getName());
    14. }
    15. }

    PS额外小知识:

    查询“条件”尽量不要放在Having里面,放在where中,目的是减少聚合数据量。

  • 相关阅读:
    Biome 1.7 发布,支持从 ESLint 和 Prettier 迁移
    Verilog代码之求勾股定理和arctan
    介词练习题
    生态环境综合管理信息化平台推动生态环境部门数字化转型
    C++类成员初始化顺序(声明初始化,初始化列表初始化和构造函数初始化)
    docker进程隔离
    分布式消息队列RocketMQ的应用(代码实例1)
    【leetocde】128. 最长连续序列
    Redis ----Spring MVC 有时候找不到类的原因
    Redis核心数据结构【set】【从入门到入坟】
  • 原文地址:https://blog.csdn.net/HD243608836/article/details/127882037