一、分頁查詢
場景:動態(tài)查詢,分頁查詢,根據(jù)傳入不同的狀態(tài),分別查詢不同數(shù)據(jù)表,并且在傳入page對象之前用map進行VO轉(zhuǎn)換。而pageable的使用地方不同影響到了分頁數(shù)據(jù)的正確性,以此進行探討。
- pageable使用于new PageImpl<>中,且直到最后才將List -> Page
- pageable使用于findAll()中
前提:Page對象封于VO內(nèi),返回數(shù)據(jù)包括了分頁數(shù)據(jù)
@ApiModelProperty("記錄")
private Page<ActivityRecordVO> activityRecordVOList;
@ApiModelProperty("數(shù)量")
private Integer num = 0;
@ApiModelProperty("金額")
private BigDecimal totalMoney = BigDecimal.valueOf(0);
錯誤運用:
List<ActivityRecordVO> activityRecordVOList = new ArrayList<>();
if (receiveSendRecordRequestVO.getSendOrReceiveType() == SendOrReceiveType.RECEIVE) {
List<ChallengeRecord> challengeRecordList = challengeRecordDao.findByUserIdAndDeleteType(userId,
DeleteType.FALSE);
if (!CollectionUtils.isEmpty(challengeRecordList)) {
activityRecordVOList = challengeRecordList.stream()
.map(this::challengeRecordToActivityRecordVO)
.collect(Collectors.toList());
}
} else if (receiveSendRecordRequestVO.getSendOrReceiveType() == SendOrReceiveType.SEND) {
List<Activity> activityList = activityDao.findByUserIdAndDeleteType(userId, DeleteType.FALSE);
if (!CollectionUtils.isEmpty(activityList)) {
activityRecordVOList = activityList.stream()
.map(this::activityTOActivityRecordVO)
.collect(Collectors.toList());
}
}
activityReceiveSendRecordVO.setActivityRecordVOList(new PageImpl<>(activityRecordVOList,
pageable, activityRecordVOList.size()));
解析:傳入的pageable只在set進VO的時候,用new PageIml將List轉(zhuǎn)為page對象,前端報的問題 雖然總頁數(shù)、總條數(shù)均為正確,但第一頁的條數(shù)是全部 ,數(shù)據(jù)異常!
正確參考做法:
采用Specifications先根據(jù)查詢條件動態(tài)查詢并map出相應(yīng)分頁對象(此塊代碼因需求而異),這時 findAll 傳入的pageable是生效的,便會顯現(xiàn)正確的分頁信息。
代碼塊參考:
xxxCommonSpecUtil 是自封的specification工具類,與原生spring data jpa原生查詢方法類似。
Page<ActivityRecordVO> page = new PageImpl<>(activityRecordVOList, pageable, activityRecordVOList.size());
if (receiveSendRecordRequestVO.getSendOrReceiveType() == SendOrReceiveType.RECEIVE) {
Specifications<ChallengeRecord> spec = Specifications.where(
challengeCommonSpecUtil.equal("userId", userId))
.and(challengeCommonSpecUtil.equal("deleteType", DeleteType.FALSE));
page = challengeRecordDao.findAll(spec, pageable).map(this::challengeRecordToActivityRecordVO);
} else if (receiveSendRecordRequestVO.getSendOrReceiveType() == SendOrReceiveType.SEND) {
Specifications<Activity> spec = Specifications.where(
activityCommonSpecUtil.equal("userId", userId))
.and(activityCommonSpecUtil.equal("deleteType", DeleteType.FALSE));
page = activityDao.findAll(spec, pageable).map(this::activityTOActivityRecordVO);
}
注:activityReceiveSendRecordVO為封裝的VO,包含了返回的Page對象
activityReceiveSendRecordVO.setActivityRecordVOList(page);
二、事務(wù),更新實體,并查詢
場景:@Transactional事務(wù),更新用戶余額(處理并發(fā)問題),更新完畢返回VO帶上用戶剩余余額,但卻非更新后的余額。
更新邏輯代碼:
userWebService.updateBalanceAfterTransaction(userId, transactionRecordAddVO.getMoney(),user.getBalance());
@Transactional
public void updateBalanceAfterTransaction(Integer userId, BigDecimal money, BigDecimal userBalance) {
int i = userDao.updateBalanceAfterTransaction(userId, money, userBalance);
if (i == 0) {
throw new ValidationException(MessageCodes.TRANSACTION_RECEIVE_IS_ERROR);
}
}
@Modifying
@Query(value = "update User u set u.balance = u.balance - ?2 where u.id = ?1 and u.balance = ?3")
int updateBalanceAfterTransaction(Integer userId, BigDecimal money, BigDecimal userBalance);
解析:
在jpa中使用 @Modifying ,雖然事務(wù)已經(jīng)能夠更新,但是在循環(huán)更新的時候,執(zhí)行modify語句后的查詢的實體仍然是沒有更新的。
執(zhí)行完modifying query, EntityManager可能會包含過時的數(shù)據(jù),因為EntityManager不會自動清除實體。
只有添加clearAutomatically屬性,EntityManager才會自動清除實體對象。
添加代碼:
@Modifying(clearAutomatically = true)
改正后的示例代碼:
@Modifying(clearAutomatically = true)
@Query(value = "update User u set u.balance = u.balance - ?2 where u.id = ?1 and u.balance = ?3")
int updateBalanceAfterTransaction(Integer userId, BigDecimal money, BigDecimal userBalance);
總結(jié)
使用了這么長時間spring data jpa,覺得Specifications巨好用,也不容易出錯,也是我喜歡的編碼風(fēng)格,而new PageImpl<>()這種簡單粗暴的方法我一般都用在查詢數(shù)據(jù)關(guān)聯(lián)太多表的情況,在最后直接返回,更深層次的還需要再探討!