rest-apiV2.0.0升级为simplest-api开源框架生态之simplest-jpa发布

kenx
• 阅读 174

什么是 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 编译项目。 rest-apiV2.0.0升级为simplest-api开源框架生态之simplest-jpa发布

会在你指定目录生成对应查询EntityPaht实体

rest-apiV2.0.0升级为simplest-api开源框架生态之simplest-jpa发布

简单查询

  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();
}
点赞
收藏
评论区
推荐文章
雷厉风行 雷厉风行
1年前
Mac程序员开发必备-MAMP PRO for Mac 自带激活版-PHP+Mysql 开发集成环境,本地站点
MAMPPROforMac是一款集成了PHP和MYSQL的开发环境,可以帮助Mac程序员快速搭建本地站点,进行开发和测试,并且自带激活版,非常方便。它提供了一个稳定的环境,方便程序员进行代码开发和调试,同时还支持多种应用程序和框架,满足不同程序员的开发需求。
Easter79 Easter79
2年前
springboot2之优雅处理返回值
前言最近项目组有个老项目要进行前后端分离改造,应前端同学的要求,其后端提供的返回值格式需形如{"status":0,"message":"success","data":{}}方便前端数据处理。要实现前端同学这个需求,其实也挺简单的,
Stella981 Stella981
2年前
JUnit学习笔记
JUnitJJUnit是用于编写和运行可重复的自动化测试的开源测试框架,这样可以保证我们的代码按预期工作。JUnit可广泛用于工业和作为支架(从命令行)或IDE(如Eclipse)内单独的Java程序。基础知识JUnit的安装和使用都非常的简单。这里使用IDEAMaven演示。创建项目使用Ide
Easter79 Easter79
2年前
Spring面试题总结
1、Spring是什么?Spring是一个轻量级的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于java的配置。主要由以下几个模块组成:SpringCore:核心类库,提供IOC服务;
Easter79 Easter79
2年前
SpringBoot开发及学习
SpringBoot是Spring新出的一个框架,他的目的一如始初简化开发。我们开发项目的时候,为了让项目运行起来,我们要考虑很多架构、配置、依赖等问题,这些问题其实每个项目都要考虑,而且每个项目的开发都有固定的模版,这些重复的工作是每个项目的样板代码,SpringBoot做的就是帮我们完成这些重复行的工作,让我们只关注业务逻辑。主要帮我们完成了以下
Stella981 Stella981
2年前
SpringBoot开发及学习
SpringBoot是Spring新出的一个框架,他的目的一如始初简化开发。我们开发项目的时候,为了让项目运行起来,我们要考虑很多架构、配置、依赖等问题,这些问题其实每个项目都要考虑,而且每个项目的开发都有固定的模版,这些重复的工作是每个项目的样板代码,SpringBoot做的就是帮我们完成这些重复行的工作,让我们只关注业务逻辑。主要帮我们完成了以下
Stella981 Stella981
2年前
Spring Boot实践教程:开篇
前言  Java项目开发Spring应该是最常被用到的框架了,但是老式的配置方式让人觉得特别的繁琐,虽然可以通过注解去简化xml文件的配置,但是有没有更简单的方式来帮我们完成这些重复性的事情呢?于是SpringBoot就出现了,SpringBoot极大的简化了Spring的应用开发,它采用约定优于配置的方式,让开发人员能够快速的搭建起项目并运行
kenx kenx
9个月前
我开源了团队内部基于SpringBoot Web快速开发的API脚手架v1.6.0更新
什么是restapispringbootstarterrestapispringbootstarter适用于SpringBootWebAPI快速构建让开发人员快速构建统一规范的业务RestFullAPI不在去关心一些繁琐。重复工作,而是把重点聚焦到业务。动
臧霸 臧霸
1个月前
SpringBoot+Vue3+MySQL集群 开发健康体检双系统
开发健康体检双系统需要将后端和前端技术整合起来,并且要考虑到数据库的集群部署。以下是可能涉及的一些关键技术和步骤:1.后端技术选型:SpringBoot:作为后端的主要框架,提供了快速开发的能力和丰富的生态系统。MySQL数据库集群:选择MySQL数据库,
臧霸 臧霸
1个月前
SpringCloudalibaba+Vue开发仿社交小程序|完结无密
这个项目结合了后端的SpringCloudAlibaba和前端的Vue框架,用于开发一个仿社交小程序。下面是一些可能涉及到的功能和技术点:一、用户系统:1.使用SpringSecurity实现用户认证和授权。2.用户注册、登录、密码找回等功能。3.用户信息