JPA如何使用nativequery多表關(guān)聯(lián)查詢(xún)返回自定義實(shí)體類(lèi)
JPA nativequery多表關(guān)聯(lián)查詢(xún)返回自定義實(shí)體類(lèi)
JPA官方推薦的多表關(guān)聯(lián)查詢(xún)使用不便,接觸的有些項(xiàng)目可能會(huì)使用JPA 做簡(jiǎn)單查詢(xún),Mybaits做復(fù)雜查詢(xún)。所以想要尋找一種好用的解決方案。
JPA多表關(guān)聯(lián)的實(shí)現(xiàn)方式
1.使用Specification實(shí)現(xiàn)映射關(guān)系匹配,如@ManyToOne等
2.使用NativeQuery等sql或hql來(lái)實(shí)現(xiàn)
優(yōu)缺點(diǎn)對(duì)比
1.映射關(guān)系是hibernate的入門(mén)基礎(chǔ),很多人都會(huì)習(xí)慣去使用。個(gè)人不太喜歡這種方式,復(fù)用性太弱,且不靈活特別是在多表復(fù)雜業(yè)務(wù)情況下。
2.使用Specification方式需要繼承JpaSpecificationExecutor接口,構(gòu)造對(duì)應(yīng)的方法后傳入封裝查詢(xún)條件的Specification對(duì)象。邏輯上簡(jiǎn)單易懂,但是構(gòu)造Specification對(duì)象需要拼接格式條件非常繁瑣。
3.直接使用NativeQuery等方式實(shí)現(xiàn)復(fù)雜查詢(xún)個(gè)人比較喜歡,直觀且便利,弊端在于無(wú)法返回自定義實(shí)體類(lèi)。需要手動(dòng)封裝工具類(lèi)來(lái)實(shí)現(xiàn)Object到目標(biāo)對(duì)象的反射。
使用sql并返回自定義實(shí)體類(lèi)
個(gè)人比較喜歡的實(shí)現(xiàn)方式,不多說(shuō)看代碼
import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.transaction.Transactional; @Repository public class EntityManagerDAO { @PersistenceContext private EntityManager entityManager; /** * 人員列表排序 * @return */ @Transactional public List<BackstageUserListDTO> listUser(){ String sql = "select a.create_time createTime," + "a.mobilephone phoneNum," + "a.email email,a.uid uid," + "a.enabled enabled," + "c.id_number idNumber," + " (case b.`status` when 1 then 1 else 0 end) status " + "from tbl_sys_user a " + "LEFT JOIN user_high_qic b on a.uid=b.u_id" + "LEFT JOIN user_qic c on a.uid=c.uid " + "ORDER BY status desc"; SQLQuery sqlQuery = entityManager.createNativeQuery(sql).unwrap(SQLQuery.class); Query query = sqlQuery.setResultTransformer(Transformers.aliasToBean(BackstageUserListDTO.class)); List<BackstageUserListDTO> list = query.list(); entityManager.clear(); return list; } }
public class BackstageUserListDTO implements Serializable{ private static final long serid = 1L; private String createTime; private String phoneNum; private String email; private BigInteger uid; private Integer enabled; private String idNumber; private BigInteger status; //GETTER SETTER }
這樣一個(gè)需求如果使用前兩種方式實(shí)現(xiàn),無(wú)疑會(huì)非常麻煩。使用這種方式能夠直接反射需要的自定義實(shí)體類(lèi)。
可以根據(jù)需求整理封裝成不同的方法,加入排序,分頁(yè)等。我在這里主要提供一種方便的解決思路。
JPA多表關(guān)聯(lián)動(dòng)態(tài)查詢(xún)(自定義sql語(yǔ)句)
項(xiàng)目需求,查詢(xún)需求數(shù)據(jù)需要多表鏈接——>根據(jù)多種條件篩選查詢(xún)到的數(shù)據(jù),在網(wǎng)上查了很多資料最終選擇這個(gè)字符串拼接查詢(xún)
類(lèi)似如此動(dòng)態(tài)查詢(xún)
以下是本人項(xiàng)目中使用總結(jié):
實(shí)體類(lèi)
/** * 訂單表 */ @Entity @Table(name = "signedorder") @Getter @Setter @NoArgsConstructor @EntityListeners(AuditingEntityListener.class) public class SignedOrder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column private Integer id;//id @CreatedDate @Column(updatable = false) private Date createTime;//創(chuàng)建時(shí)間 @LastModifiedDate @Column private Date lastModifiedTime;//修改時(shí)間 @ManyToOne(fetch = FetchType.EAGER, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH }) @JoinTable(name = "staff_signedorder", joinColumns = @JoinColumn(name = "signedorder_id"), inverseJoinColumns = @JoinColumn(name = "staff_id")) private Staff staff;//所屬用戶(hù) @JoinColumn(name = "industry_id") private Integer industryId;//行業(yè)Id }
/** * 用戶(hù)表 */ @Entity @Table(name = "staff") @Getter @Setter @NoArgsConstructor public class Staff { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id;//id @Column(name = "name", length = 25) private String name;//姓名 @JoinColumn(name = "city_id") private Integer cityId;//城市id
/** * 城市表 */ @Entity @Table(name = "city") @Getter @Setter @NoArgsConstructor @Accessors(chain = true) public class City { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id;//id @Column(name = "name", length = 50) private String name;//名稱(chēng) } //行業(yè)表和城市表一致,就不展示了
注解解釋
實(shí)體類(lèi)中相關(guān)注解解釋?zhuān)?/p>
@Entity
: 對(duì)實(shí)體注釋。任何Hibernate映射對(duì)象都要有這個(gè)注釋@Table
: 聲明此對(duì)象映射到數(shù)據(jù)庫(kù)的數(shù)據(jù)表,該注釋不是必須的,如果沒(méi)有則系統(tǒng)使用默認(rèn)值(實(shí)體的短類(lèi)名)@Getter
、@Setter
、@NoArgsConstructor
:lombok提供注解,get、set方法及無(wú)參構(gòu)造@EntityListeners(AuditingEntityListener.class)
:加上此注解,時(shí)間注解@LastModifiedDate 和 @CreatedDate才可以生效@Id
: 聲明此屬性為主鍵@GeneratedValue(strategy = GenerationType.IDENTITY)
:指定主鍵,
TABLE
:使用一個(gè)特定的數(shù)據(jù)庫(kù)表格來(lái)保存主鍵;
IDENTITY
:主鍵由數(shù)據(jù)庫(kù)自動(dòng)生成(主要是自動(dòng)增長(zhǎng)型);
SEQUENCR
:根據(jù)底層數(shù)據(jù)庫(kù)的序列來(lái)生成主鍵,條件是數(shù)據(jù)庫(kù)支持序列;
AUTO
:主鍵由程序控制
@CreatedDate(updatable = false)
:創(chuàng)建時(shí)間時(shí)間字段,在insert的時(shí)候,會(huì)設(shè)置值;update時(shí)時(shí)間不變@LastModifiedDate
:修改時(shí)間段,update時(shí)會(huì)修改值@ManyToOne
(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}): 多對(duì)一,
FetchType.EAGER
:立即加載, 獲取關(guān)聯(lián)實(shí)體;
CascadeType.MERGE
: 級(jí)聯(lián)更新;
CascadeType.PERSIST
:級(jí)聯(lián)新建;
CascadeType.REFRESH
:級(jí)聯(lián)刷新
@JoinTable
: JoinColumn:保存關(guān)聯(lián)關(guān)系的外鍵的字段;inverseJoinColumns:保存關(guān)系的另外一個(gè)外鍵字@Column
:用來(lái)標(biāo)識(shí)實(shí)體類(lèi)中屬性與數(shù)據(jù)表中字段的對(duì)應(yīng)關(guān)系
測(cè)試類(lèi)
@RunWith(SpringRunner.class) @SpringBootTest public class SprinBootMarketingsystemApplicationTests { @PersistenceContext//jpa的數(shù)據(jù)庫(kù)操作類(lèi) private EntityManager entityManger; @Test public void queryDb(){ //給參數(shù)賦值 Integer cityId = 1; Integer industryId = 2; Integer staffId = 16; Date startTime = DateUtil.parse("1970-01-01");//字符串時(shí)間轉(zhuǎn)換為date類(lèi)型 Date endTime = Calendar.getInstance().getTime();//獲取系統(tǒng)當(dāng)前時(shí)間裝換為date類(lèi)型 //創(chuàng)建SQL語(yǔ)句主體 StringBuffer stringBuffer = new StringBuffer("\tSELECT\n" + "\tcount( * ) count,\n" + "\tci.NAME cityName\n" + "\tFROM\n" + "\tsignedorder s\n" + "\tLEFT JOIN staff_signedorder t ON s.id = t.signedorder_id\n" + "\tLEFT JOIN staff sta ON t.staff_id = sta.id\n" + "\tLEFT JOIN city ci ON sta.city_id = ci.id\n" + "\tWHERE\n" + "\t1 = 1"); Map<String,Object> map = new HashMap<>(); //拼接動(dòng)態(tài)參數(shù) if(industryId != null){ /*第一種給參數(shù)賦值方式 1代表傳進(jìn)來(lái)的參數(shù)順序,給參數(shù)賦值nativeQuery.setParameter(1, industryId); stringBuffer.append(" and s.industryId = ?1");*/ //industryId代表傳進(jìn)來(lái)的參數(shù)名稱(chēng),給參數(shù)賦值nativeQuery.setParameter("industryId", industryId); stringBuffer.append(" and s.industry_id = :industryId"); map.put("industryId",industryId); } if(cityId != null){ stringBuffer.append(" and ci.id = :cityId"); map.put("cityId",cityId); } if(staffId != null){ stringBuffer.append(" and sta.id = :staffId"); map.put("staffId",staffId); } if(startTime!=null && endTime!=null){ //使用這種賦值方式,時(shí)間類(lèi)型需要給三個(gè)參數(shù),參數(shù)名稱(chēng),參數(shù)值,特定映射的類(lèi)型TemporalType.DATE //nativeQuery.setParameter("create_time", startTime,TemporalType.DATE); stringBuffer.append( " and s.create_time BETWEEN :startTime and :endTime "); map.put("startTime",startTime); map.put("endTime",endTime); } Query nativeQuery = entityManger.createNativeQuery(stringBuffer.toString()); for (String key : map.keySet()) { nativeQuery.setParameter(key, map.get(key)); } //三種接受返回結(jié)果方式(第一種方式) /*nativeQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.TO_LIST); List resultList1 = nativeQuery.getResultList(); for (Object o : resultList1) { System.out.println(o.toString()); }*/ //第二種方式和第一種方式相似 /*nativeQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); List<Map<String, Object>> resultList = nativeQuery.getResultList(); for (Map<String, Object> map1 :resultList ) { System.out.println(map1); }*/ //第三種方式:實(shí)體類(lèi)接受 nativeQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(TestVo.class)); List<TestVo> resultList = nativeQuery.getResultList(); for (TestVo svo:resultList ) { System.out.println(svo.toString()); } }
打印結(jié)果
第一種方式打印結(jié)果
第二種方式打印結(jié)果
第三種方式打印結(jié)果
TestVo實(shí)體接收類(lèi)
@Data public class TestVo { private String cityName;//城市名字 private BigInteger count;//簽單數(shù)量(必須使用BigInteger類(lèi)型接受) }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
當(dāng)Mybatis遇上目錄樹(shù)超全完美解決方案
這篇文章主要介紹了當(dāng)Mybatis遇上目錄樹(shù)有哪些解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04詳解poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實(shí)例
本篇文章主要介紹了poi+springmvc+springjdbc導(dǎo)入導(dǎo)出excel實(shí)例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01SpringMVC下實(shí)現(xiàn)Excel文件上傳下載
這篇文章主要為大家詳細(xì)介紹了SpringMVC下實(shí)現(xiàn)Excel文件上傳下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03java連接數(shù)據(jù)庫(kù)知識(shí)點(diǎn)總結(jié)以及操作應(yīng)用
這篇文章主要給大家介紹了關(guān)于java連接數(shù)據(jù)庫(kù)知識(shí)點(diǎn)總結(jié)以及操作應(yīng)用的相關(guān)資料, 當(dāng)涉及到Java中數(shù)據(jù)庫(kù)數(shù)據(jù)處理時(shí),我們可以利用強(qiáng)大的Java數(shù)據(jù)庫(kù)連接技術(shù)與各種數(shù)據(jù)庫(kù)進(jìn)行交互,需要的朋友可以參考下2023-12-12詳解java線(xiàn)程的開(kāi)始、暫停、繼續(xù)
本文將介紹通過(guò)線(xiàn)程讀取文件內(nèi)容,并且可以控制線(xiàn)程的開(kāi)始、暫停、繼續(xù),來(lái)控制讀文件。具有一定的參考作用,下面跟著小編一起來(lái)看下吧2017-01-01springboot后端存儲(chǔ)富文本內(nèi)容的思路與步驟(含圖片內(nèi)容)
在所有的編輯器中,大概最受歡迎的就是富文本編輯器和MarkDown編輯器了,下面這篇文章主要給大家介紹了關(guān)于springboot后端存儲(chǔ)富文本內(nèi)容的思路與步驟的相關(guān)資料,需要的朋友可以參考下2023-04-04Java消息隊(duì)列RabbitMQ之消息回調(diào)詳解
這篇文章主要介紹了Java消息隊(duì)列RabbitMQ之消息回調(diào)詳解,消息回調(diào),其實(shí)就是消息確認(rèn)(生產(chǎn)者推送消息成功,消費(fèi)者接收消息成功) , 對(duì)于程序來(lái)說(shuō),發(fā)送者沒(méi)法確認(rèn)是否發(fā)送成功,需要的朋友可以參考下2023-07-07java模擬實(shí)現(xiàn)斗地主發(fā)牌小程序
這篇文章主要為大家詳細(xì)介紹了java模擬實(shí)現(xiàn)斗地主發(fā)牌小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04kettle中使用js調(diào)用java類(lèi)的方法
這篇文章主要介紹了kettle中使用js調(diào)用java類(lèi)的方法,本文講解了注意事項(xiàng)和調(diào)用語(yǔ)法,需要的朋友可以參考下2015-05-05