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)技术多多实践啦,把业务和技术结合起来才能解决实际问题哦~

说在最后的话:编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

发布了142 篇原创文章 · 获赞 499 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/zeal9s/article/details/100010263