SpringBoot环境下QueryDSL-JPA的入门及进阶

转载自:https://www.jianshu.com/p/69dcb1b85bbb

一、环境配置

1. 引入maven依赖

        
        
            com.querydsl
            querydsl-jpa
        
                
            com.querydsl
            querydsl-apt
            provided
        

2. 添加maven插件

添加这个插件是为了让程序自动生成query type(查询实体,命名方式为:"Q"+对应实体名)。
上文引入的依赖中querydsl-apt即是为此插件服务的。

注:在使用过程中,如果遇到query type无法自动生成的情况,用maven更新一下项目即可解决(右键项目->Maven->Update Project)。

           
               com.mysema.maven
               apt-maven-plugin
               1.1.3
               
                   
                       
                           process
                       
                       
                           
                               target/generatedsources/java
                           
                           
                               com.querydsl.apt.jpa.JPAAnnotationProcessor
                           
                       
                   
               
            

二、使用

在Spring环境下,我们可以通过两种风格来使用QueryDSL。

一种是使用JPAQueryFactory的原生QueryDSL风格,
另一种是基于Spring Data提供的QueryDslPredicateExecutor的Spring-data风格。

使用QueryDslPredicateExecutor可以简化一些代码,使得查询更加优雅。
JPAQueryFactory的优势则体现在其功能的强大,支持更复杂的查询业务。甚至可以用来进行更新和删除操作。

下面分别介绍两种风格的使用方式。

1. JPAQueryFactory

JPAQueryFactory使用逻辑类似于HQL/SQL语法,不再额外说明。
QueryDSL在支持JPA的同时,也提供了对Hibernate的支持。可以通过HibernateQueryFactory来使用。

装配

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

注入

    @Autowired
    JPAQueryFactory queryFactory;

1.1 更新/删除

Update

QMemberDomain qm = QMemberDomain.memberDomain;
queryFactory.update(qm).set(qm.status, "0012").where(qm.status.eq("0011")).execute();

Delete

QMemberDomain qm = QMemberDomain.memberDomain;
queryFactory.delete(qm).where(qm.status.eq("0012")).execute();

1.2 查询

查询简直可以玩出花来。

1.2.1 select()和fetch()的几种常用写法

QMemberDomain qm = QMemberDomain.memberDomain;
//查询字段-select()
List nameList = queryFactory.select(qm.name).from(qm).fetch();
//查询实体-selectFrom()
List memberList = queryFactory.selectFrom(qm).fetch();
//查询并将结果封装至dto中
List dtoList = queryFactory.select(Projections.constructor(MemberFavoriteDto.class,qm.name,qf.favoriteStoreCode)).from(qm).leftJoin(qm.favoriteInfoDomains,qf).fetch();
//去重查询-selectDistinct()
List distinctNameList = queryFactory.selectDistinct(qm.name).from(qm).fetch();
//获取首个查询结果-fetchFirst()
MemberDomain firstMember = queryFactory.selectFrom(qm).fetchFirst();
//获取唯一查询结果-fetchOne()
//当fetchOne()根据查询条件从数据库中查询到多条匹配数据时,会抛`NonUniqueResultException`。
MemberDomain anotherFirstMember = queryFactory.selectFrom(qm).fetchOne();

1.2.2 where子句查询条件的几种常用写法

        //查询条件示例
        List memberConditionList = queryFactory.selectFrom(qm)
                //like示例
                .where(qm.name.like('%'+"Jack"+'%')
                        //contain示例
                        .and(qm.address.contains("厦门"))
                        //equal示例
                        .and(qm.status.eq("0013"))
                        //between
                        .and(qm.age.between(20, 30)))               
                .fetch();

1.2.3 多表查询

//以左关联为例-left join
QMemberDomain qm = QMemberDomain.memberDomain;
QFavoriteInfoDomain qf= QFavoriteInfoDomain.favoriteInfoDomain;
List leftJoinList = queryFactory.selectFrom(qm).leftJoin(qm.favoriteInfoDomains,qf).where(qf.favoriteStoreCode.eq("0721")).fetch();

1.2.4 使用Mysql聚合函数

//聚合函数-avg()
Double averageAge = queryFactory.select(qm.age.avg()).from(qm).fetchOne();

//聚合函数-concat()
String concat = queryFactory.select(qm.name.concat(qm.address)).from(qm).fetchOne();

//聚合函数-date_format()
String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", qm.registerDate)).from(qm).fetchOne();

当用到DATE_FORMAT这类QueryDSL似乎没有提供支持的Mysql函数时,我们可以手动拼一个String表达式。这样就可以无缝使用Mysql中的函数了。

1.2.5 使用子查询

下面的用法中子查询没有什么实际意义,只是作为一个写法示例。

//子查询
List subList = queryFactory.selectFrom(qm).where(qm.status.in(JPAExpressions.select(qm.status).from(qm))).fetch();

1.2.6 排序

//排序
List orderList = queryFactory.selectFrom(qm).orderBy(qm.name.asc()).fetch();

1.2.7 分页的两种写法

        QMemberDomain qm = QMemberDomain.memberDomain;
        //写法一
        JPAQuery query = queryFactory.selectFrom(qm).orderBy(qm.age.asc());
        long total = query.fetchCount();//hfetchCount的时候上面的orderBy不会被执行
        List list0= query.offset(2).limit(5).fetch();
        //写法二
        QueryResults results =                   
        queryFactory.selectFrom(qm).orderBy(qm.age.asc()).offset(2).limit(5)
        .fetchResults();
        List list = results.getResults();
        logger.debug("total:"+results.getTotal());
        logger.debug("limit:"+results.getLimit());
        logger.debug("offset:"+results.getOffset());

写法一和二都会发出两条sql进行查询,一条查询count,一条查询具体数据。
写法二的getTotal()等价于写法一的fetchCount
无论是哪种写法,在查询count的时候,orderBy、limit、offset这三个都不会被执行。可以大胆使用。

1.2.8 使用Template实现QueryDSL未支持的语法

其实Template我们在1.2.4 使用Mysql聚合函数中已经使用过了。QueryDSL并没有对Mysql的所有函数提供支持,好在它给我们提供了Template特性。我们可以使用Template来实现各种QueryDSL未直接支持的语法。
示例如下。

QMemberDomain qm = QMemberDomain.memberDomain;
        //使用booleanTemplate充当where子句或where子句的一部分
        List list = queryFactory.selectFrom(qm).where(Expressions.booleanTemplate("{} = \"tofu\"", qm.name)).fetch();
        //上面的写法,当booleanTemplate中需要用到多个占位时
        List list1 = queryFactory.selectFrom(qm).where(Expressions.booleanTemplate("{0} = \"tofu\" and {1} = \"Amoy\"", qm.name,qm.address)).fetch();
        
        //使用stringTemplate充当查询语句的某一部分
        String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", qm.registerDate)).from(qm).fetchFirst();
        //在where子句中使用stringTemplate
        String id = queryFactory.select(qm.id).from(qm).where(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", qm.registerDate).eq("2018-03-19")).fetchFirst();

不过Template好用归好用,但也有其局限性。
例如当我们需要用到复杂的正则表达式匹配的时候,就有些捉襟见肘了。这是由于Template中使用了{}来作为占位符,而正则表达式中也可能使用了{},因而会产生冲突。

三、使用心得

1. 查询条件中字段为String时关于null,empty,blank的表达

(如果你还不了解null,empty,blank的区别,请先自行搜索了解)
QueryDSL为String类型的字段提供了.isEmpty(),isNull(),.isNotEmpty(),isNotNull()这四个函数支持,唯独没有对blank提供支持。经过测试,我发现可以通过这种方式来实现对blank的使用:.eq(""),.ne("")

你可能感兴趣的:(ORM框架)