架構小白到磚家-10-JPA自定義方法-支援原生sql
俗話說金無足赤,人無完人,jpa作為一個儲存層技術方案肯定也有不完美的地方,在多表模型和特殊資料庫操作方面,還是需要傳統sql來進行處理。那麼咱們就繼續討論如何通過jpa來實現原生sql的支援?
jpa已經提供了JpaRepository的預設實現類SimpleJpaRepository,咱們現在想自定義方法來擴充套件JpaRepository,就只能寫一個自定義BaseRepository介面繼承它,然後寫一個自定義實現類BaseRepositoryImpl繼承SimpleJpaRepository。
咱們就實現幾個基礎的原生sql方法就可以了,基本上能滿足絕大多數多數需求。
其實JPA擴充套件方法的實現比較簡單,建立JPA的Query,然後轉化成ORM的SQLQuery就可以了。當然不同ORM的物件不一樣,這裡用的是hibernate的方案就是SQLQuery。下面直接將關鍵方法原始碼貼出來。
sqlFindMap
public List<Map> sqlFindMap(String sql, List<Object> queryParams) { Query query = getEntityManager().createNativeQuery(sql); SQLQuery sqlQuery = query.unwrap(SQLQuery.class); sqlQuery.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); if(queryParams!=null&&queryParams.size()!=0){ for(int index = 0;index<queryParams.size();index++){ sqlQuery.setParameter(index, queryParams.get(index)); } } return sqlQuery.list(); }
findAll方法支撐物件返回結果方法
private <T> List<T> findAll(String sql, Class<T> clazz, int page,int pageSize, List<Object> queryParams) { Query query = getEntityManager().createNativeQuery(sql); SQLQuery sqlQuery = query.unwrap(SQLQuery.class); sqlQuery.setResultTransformer(Transformers.aliasToBean(clazz)); if(queryParams!=null&&queryParams.size()!=0){ for(int index = 0;index<queryParams.size();index++){ sqlQuery.setParameter(index, queryParams.get(index)); } } // 指定返回欄位,根據返回Bean的欄位來設定 Field[] fields = clazz.getDeclaredFields(); String sqlTmp = sql.toLowerCase(); int index = sqlTmp.indexOf("from"); for (Field field : fields) { if (sql.substring(0, index).contains(field.getName())) { // long型別必須設定型別,不然會轉換異常 if (field.getType() == long.class || field.getType() == Long.class) { sqlQuery.addScalar(field.getName(), StandardBasicTypes.LONG); } else if (field.getType() == int.class || field.getType() == Integer.class) { sqlQuery.addScalar(field.getName(), StandardBasicTypes.INTEGER); } else { sqlQuery.addScalar(field.getName()); } } } // 分頁設定 if (page >= 0) { sqlQuery.setFirstResult((page) * pageSize); sqlQuery.setMaxResults(pageSize); } // 返回查詢結果 return sqlQuery.list(); }
sqlExcute
@Transactional
@Modifying
public int sqlExcute(String sql, List<Object> queryParams) {
Query query = getEntityManager().createNativeQuery(sql);
SQLQuery sqlQuery = query.unwrap(SQLQuery.class);
if(queryParams!=null&&queryParams.size()!=0){
for(int index = 0;index<queryParams.size();index++){
sqlQuery.setParameter(index, queryParams.get(index));
}
}
return sqlQuery.executeUpdate();
}
這樣咱們的JPA擴充套件就完成了,需要特別注意的問題,sqlExcute方法是執行update、delete等非查詢事務,但是SimpleJpaRepository預設是隻讀事務,所以需要新增@Modifying告訴spring咱們是修改操作,並且@Transactional說明需要新增事務控制。
但是想讓UserRepository使用擴充套件的自定義方法,還需要做兩件事情,讓spring把咱們的BaseRepository作為jpa預設的實現類,這樣才能真正的實現jpa擴充套件。第一,要BaseRepository新增@NoRepositoryBean註解;第二,要在Application啟動時,初始化BaseRepository的實現類。
這樣咱們就可以使用自定義方法了,新增單元測試案例。
/**
* 測試<Specification>SQL動態查詢
*/
@SuppressWarnings("rawtypes")
@Test
public void test_Jpa_Specification_SQL() {
//返回結果為Map
String sql = "SELECT u.id as 'id',u.real_name as 'realName' FROM P_USER u WHERE u.available = ?";
List<Object> queryParams = new ArrayList<Object>();
queryParams.add(true);
List<Map> results = userRepository.sqlFindMap(sql, queryParams);
Assert.assertNotNull(results);
for(Map u : results){
System.out.println(u+", ");
}
System.out.println("===========================");
//返回結果為Object
List<User> users = userRepository.sqlFindAll(sql, User.class,queryParams);
Assert.assertNotNull(users);
for(User u : users){
System.out.print(u.getId()+"/"+u.getRealName()+", ");
}
System.out.println();
System.out.println("===========================");
//SQL查詢分頁資料
int index = 0;
int pageSize = 3;
Page<User> page = userRepository.sqlFindPage(sql, User.class,index,pageSize,queryParams);
Assert.assertNotNull(page);
System.out.println("總條數:"+page.getTotalElements());
System.out.println("總頁數:"+page.getTotalPages());
System.out.println("當前頁數:"+page.getNumber());
System.out.println("當前頁記錄數:"+page.getNumberOfElements());
System.out.print("當前頁資料內容:");
for(User u : page.getContent()){
System.out.print(u.getId()+"/"+u.getRealName()+", ");
}
System.out.println();
System.out.println("===========================");
//資料操作
long id = 84L;
String sql2 = "DELETE FROM P_USER WHERE id = ? ";
queryParams = new ArrayList<Object>();
queryParams.add(id);
int result = userRepository.sqlExcute(sql2, queryParams);
Assert.assertSame(result, 1);
}
回顧總結,jpa可以優雅的解決單表查詢和操作問題,簡單固定查詢條件使用JpaRepository介面方法,動態查詢使用JpaSpecificationExecutor介面方法,但是多表問題還是需要使用原生sql來處理,需要擴充套件jpa預設的JpaRepository介面。JPQL語言建議直接放棄,為了小概率的資料庫切換事件,增加額外的學習成本和未知問題的風險,實在不是一個明智的選擇。