Mybatis自定義Sql

前言:近日遇到很複雜的業務邏輯需要處理(每每這個時候博主經常吐槽自己腦子不夠用了😂),數據庫查詢需要通過不同業務不同條件進行動態拼接,而且涉及數據量巨大,表也關聯的多,這時候明顯xml不適合這個場景,雖然說可以做到,但是麻煩程度絕對大於我接下來的做法,然後不經意間發現下面這種寫法,發現Mybatis挺靈活的。有①xml方式(常用)、②普通註解(@Select、@Insert等),③高級註解(@SelectProvider、@InsertProvider等)④SQL語句構建器類http://www.mybatis.org/mybatis-3/zh/statement-builders.html
…還有其它的方式等我去研究發現嘿嘿,言歸正傳。

假設需求根據給出的條件(此條件不固定),然後關聯幾張表,給我查出我需要的字段(視圖)。看似很簡單的需求,實則深似海哈哈哈

下面貼出代碼(看不太懂的,下面有解釋說明)

/**
* 根據移庫規則表檢查該數據是否可自動生成任務
*
* @param warehouseTransferRuleConfig
* @return
*/
@SelectProvider(type = WarehouseTransferRuleConfigMapperProvider.class, method = "canAutoCreateSourceDetailList")
List<SourceDetail> canAutoCreateSourceDetailList(WarehouseTransferRuleConfig warehouseTransferRuleConfig,String deviceTypeCodeById,String specById);

class WarehouseTransferRuleConfigMapperProvider {

   /**
    * 用於複雜sql查詢提供sql拼接
    *
    * @param
    * @return
    */
   public String canAutoCreateSourceDetailList(WarehouseTransferRuleConfig warehouseTransferRuleConfig,String deviceTypeCodeById,String specById) {
       //根據規則表數據拼接查詢一機一檔的sql

       StringBuilder sql = new StringBuilder("select material_number,vehicle_vin_number,device_code,device_type_code,bl_division_code,region_code,device_model_code,device_spec_code,depositary_officer,storage_area,storage_province,storage_city,storage_address,placement_note,dispose_status,first_handover_date,handover_address_type from(select material_number,vehicle_vin_number,device_code,device_type_code,bl_division_code,region_code,device_model_code,device_spec_code,depositary_officer,storage_area,storage_province,storage_city,storage_address,placement_note,dispose_status,first_handover_date,handover_address_type from rmp_source_detail s where first_handover_status='Y' and depositary_officer is not null and handover_address_type is not null and bl_division_code is not null and device_spec_code is not null and dispose_status='01' and is_active='Y'");
       if (warehouseTransferRuleConfig != null) {
           //當前日期-定價日期>7
           sql.append(" and to_days(now()) - to_days(device_pricing_date)>7");
           //設備規格
           if (StringUtils.isNotBlank(specById)) {
               String specStr="";
               String[] split = specById.split(",");
               for (int i = 0; i < split.length; i++) {
                   specStr=specStr+"'"+split[i]+"',";
               }
               specStr=specStr+specStr.substring(0,specStr.length()-1);
               sql.append(" and device_spec_code in(" + specStr + ")");
           }
           //如未配置設備規格,則以設備類型爲條件
           if (StringUtils.isBlank(specById)) {
               String deviceTypeCodeSql="(select ds.device_type_code from(select device_type_code from rmp_warehouse_transfer_spec_config where id="+warehouseTransferRuleConfig.getRuleCode()+" and is_active='Y' limit 1)as ds)";
               sql.append(" and device_type_code in(" + deviceTypeCodeSql + ")");
           }
           //過戶判斷
           if (warehouseTransferRuleConfig.getTransferJudge() != null) {
               sql.append(" and pass_judge=" + warehouseTransferRuleConfig.getTransferJudge());
           }
           //存放地類型
           if (warehouseTransferRuleConfig.getHandoverAddressType() != null) {
               sql.append(" and handover_address_type in(" + warehouseTransferRuleConfig.getHandoverAddressType() + ")");
           }
           sql=sql.append(") AS ts ");

           //獲取預計整修成本的sql(不能爲0)
           String estimateRepairCostsSql = "(select if(repair_budget='0',null,repair_budget)repair_budget from rmp_source_detail where material_number=ts.material_number)";
           //獲取市場價的sql(不能爲0)
           String marketPriceSql = "(select if(market_price='0',null,market_price)market_price from rmp3_price_adjustment_history_sheet where material_number=ts.material_number ORDER BY create_date DESC LIMIT 1)";

           sql.append(" where ts.material_number in (select material_number from rmp_source_detail where 1=1  ");
           //預計整修成本/市場全款價(區間始%)
           if (warehouseTransferRuleConfig.getIntervalValueStart() != null) {
               sql.append(" and repair_budget/" + estimateRepairCostsSql + ">" + (warehouseTransferRuleConfig.getIntervalValueStart() / 100));
           }
           //預計整修成本/市場全款價(區間止%)
           if (warehouseTransferRuleConfig.getIntervalValueEnd() != null) {
               sql.append(" and repair_budget/" + marketPriceSql + "<=" + (warehouseTransferRuleConfig.getIntervalValueEnd() / 100));
           }


           //條件(and or)
           if (StringUtils.isNotBlank(warehouseTransferRuleConfig.getConditions())) {
               sql.append(" " + warehouseTransferRuleConfig.getConditions() + " ");
           }

           if (warehouseTransferRuleConfig.getRepairCostsStart() != null || warehouseTransferRuleConfig.getRepairCostsEnd() != null) {
               sql.append("(");

               //預計整修成本始(萬)
               if (warehouseTransferRuleConfig.getRepairCostsStart() != null) {
                   sql.append(" repair_budget>" + warehouseTransferRuleConfig.getRepairCostsStart());
               }
               //預計整修成本止(萬)
               if (warehouseTransferRuleConfig.getRepairCostsEnd() != null && warehouseTransferRuleConfig.getRepairCostsStart() != null) {
                   sql.append(" and repair_budget<=" + warehouseTransferRuleConfig.getRepairCostsEnd());
               }
               if (warehouseTransferRuleConfig.getRepairCostsEnd() != null && warehouseTransferRuleConfig.getRepairCostsStart() == null) {
                   sql.append(" repair_budget<=" + warehouseTransferRuleConfig.getRepairCostsEnd());
               }

               sql.append(")");
           }

           sql.append(")");

           System.out.println("打印sql=" + sql.toString());

       }
       return sql.toString();
   }
}

後臺打印的sql語句:

SELECT
	material_number,
	vehicle_vin_number,
	device_code,
	device_type_code,
	bl_division_code,
	region_code,
	device_model_code,
	device_spec_code,
	depositary_officer,
	storage_area,
	storage_province,
	storage_city,
	storage_address,
	placement_note,
	dispose_status,
	first_handover_date,
	handover_address_type 
FROM
	(
	SELECT
		material_number,
		vehicle_vin_number,
		device_code,
		device_type_code,
		bl_division_code,
		region_code,
		device_model_code,
		device_spec_code,
		depositary_officer,
		storage_area,
		storage_province,
		storage_city,
		storage_address,
		placement_note,
		dispose_status,
		first_handover_date,
		handover_address_type 
	FROM
		rmp_source_detail s 
	WHERE
		first_handover_status = 'Y' 
		AND depositary_officer IS NOT NULL 
		AND handover_address_type IS NOT NULL 
		AND bl_division_code IS NOT NULL 
		AND device_spec_code IS NOT NULL 
		AND dispose_status = '01' 
		AND is_active = 'Y' 
		AND to_days(
		now()) - to_days( device_pricing_date )> 7 
		AND device_spec_code IN (
			'50米-3橋-奔馳-國三',
			'50米-4橋-五十鈴-國三',
			'50米-4橋-奔馳-國三',
			'50米-4橋-日野-國三',
			'52米-3橋-奔馳-國五',
			'52米-4橋-五十鈴-國三',
			'52米-4橋-奔馳-國三',
			'52米-4橋-奔馳-國四',
			'52米-4橋-斯堪尼亞-國三',
			'52米-4橋-日野-國三',
			'53米-4橋-奔馳-國五',
			'53米-4橋-斯堪尼亞-國五',
			'54米-4橋-斯堪尼亞-國三',
			'56米-4橋-奔馳-國三',
			'56米-4橋-奔馳-國五',
			'56米-4橋-奔馳-國四',
			'56米-4橋-斯堪尼亞-國三',
			'56米-4橋-斯堪尼亞-國五',
			'56米-4橋-斯堪尼亞-國四',
			'56米-4橋-解放-國五',
			'56米-碳纖維-3橋-奔馳-國四',
			'58米-5橋-斯堪尼亞-國三',
			'58米-5橋-斯堪尼亞-國五',
			'50米-3橋-奔馳-國三',
			'50米-4橋-五十鈴-國三',
			'50米-4橋-奔馳-國三',
			'50米-4橋-日野-國三',
			'52米-3橋-奔馳-國五',
			'52米-4橋-五十鈴-國三',
			'52米-4橋-奔馳-國三',
			'52米-4橋-奔馳-國四',
			'52米-4橋-斯堪尼亞-國三',
			'52米-4橋-日野-國三',
			'53米-4橋-奔馳-國五',
			'53米-4橋-斯堪尼亞-國五',
			'54米-4橋-斯堪尼亞-國三',
			'56米-4橋-奔馳-國三',
			'56米-4橋-奔馳-國五',
			'56米-4橋-奔馳-國四',
			'56米-4橋-斯堪尼亞-國三',
			'56米-4橋-斯堪尼亞-國五',
			'56米-4橋-斯堪尼亞-國四',
			'56米-4橋-解放-國五',
			'56米-碳纖維-3橋-奔馳-國四',
			'58米-5橋-斯堪尼亞-國三',
			'58米-5橋-斯堪尼亞-國五' 
		) 
	AND handover_address_type IN ( 1, 2, 4 )) AS ts 
WHERE
	ts.material_number IN (
	SELECT
		material_number 
	FROM
		rmp_source_detail 
	WHERE
		1 = 1 
		AND repair_budget /(
		SELECT
		IF
			( market_price = '0', NULL, market_price ) market_price 
		FROM
			rmp3_price_adjustment_history_sheet 
		WHERE
			material_number = ts.material_number 
		ORDER BY
			create_date DESC 
			LIMIT 1 
		)<= 0.3 
	AND ( repair_budget <= NULL ))

看到打印的sql大家應該也遇到同樣的需求,需要負責的查詢SQL或者新增的SQL吧,都可以採用上述方式👆

解釋說明和注意事項:

1)爲什麼不推薦這個時候使用xml呢?其實xml也可以達到效果,但是靈活性不高,例如for循環裏面切割字符再次拼接,而且編輯sql時容易出錯,項目改動xml文件還要重新啓動,而重新加載類可以不用重新啓動,搭配Jrebel熱加載使用,不知道熱加載的可以參考這篇文章https://blog.csdn.net/zeal9s/article/details/84204812
雖然說debug也可以做到實時部署,但是不穩定,而且遇到springboot項目,還要添加依賴才能實現,種種綜合來看xml方式在此不適合

2)

@SelectProvider提供自定義sql的註解
type:自定義SQL的類型(在這裏就是提供查詢的那個內部類WarehouseTransferRuleConfigMapperProvider)
method:該類的具體哪個方法提供sql

3)註解下面一行代表你需要的數據(數據字段不確定,可以用Map、List或者自定義Dto層接收)和參數(參數不確定建議用Map接受,或者List等,如多個參數,可在參數前面加@param註解,例如@param String sex,@param int age)但是我的沒加也能正常運作,可不加

   List<SourceDetail> canAutoCreateSourceDetailList(WarehouseTransferRuleConfig warehouseTransferRuleConfig,String deviceTypeCodeById,String specById);

4)下面的類是Mapper層的內部類,注意 在此類不能注入其它mapper或者service對象,會導致注入對象失敗(已踩坑,具體原因尚且不知道),這也意味着所有的sql和邏輯層,必須在此類完成

 class WarehouseTransferRuleConfigMapperProvider {
 ....
 }

5)內部類中的方法名必須和註解@SelectProvider的說明保持一致,不然引用失敗

6)避免sql出現拼寫錯誤,最好初始化一個簡單查詢寫在最前面和最後面,。。。。。。
處再去處理複雜SQL拼接,例如

StringBuilder sql = new StringBuilder("select material_number,vehicle_vin_number,device_code,device_type_code,bl_division_code,region_code,device_model_code,device_spec_code,depositary_officer,storage_area,storage_province,storage_city,storage_address,placement_note,dispose_status,first_handover_date,handover_address_type from(select material_number,vehicle_vin_number,device_code,device_type_code,bl_division_code,region_code,device_model_code,device_spec_code,depositary_officer,storage_area,storage_province,storage_city,storage_address,placement_note,dispose_status,first_handover_date,handover_address_type from rmp_source_detail s where first_handover_status='Y' and depositary_officer is not null and handover_address_type is not null and bl_division_code is not null and device_spec_code is not null and dispose_status='01' and is_active='Y'");
。。。。。。
sql = sql.append(")");

7)這裏採用StringBuilder是提高拼接效率,也可以使用String方式的+=或者concat拼接

8)最後最好打印一下sql,方便排查sql錯誤可複製到數據庫運行

9)紀念多次子查詢博主踩的坑
①子查詢獲取最外層查詢的值:可以將最外層的查詢再包裹一層,給這個視圖取個別名,子查詢使用別名.字段的方式獲取,如果直接獲取最外層數據你要想想sql的執行順序哦)
②子查詢不能使用limit關鍵字查詢(本來是想用limit提高查詢效率,sql本身沒錯不料執行出錯,解決方法請看https://blog.csdn.net/zeal9s/article/details/99739003
③使用關鍵詞in查詢時,出現數據偏差,原因是拼接這個sql時

AND device_spec_code IN (
			'50米-3橋-奔馳-國三',
			'50米-4橋-五十鈴-國三',
			'50米-4橋-奔馳-國三',
			'50米-4橋-日野-國三',
			'52米-3橋-奔馳-國五',
		) 

,java代碼是

sql = sql.append(" and device_spec_code in(" + specStr + ")");

specStr作爲字符串變量,它的值是沒有加上引號的,所以拼接的時候要重新加上引號,不加引號sql不會報錯,但是數據不對

50米-3橋-奔馳-國三,50米-4橋-五十鈴-國三

④拼接字符串時一定要注意變量重新賦值,也可以採用concat和StringBuilder的append方法(不用重新賦值)

specStr=specStr+specStr.substring(0,specStr.length()-1);  ✔️

specStr.substring(0,specStr.length()-1);

10)每一次拼接的註釋最好寫上,方便維護。防止幾天之後,自己都不知道這寫的什麼,又要重新浪費時間閱讀代碼

12)除了查詢@SelectProvider還有@InsertProvider等用法可研究

11)技術多多實踐啦,把業務和技術結合起來才能解決實際問題哦~

說在最後的話:編寫實屬不易,若喜歡或者對你有幫助記得點贊+關注或者收藏哦~

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