jpa的好伙伴QueryDSL快速入门

Querydsl定义了一种常用的静态类型语法,用于在持久域模型数据之上进行查询。JDO和JPA是Querydsl的主要集成技术。本文旨在介绍如何使用Querydsl与JPA组合使用。JPA的Querydsl是JPQL和Criteria查询的替代方法。QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查询。

一、准备工作

1、引依赖

querydsl 相关jar包


<dependency>
    <groupId>com.querydslgroupId>
    <artifactId>querydsl-jpaartifactId>
dependency>
<dependency>
    <groupId>com.querydslgroupId>
    <artifactId>querydsl-aptartifactId>
    <scope>providedscope>
dependency>

编译插件:

<plugin>
    <groupId>com.mysema.mavengroupId>
    <artifactId>apt-maven-pluginartifactId>
    <version>1.1.3version>
    <executions>
        <execution>
            <goals>
                <goal>processgoal>
            goals>
            <configuration>
                <outputDirectory>target/generated-sources/javaoutputDirectory>
                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessorprocessor>
            configuration>
        execution>
    executions>
plugin>

该插件会 查找使用javax.persistence.Entity注解的域类型,并为它们生成对应的查询类型,生成地址对应: 配置

生成命令:

mvn clean complie

2、让 Spring管理 JPAQueryFactory

使用QueryDSL的功能时,会依赖使用到JPAQueryFactory,而JPAQueryFactory在这里依赖使用EntityManager,所以在主类中做如下配置,使得Spring自动帮我们注入EntityManager与自动管理JPAQueryFactory:

@SpringBootApplication
public class App {
 
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
    //让Spring管理JPAQueryFactory
    @Bean
    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager){
        return new JPAQueryFactory(entityManager);
    }
}

二、案例

1、JpaSpecificationExecutor

直接让你的Repository继承QueryDslPredicateExecutor即可,使用的话和继承JpaRepository差不太多,我这就不讲了,本文主要讲解JPAQueryFactory。

QueryDslPredicateExecutor下面主要有这几个方法:

public interface QueryDslPredicateExecutor<T> {
    T findOne(Predicate var1);
 
    Iterable<T> findAll(Predicate var1);
 
    Iterable<T> findAll(Predicate var1, Sort var2);
 
    Iterable<T> findAll(Predicate var1, OrderSpecifier... var2);
 
    Iterable<T> findAll(OrderSpecifier... var1);
 
    Page<T> findAll(Predicate var1, Pageable var2);
 
    long count(Predicate var1);
 
    boolean exists(Predicate var1);
}

这里面的方法大多数都是可以传入Predicate类型的参数,说明还是围绕着QUser来进行操作的,如传入quser.username.eq(“123”)的方式,操作都非常简单,如下:

public User findUserByUserName(final String userName){
    /**
     * 该例是使用spring data QueryDSL实现
     */
    QUser quser = QUser.user;
    Predicate predicate = quser.name.eq(userName);// 根据用户名,查询user表
    return repository.findOne(predicate);
}

2、JPAQueryFactory

单表

package com.chhliu.springboot.jpa.service;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.transaction.Transactional;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
 
import com.chhliu.springboot.jpa.entity.QUser;
import com.chhliu.springboot.jpa.entity.User;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
 

@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;
    }


}

多表



/**
 * @Description 查询全部
 * @Author LinLuoChen
 * @Date  10:53
 * @return java.util.List
 **/
@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();
}

三、我的个人建议

分情况使用:

  • 增删改:直接使用 JPA Repository 实现即可

  • 查询

    • 单表:

      • 有动态筛选条件:使用 QueryDSL

      • 无动态条件:使用 JPA Repository、QueryDSL均可。

    • 多表:有无动态条件均使用 QueryDSL

      JpaSpecificationExecutor也可以实现动态条件查询,但是使用起来过于繁琐,不推荐使用。

四、其他

聚合函数、子查询、Template等进阶使用看这里:https://www.jianshu.com/p/69dcb1b85bbb

你可能感兴趣的:(Java,SpringBoot,java,QueryDSL,JPA)