kotlin+springboot+jpa實現從簡單到複雜的查詢,分頁查詢,動態條件,投影Projection

前提:kotlin+springboot+jpa

1.最簡單的查詢:

import org.springframework.data.repository.CrudRepository
...
interface AccountRepository : CrudRepository<Account, Long> {
	fun findByName(name: String): Account?
}

說明:根據name字段查詢account表的信息

2.使用@Query的簡單查詢:

import org.springframework.data.jpa.repository.Query
...
interface AccountRepository : CrudRepository<Account, Long> {
	@Query(value = "from Account at where at.phone = ?1")
	fun findList(phone: String): List<Account>
}

說明:實現 根據phone查詢account信息,返回list。

如果需要具體某幾個字段:

@Query(value = "select new map(at.name, at.status, at.loginAcct) from Account at where at.phone = ?1")
fun findList(phone: String): List<Map<String, String>>

或者有聚合函數:

@Query(value = "select count(at.id),count(case when at.position is null then at.id end) from Account at where at.status= ?1")
fun countByStatus(status: Int): Array<Int>

3.使用@Query的分頁查詢:

多排序條件可以這樣寫 :
…Sort.Direction.DESC, “a”,“b”)
表示按照a,b倒序;
如果這樣寫:…Sort.Direction.DESC, “a,b”),解析出來是order by a,b desc表示a正序,b倒序;
還有一種寫法擴展性更好:
val sort = Sort(Sort.Direction.DESC,“a”).and(Sort(Sort.Direction.ASC,“b”))
val pg = PageRequest(currentPage, pageSize, sort)
表示a倒序,b正序。

import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
...
val pageable = PageRequest(currentPage, pageSize, Sort.Direction.DESC, "updateAt")
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
...
interface AccountRepository : CrudRepository<Account, Long> {
	@Query(value = "from Account as at where (at.name like %?1% or ?1 is null)")
	fun findAll(name: String?, pageable: Pageable): Page<Account>
 }

說明:page中參數爲當前頁(初始值是0),每頁數量,排序方式,排序字段;實現 根據name模糊查詢的account信息,按照更新時間(updateAt字段)倒序排序,返回page對象;content和totalElements對應查詢結果和查詢總數:

result.put("list", pg.content)
result.put("total", pg.totalElements)

4.使用nativeQuery的查詢
比如需要查視圖

	@Query(nativeQuery = true, value = "select * from v_cstm_linker_list a where a.link_phone = ?1")
	fun findViewByPhone(linkPhone: String): List<Any>

說明:nativeQuery=true表示使用本地sql查詢的方式,sql中的字段應爲表中字段(link_phone 而不是linkPhone,因爲並沒有視圖的實體映射)

如果需要分頁:

	@Query(nativeQuery = true, countQuery = "select count(*) from v_cstm_linker_list a where (a.link_name like %?1% or ?1 is null)", value = "select * from v_cstm_linker_list a where (a.link_name like %?1% or ?1 is null) order by ?#{#pageable}")
	fun findCstmLinker(linkName: String?, linkPhone: String?, pageable: Pageable): Page<Any>

說明:countQuery 計算總數,分頁查詢通過?#{#pageable}來實現
或者

	@Query(nativeQuery = true, value = "select * from (select a.*,rownum rn from (...) a where rownum < ?2) where rn >?1")
	fun findListPaginate(firstNow: Int, lastRow: Int): List<...>

這邊用(…)表示可能子查詢情況,需要計算firstNow和lastRow,注意初始頁是0還是1,rownum是<還是<=

5.最近遇到的需求
要求:多表查詢 +聚合 + 動態條件 + 根據聚合函數別名排序 + 分頁
先看下完整sql:

select a.comp_name,
                       d.name as acct_name,
                       e.name as dpt_name,
                       a.erp_code,
                       COALESCE(bb.sum1, 0.0) as this_month_weight,
                       COALESCE(cc.sum2, 0.0) as last_month_weight
                  from zzzzz a
                  left join (select b.member_code as code1,
                                   sum(b.weight) as sum1
                              from xxxxx@crmstatdev b
                             where to_char(b.deal_date, 'yyyy-MM-dd') >=
                                   '2018-12-01'
                               and to_char(b.deal_date, 'yyyy-MM-dd') <=
                                   '2018-12-31'
                             group by b.member_code) bb
                    on bb.code1 = a.erp_code
                  left join (select c.member_code as code2,
                                   sum(c.weight) as sum2
                              from xxxxx@crmstatdev c
                             where to_char(c.deal_date, 'yyyy-MM-dd') >=
                                   '2018-11-01'
                               and to_char(c.deal_date, 'yyyy-MM-dd') <=
                                   '2018-11-30'
                             group by c.member_code) cc
                    on cc.code2 = a.erp_code
                  left join ccccc d
                    on a.fk_acct_id = d.id
                  left join vvvvv e
                    on a.fk_dpt_id = e.id
                  left join bbbbb f
                    on e.fk_org_id = f.id
                 where a.mark = 2
                   and a.status = 1
                   and a.erp_code is not null
                   and a.comp_name like '%測試名稱%'
                   and d.name like '%測試人員%'
                   and e.name like '%測試部門%'
                   and (decode(COALESCE(cc.sum2, 0.0), 0,0,COALESCE(bb.sum1, 0.0) / COALESCE(cc.sum2, 0.0)) < 0.65)
                 order by this_month_weight desc

五個表關聯;獲取本月和上月的sum統計數據;decode …< 0.65 這個條件是動態的;this_month_weight是聚合函數的別名(#pageable不能識別正確的別名)。

解決方法:使用entityManager,寫分頁,拼接sql

import javax.persistence.EntityManager
import javax.persistence.PersistenceContext
...
@PersistenceContext
private lateinit var entityManager: EntityManager //實體管理對象
...
// 計數
val countSql = "select count(*)$mainStr$warningStr" //mainStr就是複雜多表語句,warningStr就是動態條件語句
// 查詢
 val queryStr = "select ...as last_month_weight $mainStr$warningStr$sortStr" // sortStr就是根據聚合函數的排序語句    
// 分頁
val querySql = selectPageSql(currentPage, pageSize, queryStr)
// 原生查詢
val countQuery = this.entityManager.createNativeQuery(countSql)
val count = countQuery.resultList
val query = this.entityManager.createNativeQuery(querySql)
val queryList = query.resultList as List<Any>
result["list"] = queryList
result["total"] = count[0].toString().toInt()
// 分頁 currentPage從0開始
fun selectPageSql(currentPage: Int, pageSize: Int, queryStr: String): String{
	val start = currentPage * pageSize
	val end = (currentPage + 1) * pageSize + 1
	return "select * from (select rst.*,rownum rn from ($queryStr) rst where rownum < $end) where rn > $start"
}

最後整個查詢1秒不到,如果有好的優化請評論。
以後補充。。。

--------------------------------------------------------2019-02-19------------------------------------------------------
補充jpa Projection的使用:
1.場景:比如我要查詢多表數據或者一個視圖然後返回我需要的字段,一般都是用List<Any>或者Page<Any>作爲返回類型.

@Query(nativeQuery = true, value = "select a.name,a.main_status,a.phone,b.linkers_id,b.customers_id,c.comp_name from t_linker a, ref_cstm_linker b, t_customer c where ...")
fun findLinkAndCstmRelation(): List<Any>

這種數據是列表加數組形式的,如下圖,沒有文檔都不知道對應的是什麼,而且如果用這個數據做處理,一般都是要 list[0],arry[0]這樣去取數據,很容易出錯而且不易維護。
在這裏插入圖片描述
網上說可以使用自定義實體+構造函數的方式,但是覺得如果字段一多構造方法就很長很煩,
所以採用基於接口的投影方式來解決這個問題
2.使用:
注意需要jpa版本1.10以上,1.10的返回有問題,即使在sql中是select C,D,B,A…還是按照首字母順序(A,B,C,D)返回給你,使用1.11以上的則沒有該問題,這邊用的是springboot,所以只要修改springboot版本就行
首先創建需要的投影,命名get+名稱,這個名稱可以任意,因爲數據是按照順序去對應的,但是還是和需要的字段一致最好,然後注意抽象方法的返回,要和你的字段的類型一致(按順序),比如我語句中寫的是select a.name,a.main_status,a.phone,b.linkers_id,b.customers_id,c.comp_name ....,那麼按照順序去定義投影,如下:

interface LinkProjection {//jpa Projection
    fun getLinkName(): String
    fun getMainStatus(): Int
    fun getLinkPhone(): String
    fun getLinkId(): Long
    fun getCstmId(): Long
    fun getCompName(): String
}

遇到個坑,就是如果是分頁的:

@Query(nativeQuery = true, countQuery = "...",value ="... ?#{#pageable}")
fun findLinkAndCstmRelation(...,pageable: Pageable): Page<LinkProjection>

那麼需要加上rownum字段(不然報數組越界):

interface LinkProjection {//jpa Projection
    fun getLinkName(): String
    fun getMainStatus(): Int
    fun getLinkPhone(): String
    fun getLinkId(): Long
    fun getCstmId(): Long
    fun getCompName(): String
    fun getRowNum(): String
}

查詢接口中的返回類型就用Page<LinkProjection>,List<LinkProjection>就可以了,然後如果不需要對返回值處理,數據樣子是:
在這裏插入圖片描述
如果需要處理,可以直接用get方法獲取:

val relationMap = linkerRepo.findLinkAndCstmRelation.groupBy { m-> m.getLinkPhone() }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章