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.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.1.6.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.xmlxygroupId>
    <artifactId>seasgameartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>seasgamename>
    <description>Demo project for Spring Bootdescription>

    <properties>
        <java.version>1.8java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>

        
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger2artifactId>
            <version>2.8.0version>
        dependency>
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger-uiartifactId>
            <version>2.8.0version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>net.sf.json-libgroupId>
            <artifactId>json-libartifactId>
            <version>2.2.2version>
            <classifier>jdk15classifier>
        dependency>
        
        <dependency>
            <groupId>com.belerwebgroupId>
            <artifactId>pinyin4jartifactId>
            <version>2.5.1version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>
        

        <dependency>
            <groupId>javax.servletgroupId>
            <artifactId>javax.servlet-apiartifactId>
            <version>3.1.0version>
            <scope>providedscope>
        dependency>
    dependencies>
    <packaging>warpackaging>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
        plugins>

        <finalName>seasgamefinalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-compiler-pluginartifactId>
                    <version>2.3.2version>
                    <configuration>
                        <encoding>${project.build.sourceEncoding}encoding>
                        <source>1.7source>
                        <target>1.7target>
                    configuration>
                plugin>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-surefire-pluginartifactId>
                    <configuration>
                        <testFailureIgnore>truetestFailureIgnore>
                    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 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
{
  
    /** 
     * 
     * 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的响应方法了。一般执行以下流程

  1. 获取一个EntityManager实例
  2. 调用实例的方法createQuery,创建一个Query实例,如果有需要可以指定检索的最大数量和起始位置
  3. 使用Query方法getResultList执行查询,当然更新和删除操作得使用executeUpdate执行

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

   public Page 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 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 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 entry:params.entrySet())
        {
            query.setParameter(entry.getKey(),entry.getValue());
        }
        query.setFirstResult((int) pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());

        List rankEntities = query.getResultList();
        Page 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,JpaSpecificationExecutor
{
}

动态查询

    public Page getTeacherClassStudent(int pageNumber,int pageSize,int gradeId, int classId,String keyword)
    {
        pageNumber = pageNumber < 0 ? 0 : pageNumber;
        pageSize = pageSize < 0 ? 10 : pageSize;
        Specification specification = new Specification()
        {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder)
            {
                //page : 0 开始, limit : 默认为 10
                List 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 pages = studentDao.findAll(specification,page);
        return pages;
    }

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

 

你可能感兴趣的