JPA如何使用nativequery多表關聯查詢返回自定義實體類
JPA nativequery多表關聯查詢返回自定義實體類
JPA官方推薦的多表關聯查詢使用不便,接觸的有些項目可能會使用JPA 做簡單查詢,Mybaits做復雜查詢。所以想要尋找一種好用的解決方案。
JPA多表關聯的實現方式
1.使用Specification實現映射關系匹配,如@ManyToOne等
2.使用NativeQuery等sql或hql來實現
優缺點對比
1.映射關系是hibernate的入門基礎,很多人都會習慣去使用。個人不太喜歡這種方式,復用性太弱,且不靈活特別是在多表復雜業務情況下。
2.使用Specification方式需要繼承JpaSpecificationExecutor接口,構造對應的方法後傳入封裝查詢條件的Specification對象。邏輯上簡單易懂,但是構造Specification對象需要拼接格式條件非常繁瑣。
3.直接使用NativeQuery等方式實現復雜查詢個人比較喜歡,直觀且便利,弊端在於無法返回自定義實體類。需要手動封裝工具類來實現Object到目標對象的反射。
使用sql並返回自定義實體類
個人比較喜歡的實現方式,不多說看代碼
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 }
這樣一個需求如果使用前兩種方式實現,無疑會非常麻煩。使用這種方式能夠直接反射需要的自定義實體類。
可以根據需求整理封裝成不同的方法,加入排序,分頁等。我在這裡主要提供一種方便的解決思路。
JPA多表關聯動態查詢(自定義sql語句)
項目需求,查詢需求數據需要多表鏈接——>根據多種條件篩選查詢到的數據,在網上查瞭很多資料最終選擇這個字符串拼接查詢
類似如此動態查詢
以下是本人項目中使用總結:
實體類
/** * 訂單表 */ @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;//創建時間 @LastModifiedDate @Column private Date lastModifiedTime;//修改時間 @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;//所屬用戶 @JoinColumn(name = "industry_id") private Integer industryId;//行業Id }
/** * 用戶表 */ @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;//名稱 } //行業表和城市表一致,就不展示瞭
註解解釋
實體類中相關註解解釋:
@Entity
: 對實體註釋。任何Hibernate映射對象都要有這個註釋@Table
: 聲明此對象映射到數據庫的數據表,該註釋不是必須的,如果沒有則系統使用默認值(實體的短類名)@Getter
、@Setter
、@NoArgsConstructor
:lombok提供註解,get、set方法及無參構造@EntityListeners(AuditingEntityListener.class)
:加上此註解,時間註解@LastModifiedDate 和 @CreatedDate才可以生效@Id
: 聲明此屬性為主鍵@GeneratedValue(strategy = GenerationType.IDENTITY)
:指定主鍵,
TABLE
:使用一個特定的數據庫表格來保存主鍵;
IDENTITY
:主鍵由數據庫自動生成(主要是自動增長型);
SEQUENCR
:根據底層數據庫的序列來生成主鍵,條件是數據庫支持序列;
AUTO
:主鍵由程序控制
@CreatedDate(updatable = false)
:創建時間時間字段,在insert的時候,會設置值;update時時間不變@LastModifiedDate
:修改時間段,update時會修改值@ManyToOne
(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}): 多對一,
FetchType.EAGER
:立即加載, 獲取關聯實體;
CascadeType.MERGE
: 級聯更新;
CascadeType.PERSIST
:級聯新建;
CascadeType.REFRESH
:級聯刷新
@JoinTable
: JoinColumn:保存關聯關系的外鍵的字段;inverseJoinColumns:保存關系的另外一個外鍵字@Column
:用來標識實體類中屬性與數據表中字段的對應關系
測試類
@RunWith(SpringRunner.class) @SpringBootTest public class SprinBootMarketingsystemApplicationTests { @PersistenceContext//jpa的數據庫操作類 private EntityManager entityManger; @Test public void queryDb(){ //給參數賦值 Integer cityId = 1; Integer industryId = 2; Integer staffId = 16; Date startTime = DateUtil.parse("1970-01-01");//字符串時間轉換為date類型 Date endTime = Calendar.getInstance().getTime();//獲取系統當前時間裝換為date類型 //創建SQL語句主體 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<>(); //拼接動態參數 if(industryId != null){ /*第一種給參數賦值方式 1代表傳進來的參數順序,給參數賦值nativeQuery.setParameter(1, industryId); stringBuffer.append(" and s.industryId = ?1");*/ //industryId代表傳進來的參數名稱,給參數賦值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){ //使用這種賦值方式,時間類型需要給三個參數,參數名稱,參數值,特定映射的類型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)); } //三種接受返回結果方式(第一種方式) /*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); }*/ //第三種方式:實體類接受 nativeQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(TestVo.class)); List<TestVo> resultList = nativeQuery.getResultList(); for (TestVo svo:resultList ) { System.out.println(svo.toString()); } }
打印結果
第一種方式打印結果
第二種方式打印結果
第三種方式打印結果
TestVo實體接收類
@Data public class TestVo { private String cityName;//城市名字 private BigInteger count;//簽單數量(必須使用BigInteger類型接受) }
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- JPA如何使用entityManager執行SQL並指定返回類型
- 使用JPA單項一對多外鍵關聯
- springboot jpa 實現返回結果自定義查詢
- Springboot JPA如何使用distinct返回對象
- springboot jpa之返回表中部分字段的處理詳解