Spring JPA整合QueryDSL

前言

Spring JPA是目前比較經常使用的ORM解決方案,可是其對於某些場景並非特別的方便,例如查詢部分字段,聯表查詢,子查詢等。java

而接下來我會介紹與JPA造成互補,同時也是與JPA兼容得很好的框架QueryDSLmysql

同時因爲目前主流使用Spring Boot,因此本文也會基於Spring Boot來進行演示git

若是對於長文無感,可是又但願瞭解QueryDSL能夠直接查看文章最後的總結github

環境信息

如下爲示例的關鍵環境信息spring

  1. JDK 1.8
  2. maven 3.6.1
  3. SpringBoot 2.2.0.RELEASE
  4. IntelliJ IDEA 2019.2.3
  5. lombok
  6. mysql-5.7

源碼地址

https://github.com/spring-bas...sql

項目整合

pom文件配置

QueryDSL自己定位就是對某些技術的補充或者說是完善,其提供了對JPAJDBCJDO等技術的支持。這裏引入的是QueryDSL-JPA,須要注意必定要引入querydsl代碼生成器插件。數據庫

<properties>
        <java.version>1.8</java.version>
        <querydsl.version>4.2.1</querydsl.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--使用版本較老的mysql驅動包,用於鏈接mysql-5.7-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--引入querydsl-jpa依賴-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--引入querydsl代碼生成器插件-->
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <dependencies>
                    <dependency>
                        <groupId>com.querydsl</groupId>
                        <artifactId>querydsl-apt</artifactId>
                        <version>${querydsl.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <!--設置插件生效的maven生命週期-->
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                           <!--配置生成文件的目錄--> 
                           <outputDirectory>src/generated-sources/java/</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

application配置文件

spring:
  datasource:
    ## 數據庫相關配置
    url: jdbc:mysql://127.0.0.1:3306/example?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver # 指定驅動類
  jpa:
    hibernate:
      ddl-auto: update # 自動建立表以及更新表結構,生產環境慎用
    show-sql: true # 打印執行的SQL

配置類

因爲QueryDSL不提供starter,因此須要自行準備一個配置類,代碼以下所示express

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 * QueryDSL配置類
 * @author Null
 * @date 2019-10-24
 */
@Configuration
public class QuerydslConfig {

    @Autowired
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory queryFactory(){
        return new JPAQueryFactory(entityManager);
    }

}

啓動類

啓動類很簡單,只須要使用@SpringBootApplication便可app

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class QuerydslJpaDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuerydslJpaDemoApplication.class, args);
    }
}

實體類

主要有講師和課程,每一個課程都有一個講師,每一個講師有多個課程,即講師與課程的關係爲一對多框架

課程

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * 課程,一個課程對應一個講師
 * @author Null
 * @date 2019-10-24
 */
@Data
@Entity
public class Course {
    /**
     * 課程ID
     */
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    /**
     * 課程名稱
     */
    private String name;
    /**
     * 對應講師的ID
     */
    private Long lecturerId;
}

講師

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * 講師,一個講師有多個課程
 * @author Null
 * @date 2019-10-24
 */
@Data
@Entity
public class Lecturer {
    /**
     * 講師ID
     */
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;
    /**
     * 講師名字
     */
    private String name;
    /**
     * 性別,true(1)爲男性,false(0)爲女性
     */
    private Boolean sex;
}

Repository接口

若是要使用QuerDSL須要Repository接口除了繼承JpaRepository接口(此接口爲Spring-JPA提供的接口)外,還須要繼承QuerydslPredicateExecutor接口。關鍵示例以下:

課程Repository

import com.example.querydsl.jpa.entity.Course;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;

/**
 * 課程Repository
 *
 * @author Null
 * @date 2019-10-24
 */
public interface CourseRepository extends
        JpaRepository<Course, Integer>,
        QuerydslPredicateExecutor<Course> {
}

講師Repository

import com.example.querydsl.jpa.entity.Lecturer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;

/**
 * 講師Repository
 * @author Null
 * @date 2019-10-24
 */
public interface LecturerRepository extends
        JpaRepository<Lecturer,Integer>,
        QuerydslPredicateExecutor<Lecturer> {
}

代碼生成

前面配置QueryDSL代碼生成器就是用於這一步,==每次實體類有變動最好重複執行本步驟從新生成新的代碼==。因爲我的習慣使用IDEA,因此以IDEA做爲演示。

  1. 雙擊下圖內容便可生成代碼了,

    image

  2. 而後就會在src/generated-sources目錄能夠看到生成的代碼,包名與實體包名一致,可是類名爲Q開頭的文件

    image

  3. 上一步的截圖咱們能夠看到其實生成的代碼被IDEA識別爲普通文件了,因此咱們須要標記src/generated-sources/java目錄的用途,以下圖所示
    image

    標記後,效果以下,能夠看到代碼被正確識別了
    image
    到了這一步其實已經完成整合了,下面就開始驗證是否正確整合以及展現QueryDSL的優點了

驗證整合與演示

下面我會使用單元測試來驗證QueryDSL是否正確整合以及演示一下QueryDSL的優點

單元測試類

這裏主要是單元測試類的關鍵內容,須要注意@BeforeEachJunit5的註解,表示每一個單元測試用例執行前會執行的方法其實對應Junit4@Before

/**
 * @SpringBootTest 默認不支持事務且自動回滾
 * 使用@Transactional 開啓事務,
 * 使用@Rollback(false) 關閉自動回滾
 * @author Null
 * @date 2019-10-24
 */
@SpringBootTest
class QuerydslJpaDemoApplicationTests {

    @Autowired
    private CourseRepository courseRepository;

    @Autowired
    private LecturerRepository lecturerRepository;
    
    @Autowired
    private JPAQueryFactory queryFactory;

    /**
     * 初始化數據
     */
    @BeforeEach
    public void initData(){
        // 清空數據表
        courseRepository.deleteAll();
        lecturerRepository.deleteAll();

        // 初始化講師
        Lecturer tom=new Lecturer();
        tom.setName("Tom");
        tom.setSex(true);
        lecturerRepository.save(tom);

        Lecturer marry=new Lecturer();
        marry.setName("Marry");
        marry.setSex(false);
        lecturerRepository.save(marry);

        // 初始化課程
        Course chinese=new Course();
        chinese.setName("Chinese");
        chinese.setLecturerId(tom.getId());
        courseRepository.save(chinese);

        Course physics=new Course();
        physics.setName("Physics");
        physics.setLecturerId(tom.getId());
        courseRepository.save(physics);

        Course english=new Course();
        english.setName("English");
        english.setLecturerId(marry.getId());
        courseRepository.save(english);
    }
    
    ...省略各個用例
    
}

單表模糊查詢

/**
     * 根據課程名稱模糊查詢課程
     */
    @Test
    public void testSelectCourseByNameLike() {
        // 組裝查詢條件
        QCourse qCourse = QCourse.course;
        // %要自行組裝
        BooleanExpression expression = qCourse.name.like("P%");
        System.out.println(courseRepository.findAll(expression));
    }

聯表查詢

/**
     * 根據講師姓名查課程
     */
    @Test
    public void testSelectCourseByLecturerName(){
        QCourse qCourse = QCourse.course;
        QLecturer qLecturer = QLecturer.lecturer;
        // 這裏包含了組裝查詢條件和執行查詢的邏輯,組裝好條件後記得執行fetch()
        List<Course> courses=queryFactory.select(qCourse)
                .from(qCourse)
                .leftJoin(qLecturer)
                .on(qCourse.lecturerId.eq(qLecturer.id))
                .where(qLecturer.name.eq("Tom"))
                .fetch();
        System.out.println(courses);
    }

更新

/**
     * 根據姓名更新講師性別<br/>
     * 使用@Transactional開啓事務<br/>
     * 使用@Rollback(false)關閉自動回滾<br/>
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testUpdateLecturerSexByName(){
        QLecturer qLecturer = QLecturer.lecturer;
        // 更新Tom的性別爲女性,返回的是影響記錄條數
        long num=queryFactory.update(qLecturer)
                .set(qLecturer.sex,false)
                .where(qLecturer.name.eq("Tom"))
                .execute();
        // 這裏輸出被更新的記錄數
        System.out.println(num);
    }

刪除

/**
     * 根據根據性別刪除講師
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testDeleteLecturerBySex(){
        QLecturer qLecturer = QLecturer.lecturer;
        // 刪除性別爲男性的講師
        long num=queryFactory.delete(qLecturer)
                .where(qLecturer.sex.eq(true))
                .execute();
        // 輸出被刪除的記錄數
        System.out.println(num);
    }

用例分析

從用例中能夠看出其實QueryDSLAPI更加切合原生的SQL,基本上從代碼上就能夠看出你但願執行的SQL了。

細心的朋友會發現QueryDSL是沒有insert方法,由於JPA提供的save()方法已經足夠處理了。

同時要記得要組裝好你的SQL後別忘記調用fetch()或者execute()方法。

總結

  • Spring Boot JPA整合QueryDSL的關鍵步驟

    1. 引入依賴和插件
    2. 編寫配置類
    3. 使用插件生成代碼
    4. 標記生成文件爲代碼
    5. Repository繼承QuerydslPredicateExecutor
  • QueryDSLAPI相似原生SQLAPI風格相似StringBuilderAPIFluent API風格)。可是不提供insert對應的操做。
  • QueryDSL對於複雜的SQL的支持十分友好,算是對於JPA對這塊需求的補充和完善。
相關文章
相關標籤/搜索