JPA多条件复杂SQL动态分页查询功能

概述

  ORM映射为我们带来便利的同时,也失去了较大灵活性,如果SQL较复杂,要进行动态查询,那必定是一件头疼的事情(也可能是lz还没发现好的方法),记录下自己用的三种复杂查询方式。

环境

springBoot

IDEA2017.3.4

JDK8

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.xmlxy</groupId>
  <artifactId>seasgame</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>seasgame</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!--数据库连接-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <!-- 热启动等 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <!--Java bean 实体-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!--swagger2 API 测试工具 -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.8.0</version>
    </dependency>
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.8.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>
    <!--安全框架认证-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>net.sf.json-lib</groupId>
      <artifactId>json-lib</artifactId>
      <version>2.2.2</version>
      <classifier>jdk15</classifier>
    </dependency>
    <!--汉字转拼音-->
    <dependency>
      <groupId>com.belerweb</groupId>
      <artifactId>pinyin4j</artifactId>
      <version>2.5.1</version>
    </dependency>
    <!-- thymeleaf模板 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!--
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    移除嵌入式tomcat插件
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  <packaging>war</packaging>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
    <finalName>seasgame</finalName>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>2.3.2</version>
          <configuration>
            <encoding>${project.build.sourceEncoding}</encoding>
            <source>1.7</source>
            <target>1.7</target>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <configuration>
            <testFailureIgnore>true</testFailureIgnore>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

@Query

当一个SQL较为复杂时,第一个想到的就是原生的SQL语句。如果只是简单的查询,那情况还没这么糟糕

 @Query(value = " SELECT IFNULL(sum(right_num),0) sumRight FROM t_record WHERE record_owner_id = ?1 AND responder_no = ?2 ",nativeQuery = true)
 Map<String,Object> sumRightNum(int studentId,int responderNo);

但如果需要进行动态查询,或更改,那这个value就变得复杂了。

package com.xmlxy.seasgame.dao;
import com.xmlxy.seasgame.entity.ScoreEntity;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
 *
 * Description:
 * @author hwc
 * @date 2019/9/5
 * @return
*/
public interface ScoreDao extends CrudRepository<ScoreEntity,Integer>
{
  /**
   *
   * Description:
   *@param scoreEntity
   * @author hwc
   * @date 2019/9/6
  */
  @Transactional(rollbackFor = Exception.class)
  @Modifying
  @Query(value = "UPDATE t_score t SET " +
      "t.responder_no = CASE WHEN :#{#scoreEntity.responderNo} IS NULL THEN t.responder_no ELSE :#{#scoreEntity.responderNo} END," +
      "t.max_level = CASE WHEN :#{#scoreEntity.maxLevel} IS NULL THEN t.max_level ELSE :#{#scoreEntity.maxLevel} END," +
      "t.right_num = CASE WHEN :#{#scoreEntity.rightNum} IS NULL THEN t.right_num ELSE :#{#scoreEntity.rightNum} END," +
      "t.use_time = CASE WHEN :#{#scoreEntity.userTime} IS NULL THEN t.use_time ELSE :#{#scoreEntity.userTime} END WHERE student_id = :#{#scoreEntity.getStudentId()}",nativeQuery = true)
  void updateScore(@Param("scoreEntity") ScoreEntity scoreEntity);
}

JPQL

如果Java代码内发出JPQL查询,就需要利用到EntityManager的响应方法了。一般执行以下流程

获取一个EntityManager实例

调用实例的方法createQuery,创建一个Query实例,如果有需要可以指定检索的最大数量和起始位置

使用Query方法getResultList执行查询,当然更新和删除操作得使用executeUpdate执行

进行一个复杂的动态SQL查询

public Page<RankEntity> getScoreByRank(int gradeId,int classId,Pageable pageable)
 {
   StringBuilder countSelectSql = new StringBuilder("");
   countSelectSql.append(" SELECT COUNT(*) ");
   countSelectSql.append(" FROM ");
   countSelectSql.append(" t_score s, ");
   countSelectSql.append(" t_student st  ");
   countSelectSql.append(" WHERE ");
   countSelectSql.append(" s.student_id = st.student_id ");
   StringBuilder selectSql = new StringBuilder();
   selectSql.append(" SELECT s.student_id,st.real_name,st.student_class,s.max_level,s.use_time,s.right_num ");
   selectSql.append(" FROM t_score s ");
   selectSql.append(" JOIN t_student st ON s.student_id = st.student_id ");
   selectSql.append(" WHERE 1 = 1 ");
   Map<String,Object> params = new HashMap<>();
   StringBuilder whereSql = new StringBuilder();
   if (gradeId != -1)
   {
     whereSql.append(" AND st.student_grade = :student_grade ");
     params.put("student_grade",gradeId);
   }
   /**班级ID*/
   if (classId != -1)
   {
     whereSql.append(" AND st.student_class = :classId ");
     params.put("classId",classId);
   }
   String orderSql = " ORDER BY s.max_level DESC,s.use_time,s.right_num ASC ";
   String countSql = new StringBuilder().append(countSelectSql).append(whereSql).toString();
   Query countQuery = entityManager.createNativeQuery(countSql);
   for (Map.Entry<String,Object> entry : params.entrySet())
   {
     countQuery.setParameter(entry.getKey(),entry.getValue());
   }
   BigInteger totalCount = (BigInteger)countQuery.getSingleResult();
   String querySql = new StringBuilder().append(selectSql).append(whereSql).append(orderSql).toString();
   Query query = entityManager.createNativeQuery(querySql,RankEntity.class);
   for (Map.Entry<String,Object> entry:params.entrySet())
   {
     query.setParameter(entry.getKey(),entry.getValue());
   }
   query.setFirstResult((int) pageable.getOffset());
   query.setMaxResults(pageable.getPageSize());
   List<RankEntity> rankEntities = query.getResultList();
   Page<RankEntity> page = new PageImpl<>(rankEntities,pageable,totalCount.longValue());
   return page;
 }

注意:如果没有重新定义Pageable那么pageNumber必须减1,因为是从0开始的。

Criteria

这是一种规范查询是以元模型的概念为基础的,这个元模型可以是实体累,嵌入类,或者映射的父类,简单介绍几个里面用到接口。

CriteraQuery是一个特定的顶层查询对象,里面包含select,from,where,order by等各个部分,然而他只对实体类或嵌入类的标准查询起作用。

Root标准查询的根对象,根定义了实体类型,是你想要查询要获得的结果,也可以添加查询条件,结合实体管理对象得到查询的对象。

CriteriaBuilder接口用来构建CritiaQuery的构建器

StudentEntity类

package com.xmlxy.seasgame.entity;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import javax.persistence.*;
import javax.print.attribute.standard.MediaSize;
import java.io.Serializable;
/**
 *
 * Description:学生对象
 * @param
 * @author hwc
 * @date 2019/8/8
*/
@Entity
@Table(name = "t_base_student")
@ApiModel
@Data
public class StudentEntity implements Serializable
{
  private static final long serialVersionUID = 546L;
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "student_id")
  private Integer studentId;
  @Column(name = "student_grade")
  private Integer studentGrade;
  @Column(name = "student_class")
  private Integer studentClass;
  @Column(name = "address")
  private String address;
  @Column(name = "telephone")
  private Integer telephone;
  @Column(name = "real_name")
  private String realName;
  @Column(name = "id_number")
  private String idNumber;
  @Column(name = "study_id")
  private String studyId;
  @Column(name = "is_delete")
  private int isDelete;
  @Column(name = "uuid")
  private String uuid;
}

dao层

public interface StudentDao extends JpaRepository<StudentEntity,Integer>,JpaSpecificationExecutor
{
}

动态查询

 public Page<StudentEntity> getTeacherClassStudent(int pageNumber,int pageSize,int gradeId, int classId,String keyword)
  {
    pageNumber = pageNumber < 0 ? 0 : pageNumber;
    pageSize = pageSize < 0 ? 10 : pageSize;
    Specification<StudentEntity> specification = new Specification<StudentEntity>()
    {
      @Override
      public Predicate toPredicate(Root<StudentEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder)
      {
        //page : 0 开始, limit : 默认为 10
        List<Predicate> predicates = new ArrayList<>();
        predicates.add(criteriaBuilder.equal(root.get("studentGrade"),gradeId));
        predicates.add(criteriaBuilder.equal(root.get("studentClass"),classId));
        if (!Constant.isEmptyString(keyword))
        {
          predicates.add(criteriaBuilder.like(root.get("realName").as(String.class),"%" + keyword + "%"));
        }
        return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
      }
    };
    /*studentId必须是实体类属性与数据库对应,否则报ropertyReferenceException异常*/
    PageRequest page = new PageRequest(pageNumber,pageSize,Sort.Direction.ASC,"studentId");
    Page<StudentEntity> pages = studentDao.findAll(specification,page);
    return pages;
  }

因为这个项目应用比较简单,所以条件只有一个,如果条件较多,甚至可以定义一个专门的类去接收拼接参数,然后判

断,成立就add进去。

总结

以上所述是小编给大家介绍的JPA多条件复杂SQL动态分页查询功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

时间: 2019-09-05

sql分页查询几种写法

关于SQL语句分页,网上也有很多,我贴一部分过来,并且总结自己已知的分页到下面,方便日后查阅 1.创建测试环境,(插入100万条数据大概耗时5分钟). create database DBTest use DBTest --创建测试表 create table pagetest ( id int identity(1,1) not null, col01 int null, col02 nvarchar(50) null, col03 datetime null ) --1万记录集 declar

SQLSERVER分页查询关于使用Top方式和row_number()解析函数的不同

临近春节,心早已飞了不在工作上了,下面小编给大家整理些数据库的几种分页查询. Sql Sever 2005之前版本: select top 页大小 * from 表名 where id not in ( select top 页大小*(查询第几页-1) id from 表名 order by id ) order by id 例如: select top 10 * --10 为页大小 from [TCCLine].[dbo].[CLine_CommonImage] where id not in

SQL Server 分页查询通用存储过程(只做分页查询用)

自开始做项目以来,一直在用.这段存储过程的的原创者(SORRY,忘记名字了),写得这段SQL代码很不错,我在这个基础上,按照我的习惯以及思维方式,调整了代码,只做分页查询用. /*---------------------------------------------- *procedure name : P_PageResult * author :Fay * create date : 2014-07-18 */ CREATE PROCEDURE prcPageResult -- 获得某一

Spring Data JPA 复杂/多条件组合分页查询

话不多说,请看代码: public Map<String, Object> getWeeklyBySearch(final Map<String, String> serArgs, String pageNum, String pageSize) throws Exception { // TODO Auto-generated method stub Map<String,Object> resultMap=new HashMap<String, Object&

MySQL百万级数据分页查询优化方案

当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询.对于数据库分页查询,也有很多种方法和优化的点.下面简单说一下我知道的一些方法. 准备工作 为了对下面列举的一些优化进行测试,下面针对已有的一张表进行说明. 表名:order_history 描述:某个业务的订单历史表 主要字段:unsigned int id,tinyint(4) int type 字段情况:该表一共37个字段,不包含text等大型数组,最大为varcha

Oracle实现分页查询的SQL语法汇总

本文实例汇总了Oracle实现分页查询的SQL语法,整理给大家供大家参考之用,详情如下: 1.无ORDER BY排序的写法.(效率最高) 经过测试,此方法成本最低,只嵌套一层,速度最快!即使查询的数据量再大,也几乎不受影响,速度依然! sql语句如下: SELECT * FROM (Select ROWNUM AS ROWNO, T.* from k_task T where Flight_date between to_date('20060501', 'yyyymmdd') and to_d

Oracle、MySQL和SqlServe三种数据库分页查询语句的区别介绍

先来定义分页语句将要用到的几个参数: int currentPage ; //当前页 int pageRecord ; //每页显示记录数 以之前的ADDRESSBOOK数据表为例(每页显示10条记录): 一.SqlServe下载 分页语句 String sql = "select top "+pageRecord +" * from addressbook where id not in (select top "+(currentPage-)*pageRecor

详解MySQL的limit用法和分页查询语句的性能分析

limit用法 在我们使用查询语句的时候,经常要返回前几条或者中间某几行数据,这个时候怎么办呢?不用担心,mysql已经为我们提供了这样一个功能. SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数.LIMIT 接受一个或两个数字参数.参数必须是一个整数常量.如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目.初始记

mysql千万级数据分页查询性能优化

mysql数据量大时使用limit分页,随着页码的增大,查询效率越低下. 实验 1.直接使用用limit start, count分页语句: select * from order limit start, count 当起始页较小时,查询没有性能问题,我们分别看下从10, 100, 1000, 10000开始分页的执行时间(每页取20条), 如下: select * from order limit 10, 20 0.016秒 select * from order limit 100, 20

Oracle分页查询性能优化代码详解

对于数据库中表的数据的 Web 显示,如果没有展示顺序的需要,而且因为满足条件的记录如此之多,就不得不对数据进行分页处理.常常用户并不是对所有数据都感兴趣的,或者大部分情况下,他们只看前几页. 通常有以下两种分页技术可供选择. Select * from ( Select rownum rn,t.* from table t) Where rn>&minnum and rn<=&maxnum 或者 Select * from ( Select rownum rn,t.* fro

MySQL 百万级分页优化(Mysql千万级快速分页)

以下分享一点我的经验 一般刚开始学SQL的时候,会这样写 复制代码 代码如下: SELECT * FROM table ORDER BY id LIMIT 1000, 10; 但在数据达到百万级的时候,这样写会慢死 复制代码 代码如下: SELECT * FROM table ORDER BY id LIMIT 1000000, 10; 也许耗费几十秒 网上很多优化的方法是这样的 复制代码 代码如下: SELECT * FROM table WHERE id >= (SELECT id FROM

MySQL 千万级数据量如何快速分页

前言 后端开发中为了防止一次性加载太多数据导致内存.磁盘IO都开销过大,经常需要分页展示,这个时候就需要用到MySQL的LIMIT关键字.但你以为LIMIT分页就万事大吉了么,Too young,too simple啊,LIMIT在数据量大的时候极可能造成的一个问题就是深度分页. 案例 这里我以显示电商订单详情为背景举个例子,新建表如下: CREATE TABLE `cps_user_order_detail` ( `id` bigint(20) unsigned NOT NULL AUTO_I

MySQL Limit性能优化及分页数据性能优化详解

MySQL Limit可以分段查询数据库数据,主要应用在分页上.虽然现在写的网站数据都是千条级别,一些小的的优化起的作用不大,但是开发就要做到极致,追求完美性能.下面记录一些limit性能优化方法. Limit语法: SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset LIMIT子句可以被用于强制 SELECT 语句返回指定的记录数.LIMIT接受一个或两个数字参数.参数必须是一个整数常量. 如果给定两个参数,第一个参数指定

MySQL数据库查询性能优化策略

优化查询 使用Explain语句分析查询语句 Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句. 通过对查询语句的分析,可以了解查询语句的执行情况,找出查询语句执行的瓶颈,从而优化查询语句. 使用索引查询 MySql中提高性能的一个最有效的方式就是对数据表设计合理的索引. 索引提供了高效访问数据的方法,并且加快查询速度. 如果查询时没有使用索引,那么查询语句将扫描表中所有的记录.在数据量大的时候,这样查询速度会很慢. 使用索引进行查询,查

mysql oracle和sqlserver分页查询实例解析

最近简单的对oracle,mysql,sqlserver2005的数据分页查询作了研究,把各自的查询的语句贴出来供大家学习..... (一).mysql的分页查询 mysql的分页查询是最简单的,借助关键字limit即可实现查询,查询语句通式: selecto.*from(sql)o limit firstIndex,pageSize 如下面的截图,每页显示的记录数为20: 查询(1-20)这20条记录 查询(21-40)这20条记录 mysql的分页查询就这么简单...... (二).sqls

Sql Server 查询性能优化之走出索引的误区分析

据了解绝大多数开发人员对于索引的理解都是一知半解,局限于大多数日常工作没有机会.也什么没有必要去关心.了解索引,实在哪天某个查询太慢了找到查询条件建个索引就ok,哪天又有个查询慢了,再建立个索引就是,或者干脆把整个查询SQL直接发给DBA,让DBA直接帮忙优化了,所以造成的状况就是开发人员对于索引的理解.认识很局限,以下就把我个人对于索引的理解及浅薄认识和大家分享下,希望能解除一些大家的疑惑,一起走出索引的误区 误区1.在表上建立了索引,在查询时用到了索引的列,索引就一定会生效 首先明确下这样的

MongoDB查询性能优化验证及验证

结论: 1. 200w数据,合理使用索引的情况下,单个stationId下4w数据.mongodb查询和排序的性能理想,无正则时client可以在600ms+完成查询,qps300+.有正则时client可以在1300ms+完成查询,qps140+. 2. Mongodb的count性能比较差,非并发情况下client可以在330ms完成查询,在并发情况下则需要1-3s.可以考虑估算总数的方法,http://blog.sina.com.cn/s/blog_56545fd30101442b.htm