在前面的文章中,咱们介绍了 JPA 的基础使用方式, 《Spring Boot (三): ORM 框架 JPA 与链接池 Hikari》,本篇文章,咱们由入门至进阶的介绍一下为 JPA 插上翅膀的 QueryDSL。
不能否认的是 JPA 使用是很是方便的,极简化的配置,只须要使用注解,无需任何 xml 的配置文件,语义简单易懂,可是,以上的一切都创建在单表查询的前提下的,咱们可使用 JPA 默认提供的方法,简单加轻松的完成 CRUD 操做。html
可是若是涉及到多表动态查询, JPA 的功能就显得有些捉襟见肘了,虽然咱们可使用注解 @Query
,在这个注解中写 SQL 或者 HQL 都是在拼接字符串,而且拼接后的字符串可读性很是的差,固然 JPA 还为咱们提供了 Specification
来作这件事情,从我我的使用体验上来说,可读性虽然还不错,可是在初学者上手的时候, Predicate
和 CriteriaBuilder
使用方式估计能劝退很多人,并且若是直接执行 SQL 连表查询,得到是一个 Object[]
,类型是什么?字段名是什么?这些都没法直观的得到,还需咱们手动将 Object[]
映射到咱们须要的 Model
类里面去,这种使用体验无疑是极其糟糕的。java
这一切都在 QueryDSL 出世之后终结了, QueryDSL 语法与 SQL 很是类似,代码可读性很是强,异常简介优美,,而且与 JPA 高度集成,无需多余的配置,从笔者我的使用体验上来说是很是棒的。能够这么说,只要会写 SQL ,基本上只须要看一下示例代码彻底能够达到入门的级别。mysql
QueryDSL 是一个很是活跃的开源项目,目前在 Github 上的发布的 Release 版本已经多达 251 个版本,目前最新版是 4.2.1 ,而且由 Querydsl Google组 和 StackOverflow 两个团队提供支持。git
QueryDSL 是一个框架,可用于构造静态类型的相似SQL的查询。能够经过诸如 QueryDSL 之类的 API 构造查询,而不是将查询编写为内联字符串或将其外部化为XML文件。github
例如,与简单字符串相比,使用 API 的好处是spring
代码清单:spring-boot-jpa-querydsl/pom.xmlsql
<!--QueryDSL支持--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <scope>provided</scope> </dependency> <!--QueryDSL支持--> <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> </dependency>
spring-boot-dependencies
工程中定义。添加这个插件是为了让程序自动生成 query type (查询实体,命名方式为:"Q"+对应实体名)。
上文引入的依赖中 querydsl-apt 便是为此插件服务的。安全
注:在使用过程当中,若是遇到 query type 没法自动生成的状况,用maven更新一下项目便可解决(右键项目 -> Maven -> Update Folders)。springboot
代码清单:spring-boot-jpa-querydsl/pom.xml框架
<plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>com.mysema.maven</groupId> <artifactId>apt-maven-plugin</artifactId> <version>1.1.3</version> <executions> <execution> <goals> <goal>process</goal> </goals> <configuration> <outputDirectory>target/generated-sources/java</outputDirectory> <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> </plugins>
在 JPA 中已经为咱们提供了很是简便的更新和删除的使用方式,咱们彻底没有必要使用 QueryDSL 的更新和删除,不过这里仍是给出用法,供你们参考:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override public Long update(String id, String nickName) { QUserModel userModel = QUserModel.userModel; // 更新 return queryFactory.update(userModel).set(userModel.nickName, nickName).where(userModel.id.eq(id)).execute(); } @Override public Long delete(String id) { QUserModel userModel = QUserModel.userModel; // 删除 return queryFactory.delete(userModel).where(userModel.id.eq(id)).execute(); }
QueryDSL 在查询这方面能够说玩的很是花了,好比一些有关 select()
和 fetch()
经常使用的写法以下:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override public List<String> selectAllNameList() { QUserModel userModel = QUserModel.userModel; // 查询字段 return queryFactory.select(userModel.nickName).from(userModel).fetch(); } @Override public List<UserModel> selectAllUserModelList() { QUserModel userModel = QUserModel.userModel; // 查询实体 return queryFactory.selectFrom(userModel).fetch(); } @Override public List<UserDTO> selectAllUserDTOList() { QUserModel userModel = QUserModel.userModel; QLessonModel lessonModel = QLessonModel.lessonModel; // 连表查询实体并将结果封装至DTO return queryFactory .select( Projections.bean(UserDTO.class, userModel.nickName, userModel.age, lessonModel.startDate, lessonModel.address, lessonModel.name) ) .from(userModel) .leftJoin(lessonModel) .on(userModel.id.eq(lessonModel.userId)) .fetch(); } @Override public List<String> selectDistinctNameList() { QUserModel userModel = QUserModel.userModel; // 去重查询 return queryFactory.selectDistinct(userModel.nickName).from(userModel).fetch(); } @Override public UserModel selectFirstUser() { QUserModel userModel = QUserModel.userModel; // 查询首个实体 return queryFactory.selectFrom(userModel).fetchFirst(); } @Override public UserModel selectUser(String id) { QUserModel userModel = QUserModel.userModel; // 查询单个实体,若是结果有多个,会抛`NonUniqueResultException`。 return queryFactory.selectFrom(userModel).fetchOne(); }
上面列举了简单的查询,但实际咱们会遇到至关复杂的操做,好比子查询,多条件查询,多表连查,使用示例以下:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/LessonServiceImpl.java
@Service public class LessonServiceImpl implements LessonService { @Autowired JPAQueryFactory queryFactory; @Override public List<LessonModel> findLessonList(String name, Date startDate, String address, String userId) throws ParseException { QLessonModel lessonModel = QLessonModel.lessonModel; SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); // 多条件查询示例 return queryFactory.selectFrom(lessonModel) .where( lessonModel.name.like("%" + name + "%") .and(lessonModel.address.contains(address)) .and(lessonModel.userId.eq(userId)) .and(lessonModel.startDate.between(simpleDateFormat.parse("2018-12-31 00:00:00"), new Date())) ) .fetch(); } @Override public List<LessonModel> findLessonDynaList(String name, Date startDate, String address, String userId) throws ParseException { QLessonModel lessonModel = QLessonModel.lessonModel; SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); // 动态查询示例 BooleanBuilder builder = new BooleanBuilder(); if (!StringUtils.isEmpty(name)){ builder.and(lessonModel.name.like("%" + name + "%")); } if (startDate != null) { builder.and(lessonModel.startDate.between(simpleDateFormat.parse("2018-12-31 00:00:00"), new Date())); } if (!StringUtils.isEmpty(address)) { builder.and(lessonModel.address.contains(address)); } if (!StringUtils.isEmpty(userId)) { builder.and(lessonModel.userId.eq(userId)); } return queryFactory.selectFrom(lessonModel).where(builder).fetch(); } @Override public List<LessonModel> findLessonSubqueryList(String name, String address) { QLessonModel lessonModel = QLessonModel.lessonModel; // 子查询示例,并没有实际意义 return queryFactory.selectFrom(lessonModel) .where(lessonModel.name.in( JPAExpressions .select(lessonModel.name) .from(lessonModel) .where(lessonModel.address.eq(address)) )) .fetch(); } }
QueryDSL 已经内置了一些经常使用的 Mysql 的聚合函数,若是遇到 QueryDSL 没有提供的聚合函数也无需慌张, QueryDSL 为咱们提供了 Expressions
这个类,咱们可使用这个类手动拼接一个就好,以下示例:
代码清单:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java
@Override public String mysqlFuncDemo(String id, String nickName, int age) { QUserModel userModel = QUserModel.userModel; // Mysql 聚合函数示例 // 聚合函数-avg() Double averageAge = queryFactory.select(userModel.age.avg()).from(userModel).fetchOne(); // 聚合函数-sum() Integer sumAge = queryFactory.select(userModel.age.sum()).from(userModel).fetchOne(); // 聚合函数-concat() String concat = queryFactory.select(userModel.nickName.concat(nickName)).from(userModel).fetchOne(); // 聚合函数-contains() Boolean contains = queryFactory.select(userModel.nickName.contains(nickName)).from(userModel).where(userModel.id.eq(id)).fetchOne(); // 聚合函数-DATE_FORMAT() String date = queryFactory.select(Expressions.stringTemplate("DATE_FORMAT({0},'%Y-%m-%d')", userModel.createDate)).from(userModel).fetchOne(); return null; }
有关 QueryDSL 的介绍到这里就结束了,不知道各位读者看了上面的示例,有没有一种直接读 SQL 的感受,并且这种 SQL 仍是使用 OOM 的思想,将本来 Hibernate 没有作好的事情给出了一个至关完美的解决方案,上手简单易操做,而又无需写 SQL ,实际上咱们操做的仍是对象类。
若是个人文章对您有帮助,请扫码关注下做者的公众号:获取最新干货推送:)