工作需要,接触到了QueryDSL,总结使用方法,参考了几个大佬的文章,对我帮助很大感谢大佬们!!!

使用场景

  1. QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查询。
  2. Querydsl可以通过一组通用的查询API为用户构建出适合不同类型ORM框架或者是SQL的查询语句,也就是说QueryDSL是基于各种ORM框架以及SQL之上的一个通用的查询框架。
  3. 借助QueryDSL可以在任何支持的ORM框架或者SQL平台上以一种通用的API方式来构建查询。目前QueryDSL支持的平台包括
    JPA,JDO,SQL,Java,Collections,RDF,Lucene,Hibernate Search。

QueryDSL官网:http://querydsl.com/static/querydsl/4.1.3/reference/html_single/

一些概念

  1. EntityManager:
    在 JPA 规范中, EntityManager 是完成持久化操作的核心对象。实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。
    EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。
  2. @PersistenceContext
    Persistence context是由一组受托管的实体对象实例所构成的集合。它受entity manager 的管理。Entity manager追踪persistence context中所有对象的修改和更新情况,并根据指定的flush模式(本章稍后会做讨论)将这些修改保存到数据库中。一旦persistence context被关闭,所有实体对象实例都会脱离EntityManager而成为非托管对象。对象一旦从persistence context中脱离,就不再受entity manager管理了,任何对此对象的状态变更也将不会被同步到数据库。

使用方法

pom文件的导入

<!-- queryDSL -->
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
    </dependency>
    <!-- queryDSL -->
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <scope>provided</scope>
    </dependency>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>

        <!--因为是类型安全的,所以还需要加上Maven APT plugin,使用 APT 自动生成一些类:-->
        <plugin>
            <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</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                    </configuration>
                </execution>
            </executions>
        </plugin>

    </plugins>
</build>

下面是两个大佬的文章里给出的例子,根据例子学习非常快,再次感谢大佬们!!!

例1
  1. 创建实体类
@Entity
@Getter
@Setter
@ToString
@Accessors(fluent = true)
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "t_user")
public class User extends IdEntity {
    /** 用户名 */
    private String username;

    /** 密码 */
    private String password;

    /** 性别 */
    private String sex;

    /** 年龄 */
    private String age;

    /** 地址 */
    private String address;

    /** 角色id */
    private Long roleId;
}
  1. 创建repository接口
@Repository
public interface UserRepository extends JpaRepository<User,Long>, QuerydslPredicateExecutor<User> {}
  1. 生成Q类 (举例: user实体生成实体后会有一个QUser的类,该框架操作的是QUser类,而不是直接的是实体类)

IDEA 的Maven面板,项目生命周期里重新编译项目,会自动生成Q实体类

  1. 创建service接口实现类
@Service
@Transactional
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @PersistenceContext
    private EntityManager entityManager;

    /**
     * 添加用户
     *
     * @return user
     */
    @Override
    public User saveUser() {
        User address = new User().username("admin").password("123456").age("12").sex("男").address("山西省");
        return userRepository.save(address);
    }

    /**
     * 通过id查询
     *
     * @param id 数据id
     * @return Optional<User>
     */
    @Override
    public Optional<User> findById(Long id) {
        QUser qUser = QUser.user;
        BooleanExpression eq = qUser.id.eq(id);
        return userRepository.findOne(eq);
    }

    /**
     * 查询所有
     *
     * @return List<RoUser>
     */
    @Override
    public List<RoUser> findAll() {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QUser qUser = QUser.user;
        QRole qRole = QRole.role;

        QBean<RoUser> bean = Projections.bean(
                RoUser.class,//返回自定义实体的类型
                qUser.id,
                qUser.username,
                qUser.password,
                qUser.sex,
                qUser.age,
                qUser.address,
                qRole.roleName,
                qRole.remark
        );
        // 返回分页参数的信息
        queryFactory
                .select(bean) // 返回自定义实体
                .from(qUser) // 主表
                .leftJoin(qRole) // 从表
                .on(
                        qUser.roleId.eq(qRole.id) // on 条件
                ).where(qUser.id.eq(2L)) // where 条件
                .orderBy(qUser.createTime.desc()) // 排序
                .offset(1).limit(10)    // 分页
                .fetchResults();

        return queryFactory
                .select(bean) // 返回自定义实体
                .from(qUser) // 主表
                .leftJoin(qRole) // 从表
                .on(
                        qUser.roleId.eq(qRole.id) // on 条件
                ).where(qUser.id.eq(2L)) // where 条件
                .fetch();
    }

    /**
     * 动态查询 + 子查询 + 分页 + 排序
     *
     * @return List<RoUser>
     */
    @Override
    public QueryResults<RoUser> findAll(String username, Long roleId) {
        JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
        QUser qUser = QUser.user;
        QRole qRole = QRole.role;

        QBean<RoUser> bean = Projections.bean(
                RoUser.class,//返回自定义实体的类型
                qUser.id,
                qUser.username,
                qUser.password,
                qUser.sex,
                qUser.age,
                qUser.address,
                qRole.roleName,
                qRole.remark
        );
        // 定义返回类型
        JPAQuery<RoUser> from = queryFactory
                .select(bean) // 返回自定义实体
                .from(qUser);// 主表

        // 模糊查询名字
        if (StringUtils.isNotBlank(username)) {
            from.where(qUser.username.like("%" + username + "%"));
        }
        if (null != roleId) {
            from.where(qUser.roleId.eq(roleId));
        }
        return from
                .leftJoin(qRole)
                .on(qUser.roleId.eq(qRole.id))  // on 子查询
                .orderBy(qUser.createTime.desc())   // 排序
                .offset(0) // 起始页
                .limit(10)  // 限制条数
                .fetchResults();
    }
}
例2
一. 实体类
  1. 城市类
@Entity  
@Table(name = "t_city", schema = "test", catalog = "")  
public class TCity {  
    //省略JPA注解标识  
    private int id;  
    private String name;  
    private String state;  
    private String country;  
    private String map;  
}
  1. 旅馆类
@Entity  
@Table(name = "t_hotel", schema = "test", catalog = "")  
public class THotel {  
    //省略JPA注解标识  
    private int id;  
    private String name;  
    private String address;  
    private Integer city;//保存着城市的id主键  
}
二. 单表动态分页查询

Spring Data JPA中提供了QueryDslPredicateExecutor接口,用于支持QueryDSL的查询操作

public interface tCityRepository extends JpaRepository<TCity, Integer>, QueryDslPredicateExecutor<TCity> {}

这样的话单表动态查询就可以参考如下代码:

//查找出Id小于3,并且名称带有`shanghai`的记录.  
  
//动态条件  
QTCity qtCity = QTCity.tCity; //SDL实体类  
//该Predicate为querydsl下的类,支持嵌套组装复杂查询条件  
Predicate predicate = qtCity.id.longValue().lt(3).and(qtCity.name.like("shanghai"));  
//分页排序  
Sort sort = new Sort(new Sort.Order(Sort.Direction.ASC,"id"));  
PageRequest pageRequest = new PageRequest(0,10,sort);  
//查找结果  
Page<TCity> tCityPage = tCityRepository.findAll(predicate,pageRequest);
三. 多表动态查询

QueryDSL对多表查询提供了一个很好地封装,看下面代码:

/** 
* 关联查询示例,查询出城市和对应的旅店 
* @param predicate 查询条件 
* @return 查询实体 
*/  
@Override  
public List<Tuple> findCityAndHotel(Predicate predicate) {  
	JPAQueryFactory queryFactory = new JPAQueryFactory(em);  
	JPAQuery<Tuple> jpaQuery = queryFactory.select(QTCity.tCity,QTHotel.tHotel)  
	                                .from(QTCity.tCity)  
	                                .leftJoin(QTHotel.tHotel)  
	                                .on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()));  
	//添加查询条件  
	jpaQuery.where(predicate);  
	//拿到结果  
	return jpaQuery.fetch();  
}

城市表左连接旅店表,当该旅店属于这个城市时查询出两者的详细字段,存放到一个Tuple的多元组中.相比原生sql,简单清晰了很多.
那么该怎么调用这个方法呢?

@Test  
    public void findByLeftJoin(){  
        QTCity qtCity = QTCity.tCity;  
        QTHotel qtHotel = QTHotel.tHotel;  
        //查询条件  
        Predicate predicate = qtCity.name.like("shanghai");  
        //调用  
        List<Tuple> result = tCityRepository.findCityAndHotel(predicate);  
        //对多元组取出数据,这个和select时的数据相匹配  
        for (Tuple row : result) {  
            System.out.println("qtCity:"+row.get(qtCity));  
            System.out.println("qtHotel:"+row.get(qtHotel));  
            System.out.println("--------------------");  
        }  
        System.out.println(result);  
    }

这样做的话避免了返回Object[]数组,下面是自动生成的sql语句:

select  
    tcity0_.id as id1_0_0_,  
    thotel1_.id as id1_1_1_,  
    tcity0_.country as country2_0_0_,  
    tcity0_.map as map3_0_0_,  
    tcity0_.name as name4_0_0_,  
    tcity0_.state as state5_0_0_,  
    thotel1_.address as address2_1_1_,  
    thotel1_.city as city3_1_1_,  
    thotel1_.name as name4_1_1_   
from  
    t_city tcity0_   
left outer join  
    t_hotel thotel1_   
        on (  
            cast(thotel1_.city as signed)=cast(tcity0_.id as signed)  
        )   
where  
    tcity0_.name like ? escape '!'
四 多表动态分页查询

分页查询对于queryDSL无论什么样的sql只需要写一遍,会自动转换为相应的count查询,下面代码是对上面的查询加上分页功能:

@Override  
public QueryResults<Tuple> findCityAndHotelPage(Predicate predicate,Pageable pageable) {  
   JPAQueryFactory queryFactory = new JPAQueryFactory(em);  
   JPAQuery<Tuple> jpaQuery = queryFactory.select(QTCity.tCity.id,QTHotel.tHotel)  
                                              .from(QTCity.tCity)  
                                              .leftJoin(QTHotel.tHotel)  
                                              .on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()))  
                                              .where(predicate)  
                                              .offset(pageable.getOffset())  
                                              .limit(pageable.getPageSize());  
   //拿到分页结果  
   return jpaQuery.fetchResults();  
}

和上面不同之处在于这里使用了offset和limit限制查询结果,并且返回一个QueryResults。该类会自动实现count查询和结果查询,并进行封装。
调用形式如下:

@Test  
public void findByLeftJoinPage(){  
    QTCity qtCity = QTCity.tCity;  
    QTHotel qtHotel = QTHotel.tHotel;  
    //条件  
    Predicate predicate = qtCity.name.like("shanghai");  
    //分页  
    PageRequest pageRequest = new PageRequest(0,10);  
    //调用查询  
    QueryResults<Tuple> result = tCityRepository.findCityAndHotelPage(predicate,pageRequest);  
    //结果取出  
    for (Tuple row : result.getResults()) {  
        System.out.println("qtCity:"+row.get(qtCity));  
        System.out.println("qtHotel:"+row.get(qtHotel));  
        System.out.println("--------------------");  
    }  
    //取出count查询总数  
    System.out.println(result.getTotal());  
}

生成的原生count查询sql,当该count查询结果为0的话,则直接返回,并不会再进行具体数据查询:

select  
    count(tcity0_.id) as col_0_0_   
from  
    t_city tcity0_   
left outer join  
    t_hotel thotel1_   
        on (  
            cast(thotel1_.city as signed)=cast(tcity0_.id as signed)  
        )   
where  
    tcity0_.name like ? escape '!'

生成的原生查询sql:

select  
    tcity0_.id as id1_0_0_,  
    thotel1_.id as id1_1_1_,  
    tcity0_.country as country2_0_0_,  
    tcity0_.map as map3_0_0_,  
    tcity0_.name as name4_0_0_,  
    tcity0_.state as state5_0_0_,  
    thotel1_.address as address2_1_1_,  
    thotel1_.city as city3_1_1_,  
    thotel1_.name as name4_1_1_   
from  
    t_city tcity0_   
left outer join  
    t_hotel thotel1_   
        on (  
            cast(thotel1_.city as signed)=cast(tcity0_.id as signed)  
        )   
where  
    tcity0_.name like ? escape '!' limit ?

查看打印,可以发现对应的city也都是同一个对象,hotel是不同的对象。