架構小白到磚家-10-【數據存儲問題】-JpaRepository支持原生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語言建議直接放棄,爲了小概率的數據庫切換事件,增加額外的學習成本和未知問題的風險,實在不是一個明智的選擇。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章