springJPA 之 QueryDSL(一)


引言
不可否認的是 JPA 使用是非常方便的,極簡化的配置,只需要使用注解,無需任何 xml 的配置文件,語義簡單易懂,但是,以上的一切都建立在單表查詢的前提下的,我們可以使用 JPA 默認提供的方法,簡單加輕松的完成 CRUD 操作。
但是如果涉及到多表動態查詢, JPA 的功能就顯得有些捉襟見肘了,雖然我們可以使用注解 @Query ,在這個注解中寫 SQL 或者 HQL 都是在拼接字符串,並且拼接后的字符串可讀性非常的差,當然 JPA 還為我們提供了 Specification 來做這件事情,從我個人使用體驗上來講,可讀性雖然還不錯,但是在初學者上手的時候, Predicate 和 CriteriaBuilder 使用方式估計能勸退不少人,而且如果直接執行 SQL 連表查詢,獲得是一個 Object[] ,類型是什么?字段名是什么?這些都無法直觀的獲得,還需我們手動將 Object[] 映射到我們需要的 Model 類里面去,這種使用體驗無疑是極其糟糕的。

這一切都在 QueryDSL 出世以后終結了, QueryDSL 語法與 SQL 非常相似,代碼可讀性非常強,異常簡介優美,,並且與 JPA 高度集成,無需多余的配置,從筆者個人使用體驗上來講是非常棒的。可以這么說,只要會寫 SQL ,基本上只需要看一下示例代碼完全可以達到入門的級別。

QueryDSL 簡介
QueryDSL 是一個非常活躍的開源項目,目前在 Github 上的發布的 Release 版本已經多達 251 個版本,目前最新版是 4.2.1 ,並且由 Querydsl Google組 和 StackOverflow 兩個團隊提供支持。
QueryDSL 是一個框架,可用於構造靜態類型的類似SQL的查詢。可以通過諸如 QueryDSL 之類的 API 構造查詢,而不是將查詢編寫為內聯字符串或將其外部化為XML文件。

例如,與簡單字符串相比,使用 API 的好處是

IDE中的代碼完成

幾乎沒有語法無效的查詢

可以安全地引用域類型和屬性

更好地重構域類型的更改

QueryDSL 使用實戰
3.1 引入 Maven 依賴
代碼清單:spring-boot-jpa-querydsl/pom.xml
com.querydsl querydsl-apt provided com.querydsl querydsl-jpa COPY 這里無需指定版本號,已在 spring-boot-dependencies 工程中定義。 3.2 添加 Maven 插件 添加這個插件是為了讓程序自動生成 query type (查詢實體,命名方式為:"Q"+對應實體名)。 上文引入的依賴中 querydsl-apt 即是為此插件服務的。

注:在使用過程中,如果遇到 query type 無法自動生成的情況,用maven更新一下項目即可解決(右鍵項目 -> Maven -> Update Folders)。

代碼清單:spring-boot-jpa-querydsl/pom.xml

org.springframework.boot spring-boot-maven-plugin com.mysema.maven apt-maven-plugin 1.1.3 process target/generated-sources/java com.querydsl.apt.jpa.JPAAnnotationProcessor COPY 3.3 更新和刪除 在 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();
}COPY
3.2 查詢
QueryDSL 在查詢這方面可以說玩的非常花了,比如一些有關 select() 和 fetch() 常用的寫法如下:

代碼清單:spring-boot-jpa-querydsl/src/main/java/com/springboot/springbootjpaquerydsl/service/impl/UserServiceImpl.java

@Override
public List selectAllNameList() {
QUserModel userModel = QUserModel.userModel;
// 查詢字段
return queryFactory.select(userModel.nickName).from(userModel).fetch();
}

@Override
public List selectAllUserModelList() {
QUserModel userModel = QUserModel.userModel;
// 查詢實體
return queryFactory.selectFrom(userModel).fetch();
}

@Override
public List 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();
}

 

/** * 根據QueryDSL查詢 * @return */ @RequestMapping(value = "/selectWithQueryDSL") public List<GoodDTO> selectWithQueryDSL() { //商品基本信息 QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean; //商品類型 QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean; return queryFactory .select( Projections.bean( GoodDTO.class,//返回自定義實體的類型 _Q_good.id, _Q_good.price, _Q_good.title, _Q_good.unit, _Q_good_type.name.as("typeName"),//使用別名對應dto內的typeName _Q_good_type.id.as("typeId")//使用別名對應dto內的typeId ) ) .from(_Q_good,_Q_good_type)//構建兩表笛卡爾集 .where(_Q_good.typeId.eq(_Q_good_type.id))//關聯兩表 .orderBy(_Q_good.order.desc())//倒序 .fetch(); }

 

 

 

 

 

@Override
public List 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();
}COPY
3.4 復雜查詢操作
上面列舉了簡單的查詢,但實際我們會遇到相當復雜的操作,比如子查詢,多條件查詢,多表連查,使用示例如下:

代碼清單: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();
}
}COPY
3.5 Mysql 聚合函數
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;

}COPY
4. 小結
有關 QueryDSL 的介紹到這里就結束了,不知道各位讀者看了上面的示例,有沒有一種直接讀 SQL 的感覺,而且這種 SQL 還是使用 OOM 的思想,將原本 Hibernate 沒有做好的事情給出了一個相當完美的解決方案,上手簡單易操作,而又無需寫 SQL ,實際上我們操作的還是對象類。

 

轉載至 cnds 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM