博客 / 詳情

返回

rest-apiV2.0.0升級為simplest-api開源框架生態之simplest-jpa發佈

什麼是 simplest

simplest 追求存粹簡單和極致。

旨在為項目快速開發提供一系列的基礎能力,方便用户根據項目需求快速進行功能拓展

不在去關心一些繁瑣。重複工作,而是把重點聚焦到業務。

前言

程序 10 年。作為一個多年程序。深知每個項目和程序,都有很多重複性工作要做。
入行近 10 年,我寫過很多程序,也寫死過很多程序。。。。。

見證互聯網黃金時代,到如今的萎靡。幸運是我還在程序員大軍中。和你們一起奮鬥!

我的故事 <<程序員三時>> 公眾號 期待與你交流。希望給迷茫你一點啓發和幫助。

相關倉庫

項目 簡介 gitee 地址 github 地址
simplest-api 前後端分離項目基於 simplest-api 可以快速構建基於 Web Json API 格式的統一通訊交互 https://gitee.com/daTouY/simplest-api/tree/main/ https://github.com/coder-amiao/simplest-api
simplest-jpa 基於 QueryDSL 語法靈活強大的 QueryWrapper,鏈式 QueryChain 強大自由組合查詢器,像寫原生 SQL 一樣基於 Java API 查詢數據,優雅極致。 https://gitee.com/daTouY/simplest-jpa https://github.com/coder-amiao/simplest-jpa
simplest-boot 業務通用生態核心組件 https://gitee.com/daTouY/simplest-boot.git https://github.com/coder-amiao/simplest-boot
simplest-admin 基於 RABC 權限模型,功能豐富最新技術棧 Vue3.3 + Vite4 + TS + Pinia + Element-Plus 管理後台腳手架。開箱即用,可定製的代碼生成模板,讓你只需專注於業務開發。 https://gitee.com/daTouY/simplest-admin.git https://github.com/coder-amiao/simplest-admin

Simplest開發文檔

這裏主要介紹simplest-jpa 使用

快速開始

項目 pom 中引入依賴

<dependency>
    <groupId>cn.soboys</groupId>
    <artifactId>simplest-jpa-spring-boot-starter</artifactId>
    <version>1.0.1</version>
</dependency>

<!-- MySQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>

在 SpringBoot 啓動類或者配置類上通過 @EnableJPAQuery註解開啓 simplest-jpa

@SpringBootApplication
@EnableJPAQuery
public class SpringbootJpaApplication {

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

}

到此你項目中就可以使用所有的功能了。

數據庫配置

在項目中配置對應數據庫連接

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/rest-admin?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 10
      maximum-pool-size: 20
      idle-timeout: 600000
      max-life-time: 1800000

  jpa:
    hibernate:
      naming:
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
      ddl-auto: update # 控制是否可以基於程序中Entity的定義自動創建或者修改DB中表結構
    show-sql: true #控制是否打印運行時的SQL語句與參數信息
    database-platform: org.hibernate.dialect.MySQLDialect #數據庫方言
    open-in-view: true
    properties:
      hibernate:
        enable_lazy_load_no_trans: true

定義對應entity 對應數據庫表。JPA 會自動幫你生成數據庫。

package cn.soboys.springbootjpa.entity;

import cn.soboys.springbootjpa.entity.base.BaseEntity;
import cn.soboys.springbootjpa.entity.dto.TitleVo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * @author 公眾號 程序員三時
 * @version 1.0
 * @date 2023/7/19 10:44
 * @webSite https://github.com/coder-amiao
 * 內容分類
 */
@Data
@Entity
@Table(name = "cms_category")
public class Category extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;


    /**
     * 標題
     */
    @Column(nullable = false, length = 64)
    @Schema(description = "標題")
    private String title;


    /**
     * @Embedded 用户映射數據庫表到一個實體vo。
     */
    @Embedded
    @Schema(description = "標題信息")
    private TitleVo titleVo;

    /**
     * 描述
     */
    @Schema(description = "描述")
    private String described;

    /**
     * 圖標
     */
    @Column( length = 32)
    @Schema(description = "圖標",maxLength = 32)
    private String icon;

    /**
     * 圖片
     */
    @Column( length = 32)
    @Schema(description = "圖片",maxLength = 32)
    private String pic;

    /***
     * 引用關係不填寫。默認對應主鍵。
     */
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},fetch = FetchType.LAZY)
    @JoinTable(name = "cms_module_relation",
            joinColumns = @JoinColumn(name = "resource_id"),
            inverseJoinColumns = @JoinColumn(name = "module_id"))
    private Set<Module> modules=new HashSet<>();


    /**
     * 額外其他屬性
     * @Transient 解綁和數據聯繫。屬於實體類屬性
     */
    @Transient
    private String other;


}

生成對應查詢EntityPath

基於 QueryDSL 的APT 技術 在 maven 的 pom.xml 中引入對應的插件

<plugin>
    <!--因為QueryDsl是類型安全的,所以還需要加上Maven APT plugin,使用 APT 自動生成Q類:-->
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources/java</outputDirectory>
                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>

然後通過 maven 編譯項目。

會在你指定目錄生成對應查詢EntityPaht實體

簡單查詢

  1. 編寫自己的Repository 繼承通用BaseRepository
package cn.soboys.springbootjpa.repository;

import cn.soboys.simplestjpa.BaseRepository;
import cn.soboys.springbootjpa.entity.Category;
import org.springframework.stereotype.Repository;

/**
 * @author 公眾號 程序員三時
 * @version 1.0
 * @date 2023/7/19 12:02
 * @webSite https://github.com/coder-amiao
 * 數據庫 dao層。
 */
@Repository
public interface CategoryRepository extends BaseRepository<Category, Long{


}
  1. 編寫自己的Service 繼承通用Service
package cn.soboys.springbootjpa.service;

import cn.soboys.simplestjpa.IService;
import cn.soboys.springbootjpa.entity.Category;
import org.springframework.data.jpa.repository.Query;

/**
 * @author 公眾號 程序員三時
 * @version 1.0
 * @date 2023/7/19 17:08
 * @webSite https://github.com/coder-amiao
 */
public interface ICategoryService extends IService<Category,Long> {



}

實現類

package cn.soboys.springbootjpa.service.impl;

import cn.soboys.simplestjpa.ServiceImpl;
import cn.soboys.springbootjpa.entity.Category;
import cn.soboys.springbootjpa.repository.CategoryRepository;
import cn.soboys.springbootjpa.service.ICategoryService;
import org.springframework.stereotype.Service;

/**
 * @author 公眾號 程序員三時
 * @version 1.0
 * @date 2023/7/20 14:46
 * @webSite https://github.com/coder-amiao
 */
@Service
public class CategoryServerImpl extends ServiceImpl<CategoryRepository, Category, Long> implements ICategoryService {


}

這樣你 service 有基礎所有操作數據增刪改查的方法

package cn.soboys.springbootjpa;

import cn.soboys.simplestjpa.UpdateWrapper;
import cn.soboys.springbootjpa.entity.Category;
import cn.soboys.springbootjpa.entity.QCategory;
import cn.soboys.springbootjpa.entity.dto.QTitleVo;
import cn.soboys.springbootjpa.service.ICategoryService;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAUpdateClause;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.text.StrUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;


/**
 * @author 公眾號 程序員三時
 * @version 1.0
 * @date 2023/7/26 21:54
 * @webSite https://github.com/coder-amiao
 */
@SpringBootTest
@Slf4j
public class ServiceTest {
    @Autowired
    private ICategoryService categoryService;

    @Test
    void countByExample() {
        Category category = new Category();
        //category.setTitle("測試");
        long count = categoryService.count(Example.of(category));
        log.info("條件count{}", count);
    }

    @Test
    void getById() {
        Optional<Category> category = categoryService.getByIdOpt(6l);
        if (category.isPresent()) {
            log.info(category.get().toString());
        }
    }

    @Test
    void getOne() {
        QCategory qCategory = QCategory.category;
        QTitleVo vo=QTitleVo.titleVo;
        Predicate query=vo.subTitle.eq("batch1");
        Category category = categoryService.getOne(query);
        log.info(category.toString());
    }

    @Test
    void getPageQuery() {
        QCategory qCategory = QCategory.category;
        PageRequest pageRequest = PageRequest.of(0, 20); //第一頁從零開始
        Predicate query = qCategory.title.like("%" + "batch" + "%");
        Page<Category> categoryList = categoryService.page(pageRequest, query);
        log.info("數量{}", categoryList.getContent().size());
    }

    @Test
    void getPage() {
        QCategory qCategory = QCategory.category;

      // categoryService.getJPAQueryFactory().select().where(qCategory.)
    }


    @Test
    void save() {
        Category c = new Category();
       // c.setTitle("保存");
        categoryService.save(c);
    }

    @Test
    void deleteById() {
        categoryService.removeById(6l);
    }

    @Test
    void deleteAll() {
        List<Long> ids = new ArrayList<>();
        ids.add(6l);
        ids.add(7l);
        Boolean flag = categoryService.removeByIds(ids);
    }

    /**
     * 實體ID對應存在更新否則添加
     */
    @Test
    void saveOrUpdate() {
        Category c = new Category();
       // c.setTitle("保存");
        categoryService.saveOrUpdate(c);
    }


    @Test
    @Rollback(value = false)
    @Transactional
    void updateChain() {
        QCategory qCategory = QCategory.category;
        categoryService.updateChain(qCategory)
                .set(qCategory.title, "測試jpa")
                .where(qCategory.id.eq(6l)).execute();
    }


    @Test
    @Rollback(value = false)
    @Transactional
    void update() {
        QCategory qCategory = QCategory.category;
        JPAUpdateClause updateWrapper = UpdateWrapper.of(qCategory);
        updateWrapper.set(qCategory.title, "bh").where(qCategory.id.eq(6l));

        Boolean flag = categoryService.update(updateWrapper);

        log.info("更新{}", flag);
    }

    @Test
    @Rollback(value = false)
    @Transactional
    void updateIgnoreNull() {
        Category category = new Category();
        category.setId(6l);
       // category.setSubTitle("忽略");
        //Category category1 = categoryService.update(category, true);  //會自動忽略實體空屬性。

        //category.setTitle("");
        Category category1 = categoryService.update(category, true, new String[]{"title"});  //自定義不忽略字段,
        log.info("更新{}", category1);
    }

    @Test
    void selectQueryChain() {
        QCategory qCategory = QCategory.category;
        List<String> categoryList = categoryService.queryChain()
                .select(qCategory.title)
                .from(qCategory).fetch();
        log.info("返回條數{}", categoryList.size());
    }


    @Test
    void selectQuery() {
        QCategory qCategory = QCategory.category;
        BooleanBuilder booleanBuilder = new BooleanBuilder();

        String subTitle = "88";
        if (StrUtil.isNotEmpty(subTitle)) {
            booleanBuilder.and(qCategory.described.eq("88"));
        }
        long id = 6l;
        if (!StrUtil.isBlankIfStr(id)) {
            booleanBuilder.and(qCategory.id.eq(6l));
        }
        List<Category> categories = categoryService.list(booleanBuilder);
        log.info("返回條數{}", categories.size());
    }

}

Simplest-JPA

simplest-jpa內置名為 BaseRepository 接口,它實現了基本的增刪改查功能以及分頁查詢功能。 和對應Service

新增數據

Service 提供了save,saveBatch,saveOrUpdate 方法

  • save(T entity)插入實體類數據,不忽略 null 值。
  • saveBatch(Collection<T> entities) 批量插入實體類數據
  • saveOrUpdate(T entity) 插入或者更新,若主鍵有值,則更新,若沒有主鍵值,則插入,插入或者更新都不會忽略 null 值。
  • saveOrUpdateSelective(T entity)插入或者更新,若主鍵有值,則更新,若沒有主鍵值,則插入,更新會忽略 null 值。

刪除數據

Service 提供了remove,removeAll,removeById,removeByIds 方法

  • removeById(ID id) 根據主鍵刪除數據
  • removeById(Collection<? extends ID> ids) 根據多個主鍵批量刪除數據
  • remove(Collection<T> entities) 根據多個實體(實體需要有主鍵)進行批量刪除
  • remove(T entity) 根據實體條件進行刪除

更新數據

Service 提供了update 多個重載方法

  • update(T entity) 查詢條件 根據實體 ID 更新。不會忽略 null 值
  • update(T entity, Boolean ignore) 查詢條件根據實體 ID 更新。自定義忽略 nul 值
  • update(T entity, Boolean ignore, String[] ignoreProperties) 自定義忽略實體字段屬性
  • update(JPAUpdateClause query) 根據查詢條件來更新數據。
@Test
@Rollback(value = false)
@Transactional
void update() {
    QCategory qCategory = QCategory.category;
    JPAUpdateClause updateWrapper = UpdateWrapper.of(qCategory);
    updateWrapper.set(qCategory.title, "bh").where(qCategory.id.eq(6l));
    Boolean flag = categoryService.update(updateWrapper);
    log.info("更新{}", flag);
}

updateChain

updateChain是一個對 UpdateWrapper 等進行封裝的一個工具類,方便用户用於進行鏈式操作。

@Test
@Rollback(value = false)
@Transactional
void updateChain() {
    QCategory qCategory = QCategory.category;
    categoryService.updateChain(qCategory)
            .set(qCategory.title, "測試jpa")
            .where(qCategory.id.eq(6l)).execute();
}

Simplest-JPA 查詢和分頁查詢

基礎查詢

simplest-jpaService提供瞭如下的功能用於查詢數據庫的數據

  • getById(ID id) 根據主鍵查詢數據。
  • getByIdOpt(ID id)根據主鍵查詢數據。返回Optional類型
  • getOne(Example<T> example) 根據查詢條件來查詢 1 條數據。
  • getOne(Predicate query) 根據查詢條件來查詢 1 條數據。
  • getOneOpt(Example<T> example)根據查詢條件來查詢 1 條數據。返回Optional類型查詢到多條匹配數據時,會拋 NonUniqueResultException
  • listByIds(Collection<ID> ids) 根據數據主鍵查詢數據集合。
  • list(Predicate query)根據查詢條件查詢數據集合。
  • list(Example query) 根據查詢條件查詢數據集合。
  • list()查詢所有數據。
  • count(Predicate query) 根據查詢條件查詢數據數量。
  • count(Example<T> example) 根據查詢條件查詢數據數量。
  • exists(Predicate query) 根據查詢條件判斷數據是否存在。
  • existsById(ID id) 根據 ID 判斷是否存在

分頁查詢

  • page(Pageable page)分頁查詢所有數據。
  • page(Pageable page, Predicate query) 根據查詢條件分頁查詢數據。

鏈式查詢

simplest-jpa 中,內置了 queryChain 和 updateChain 用於對數據進行鏈式查詢操作和鏈式數據操作(修改和刪除)。

  • queryChain:鏈式查詢
  • updateChain:鏈式更新

queryChain 示列

@Test
void selectQueryChain() {
    QCategory qCategory = QCategory.category;
    List<String> categoryList = categoryService.queryChain()
            .select(qCategory.title)
            .from(qCategory)
            .fetch();
    log.info("返回條數{}", categoryList.size());
}

條件查詢

@Test
void selectQueryChainWhere() {
    QCategory qCategory = QCategory.category;
    List<String> categoryList=  categoryService.queryChain()
            .select(qCategory.title)
            .from(qCategory)
            .where(qCategory.id.eq(1l))
            .fetch();
        log.info("返回條數{}", categoryList.size());
}

分頁查詢

@Test
void selectQueryChainWhere() {
    QCategory qCategory = QCategory.category;
    List<String> categoryList = categoryService.queryChain()
            .select(qCategory.title)
            .from(qCategory)
            .where(qCategory.id.eq(1l))
            .limit(1)
            .fetch();
    log.info("返回條數{}", categoryList.size());
}

updateChain 示例

@Test
@Rollback(value = false)
@Transactional
void updateChain() {
    QCategory qCategory = QCategory.category;
    categoryService.updateChain(qCategory)
            .set(qCategory.title, "測試jpa")
            .where(qCategory.id.eq(6l)).execute();
}

queryChain 的方法

  • fetch() 獲取多條數據 懶加載模式
  • fetchAll() 獲取多條數據 忽略懶加載
  • fetchOne() 獲取一條數據 多條會報錯
  • fetchFirst() 查詢第一條數據
  • fetchCount() 查詢數據條數

靈活的 QueryWrapper

在 增刪改 和 查詢和分頁 章節中,我們隨時能看到 QueryWrapper 的身影,QueryWrapper 是用於構造 Sql 的 強有力工具,也是 simplest-jpa 的亮點和特色。

QueryWrapper 的使用

@SpringBootTest
@Slf4j
public class JpaQueryDSLTest {

    @Autowired
    private ICategoryService categoryService;

    @Autowired
    private JPAQueryFactory queryWrapper;


    /**
     * select() 和 fetch() 的常用寫法
     * 使用fetch()查詢時,數據庫沒有符合該條件的數據時,返回的是空集合,而不是null
     */

    /**
     * 查詢字段-select()
     */
    @Test
    public void fetchColum() {
        QCategory qCategory = QCategory.category;
        List<String> a = queryWrapper
                .select(qCategory.title)
                .from(qCategory)
                .fetch();
        log.info("返回數量{}", a.size());
    }

    /**
     * 查詢實體-selectFrom()
     */
    @Test
    public void fetchEntity() {
        QCategory qCategory = QCategory.category;
        List<Category> categories = queryWrapper.selectFrom(qCategory).fetch();
        log.info("返回數量{}", categories.size());

    }

    /**
     * 查詢並將結果封裝至dto中
     */
    @Test
    public void fetchDto() {
        QCategory qCategory = QCategory.category;
        List<CategoryDto> categoryDtos = queryWrapper.select(
                        Projections.bean(CategoryDto.class, qCategory.title)
                )
                .from(qCategory).fetch();
        log.info("返回數量{}", categoryDtos.size());

    }

    /**
     * 去重查詢-selectDistinct()
     */
    @Test
    public void fetchDistinct() {
        QCategory qCategory = QCategory.category;
        List<String> c = queryWrapper
                .selectDistinct(qCategory.title)
                .from(qCategory)
                .fetch();
        log.info("返回數量{}", c.size());
    }

    /**
     * 獲取首個查詢結果-fetchFirst() 單條記錄 limit 1
     */
    @Test
    public void fetchFirst() {
        QCategory qCategory = QCategory.category;
        Category category = queryWrapper
                .selectFrom(qCategory)
                .fetchFirst();
        log.info("返回數量{}", category.toString());
    }

    /**
     * 獲取唯一查詢結果-fetchOne()
     * 當fetchOne()根據查詢條件從數據庫中查詢到多條匹配數據時,會拋`NonUniqueResultException`
     */
    @Test
    public void fetchOne() {
        QCategory qCategory = QCategory.category;
        Category category = queryWrapper
                .selectFrom(qCategory)
                .fetchOne();
        log.info("返回數量{}", category.toString());
    }


    /**
     * where 子句查詢條件的常用寫法
     */
    @Test
    public void fetchWhere() {
        QCategory qCategory = QCategory.category;
        List<Category> categories = queryWrapper
                .selectFrom(qCategory)
                .where(qCategory.title.eq("更新")
                        .and(qCategory.subTitle.like('%' + "測試" + '%')))
                .fetch();
        log.info("返回數量{}", categories.size());

    }

    /**
     * where 動態條件查詢
     */

    /**
     * 使用QueryDSL提供的BooleanBuilder來進行查詢條件管理。
     */
    @Test
    public void fetchWhereDynamic() {
        QCategory qCategory = QCategory.category;
        BooleanBuilder builder = new BooleanBuilder();
        String title = "a";
        if (StrUtil.isNotEmpty(title)) {
            builder.and(qCategory.title.eq(title));
        }
        String subTitle = "";
        if (StrUtil.isNotEmpty(subTitle)) {
            builder.and(qCategory.subTitle.eq(subTitle));
        }
        List<Category> categories = queryWrapper
                .selectFrom(qCategory)
                .where(builder)
                .fetch();
        log.info("返回數量{}", categories.size());

    }

    /**
     * 複雜的查詢關係
     */
    @Test
    public void fetchWhereDynamicComplex() {
        QCategory qCategory = QCategory.category;

        BooleanBuilder builder = new BooleanBuilder();
        builder.or(qCategory.id.eq(1l));

        String title = "a";
        if (StrUtil.isNotEmpty(title)) {
            builder.and(qCategory.title.eq(title));
        }
        String subTitle = "";
        if (StrUtil.isNotEmpty(subTitle)) {
            builder.and(qCategory.subTitle.eq(subTitle));
        }


        List<Category> categories = queryWrapper
                .selectFrom(qCategory)
                .where(builder)
                .fetch();
        log.info("返回數量{}", categories.size());
    }


    /**
     * 自定義封裝查詢的結果集
     * JPAQueryFactory查詢工廠的select方法可以將Projections方法返回的QBean作為參數,通過Projections的bean方法來構建返回的結果集映射到實體內,有點像Mybatis內的ResultMap的形式,不過內部處理機制肯定是有着巨大差別的!
     * <p>
     * bean方法第一個參數需要傳遞一個實體的泛型類型作為返回集合內的單個對象類型,如果QueryDSL查詢實體內的字段與DTO實體的字段名字不一樣時,可以採用as方法來處理,為查詢的結果集指定的字段添加別名,這樣就會自動映射到DTO實體內。
     */

    /**
     * 使用Projections的Bean方法
     */
    @Test
    public void fetchBean() {
        QCategory qCategory = QCategory.category;
        QModule qModule = QModule.module;

        List<CategoryDto> categoryDtos = queryWrapper
                .select(
                        Projections.bean(CategoryDto.class
                                , qCategory.title, qModule.code)
                ).from(qCategory, qModule).fetch();
        log.info("返回數量{}", categoryDtos.size());
    }

    /**
     * 使用Projections的fields方法
     */
    @Test
    public void fetchFields() {
        QCategory qCategory = QCategory.category;
        List<CategoryDto> categoryDtos = queryWrapper
                .select(
                        Projections.fields(CategoryDto.class
                                , qCategory.title)
                ).from(qCategory).fetch();
        log.info("返回數量{}", categoryDtos.size());
    }

    /**
     * 使用集合的stream轉換
     * 從方法開始到fetch()結束完全跟QueryDSL沒有任何區別,採用了最原始的方式進行返回結果集,但是從fetch()獲取到結果集後處理的方式就有所改變了。
     * <p>
     * fetch()方法返回的類型是泛型List(List),List繼承了Collection,完全存在使用Collection內非私有方法的權限,通過調用stream方法可以將集合轉換成Stream泛型對象,該對象的map方法可以操作集合內單個對象的轉換,具體的轉換代碼可以根據業務邏輯進行編寫。
     * <p>
     * 在map方法內有個lambda表達式參數tuple,通過tuple對象get方法就可以獲取對應select方法內的查詢字段。
     * ————————————————
     */
    @Test
    public void selectWithStream() {
        QCategory qCategory = QCategory.category;
        List<CategoryDto> categoryDtos = queryWrapper
                .select(qCategory.title, qCategory.subTitle)
                .from(qCategory)
                .fetch().stream().map(tuple -> {
                    CategoryDto c = new CategoryDto();
                    c.setTitle(tuple.get(qCategory.title));
                    return c;
                }).collect(Collectors.toList());

        log.info("返回數量{}", categoryDtos.size());
    }


    @Test
    public void findByQuery() {
        QCategory qCategory = QCategory.category;
        //該Predicate為querydsl下的類,支持嵌套組裝複雜查詢條件
        BooleanBuilder builder = new BooleanBuilder();
        String title = "a";
        if (StrUtil.isNotEmpty(title)) {
            builder.and(qCategory.title.eq(title));
        }
        String subTitle = "";
        if (StrUtil.isNotEmpty(subTitle)) {
            builder.and(qCategory.subTitle.eq(subTitle));
        }
        List<Category> c = categoryService.list(builder);
       log.info("條數{}",c.size());
    }

    @Test
    public void findByQueryWrapper(){
        QCategory qCategory = QCategory.category;
        JPAQueryFactory queryWrapper=QueryWrapper.of();
        List<String> c = queryWrapper
                .selectDistinct(qCategory.title)
                .from(qCategory)
                .fetch();
        log.info("返回數量{}", c.size());
    }

}

實例

  1. 單表查詢
@Service
@Transactional
public class UserService {

    @Autowired
    private JPAQueryFactory queryFactory;

    /**
     * attention:
     * Details:查詢user表中的所有記錄
     */
    public List<User> findAll(){
        QUser quser = QUser.user;
        return queryFactory.selectFrom(quser)
                    .fetch();
    }

    /**
     * Details:單條件查詢
     */
    public User findOneByUserName(final String userName){
        QUser quser = QUser.user;
        return queryFactory.selectFrom(quser)
            .where(quser.name.eq(userName))
            .fetchOne();
    }

    /**
     * Details:單表多條件查詢
     */
    public User findOneByUserNameAndAddress(final String userName, final String address){
        QUser quser = QUser.user;
        return queryFactory.select(quser)
            .from(quser) // 上面兩句代碼等價與selectFrom
            .where(quser.name.eq(userName).and(quser.address.eq(address)))// 這句代碼等同於where(quser.name.eq(userName), quser.address.eq(address))
            .fetchOne();
    }

    /**
     * Details:使用join查詢
     */
    public List<User> findUsersByJoin(){
        QUser quser = QUser.user;
        QUser userName = new QUser("name");
        return queryFactory.selectFrom(quser)
            .innerJoin(quser)
            .on(quser.id.intValue().eq(userName.id.intValue()))
            .fetch();
    }

    /**
     * Details:將查詢結果排序
     */
    public List<User> findUserAndOrder(){
        QUser quser = QUser.user;
        return queryFactory.selectFrom(quser)
            .orderBy(quser.id.desc())
            .fetch();
    }

    /**
     * Details:Group By使用
     */
    public List<String> findUserByGroup(){
        QUser quser = QUser.user;
        return queryFactory.select(quser.name)
                    .from(quser)
                    .groupBy(quser.name)
                    .fetch();
    }

    /**
     * Details:刪除用户
     */
    public long deleteUser(String userName){
        QUser quser = QUser.user;
        return queryFactory.delete(quser).where(quser.name.eq(userName)).execute();
    }

    /**
     * Details:更新記錄
     */
    public long updateUser(final User u, final String userName){
        QUser quser = QUser.user;
        return queryFactory.update(quser).where(quser.name.eq(userName))
            .set(quser.name, u.getName())
            .set(quser.age, u.getAge())
            .set(quser.address, u.getAddress())
            .execute();
    }

    /**
     * Details:使用原生Query
     */
    public User findOneUserByOriginalSql(final String userName){
        QUser quser = QUser.user;
        Query query = queryFactory.selectFrom(quser)
                .where(quser.name.eq(userName)).createQuery();
        return (User) query.getSingleResult();
    }


    /**
     *分頁查詢所有的實體,根據uIndex字段排序
     *
     * @return
     */
    public QueryResults<User> findAllPage(Pageable pageable) {
        QUser user = QUser.user;
        return jpaQueryFactory
                .selectFrom(user)
                .orderBy(user.uIndex.asc())
                .offset(pageable.getOffset())   //偏移量,計算:offset = ( 當前頁 - 1) * 每頁條數,這裏直接使用的是Pageable中的Offset
                .limit(pageable.getPageSize())  //每頁大小
                .fetchResults();    //獲取結果,該結果封裝了實體集合、分頁的信息,需要這些信息直接從該對象裏面拿取即可
    }


    /**
     * 部分字段映射查詢
     * 投影為UserRes,lambda方式(靈活,類型可以在lambda中修改)
     *
     * @return
     */
    public List<UserDTO> findAllUserDto(Pageable pageable) {
        QUser user = QUser.user;
        List<UserDTO> dtoList = jpaQueryFactory
                .select(
                        user.username,
                        user.userId,
                        user.nickName,
                        user.birthday
                )
                .from(user)
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch()
                .stream()
                .map(tuple -> UserDTO.builder()
                        .username(tuple.get(user.username))
                        .nickname(tuple.get(user.nickName))
                        .userId(tuple.get(user.userId).toString())
                        .birthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tuple.get(user.birthday)))
                        .build()
                )
                .collect(Collectors.toList());
        return dtoList;
    }


    /**
     * 部分字段映射查詢
     * 投影為UserRes,自帶的Projections方式,不能轉換類型,但是可以使用as轉換名字
     *
     * @return
     */
    public List<UserDTO> findAllDto2() {
        QUser user = QUser.user;
        List<UserDTO> dtoList = jpaQueryFactory
                .select(
                        Projections.bean(
                                UserDTO.class,
                                user.username,
                                user.userId,
                                user.nickName,
                                user.birthday
                        )
                )
                .from(user)
                .fetch();
        return dtoList;
    }


}
  1. 多表查詢


/**
 * @Description 查詢全部
 * @Author 程序員三時
 * @Date  10:53
 * @return java.util.List<com.cs.querydsl.model.Loc>
 **/
@Override
public List<Loc> findAll(Loc loc) {
    // 使用 QueryDSL 進行查詢
    QLoc qLoc = QLoc.loc1;
    QUser qUser = QUser.user;
    // 定於獲取條件
    BooleanBuilder booleanBuilder = new BooleanBuilder();
    // 要查詢的條件
    if(!StringUtils.isEmpty(loc.getLoc())){
        // 放入要查詢的條件信息
        booleanBuilder.and(qLoc.loc.contains(loc.getLoc()));
    }
    //連接查詢條件(Loc.id = User.id )
    booleanBuilder.and(qLoc.id.eq(qUser.id));
    // 使用 QueryDSL 進行多表聯合查詢
    QueryResults<Tuple> listResult = queryFactory
            .select(QLoc.loc1,QUser.user)
            .from(qLoc, qUser)//查詢兩表
            .where(booleanBuilder)
            .fetchResults();
    //遍歷 java8 自帶流轉換成集合
    List<Loc> collect = listResult.getResults().stream().map(tuple -> {
        Loc lcs = tuple.get(qLoc);
        return lcs;
    }).collect(Collectors.toList());
    return collect;
}



部分字段映射的投影查詢:

當使用`@ManyToOne`、`@ManyToMany`建立關聯時:

/**
 * 根據部門的id查詢用户的基本信息+用户所屬部門信息,並且使用UserDeptDTO進行封裝返回給前端展示
 * @param departmentId
 * @return
 */
public List<UserDeptDTO> findByDepatmentIdDTO(int departmentId) {
    QUser user = QUser.user;
    QDepartment department = QDepartment.department;
    //直接返回
    return jpaQueryFactory
            //投影只去部分字段
            .select(
                    user.username,
                    user.nickName,
                    user.birthday,
                    department.deptName,
                    department.createDate

            )
            .from(user)
            //聯合查詢
            .join(user.department, department)
            .where(department.deptId.eq(departmentId))
            .fetch()
            //lambda開始
            .stream()
            .map(tuple ->
                    //需要做類型轉換,所以使用map函數非常適合
                    UserDeptDTO.builder()
                            .username(tuple.get(user.username))
                            .nickname(tuple.get(user.nickName))
                            .birthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tuple.get(user.birthday)))
                            .deptName(tuple.get(department.deptName))
                            .deptBirth(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tuple.get(department.createDate)))
                            .build()
            )
            .collect(Collectors.toList());
}


當使用id建立關聯時:
/**
 * 根據部門的id查詢用户的基本信息+用户所屬部門信息,並且使用UserDeptDTO進行封裝返回給前端展示
 *
 * @param departmentId
 * @return
 */
public List<UserDeptDTO> findByDepatmentIdDTO(int departmentId) {
    QUser user = QUser.user;
    QDepartment department = QDepartment.department;
    //直接返回
    return jpaQueryFactory
            //投影只去部分字段
            .select(
                    user.username,
                    user.nickName,
                    user.birthday,
                    department.deptName,
                    department.createDate

            )
            .from(user, department)
            //聯合查詢
            .where(
                    user.departmentId.eq(department.deptId).and(department.deptId.eq(departmentId))
            )
            .fetch()
            //lambda開始
            .stream()
            .map(tuple ->
                    //需要做類型轉換,所以使用map函數非常適合
                    UserDeptDTO.builder()
                            .username(tuple.get(user.username))
                            .nickname(tuple.get(user.nickName))
                            .birthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tuple.get(user.birthday)))
                            .deptName(tuple.get(department.deptName))
                            .deptBirth(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(tuple.get(department.createDate)))
                            .build()
            )
            .collect(Collectors.toList());
}


使用 Projections 自定義返回 Bean:

/**
 * Details:方式一:使用Bean投影
 */
public List<PersonIDCardDto> findByDTOUseBean(){
    Predicate predicate = (QPerson.person.id.intValue()).eq(QIDCard.iDCard.person.id.intValue());
    return queryFactory.select(
            Projections.bean(PersonIDCardDto.class, QIDCard.iDCard.idNo, QPerson.person.address, QPerson.person.name))
            .from(QIDCard.iDCard, QPerson.person)
            .where(predicate)
            .fetch();
}

/**
 * Details:方式二:使用fields來代替setter
 */
public List<PersonIDCardDto> findByDTOUseFields(){
    Predicate predicate = (QPerson.person.id.intValue()).eq(QIDCard.iDCard.person.id.intValue());
    return queryFactory.select(
            Projections.fields(PersonIDCardDto.class, QIDCard.iDCard.idNo, QPerson.person.address, QPerson.person.name))
            .from(QIDCard.iDCard, QPerson.person)
            .where(predicate)
            .fetch();
}

/**
 * Details:方式三:使用構造方法,注意構造方法中屬性的順序必須和構造器中的順序一致
 */
public List<PersonIDCardDto> findByDTOUseConstructor(){
    Predicate predicate = (QPerson.person.id.intValue()).eq(QIDCard.iDCard.person.id.intValue());
    return queryFactory.select(
            Projections.constructor(PersonIDCardDto.class, QPerson.person.name, QPerson.person.address, QIDCard.iDCard.idNo))
            .from(QIDCard.iDCard, QPerson.person)
            .where(predicate)
            .fetch();
}
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.