文章目录
- 前言
- 问题
- 目标
- 现存的状态
- 思路
- 三大状态
- 状态计算(1)
- 状态计算(2)
- 工具类
- 示例
前言
往往项目中有一些类似于订单类的数据中有很多状态相关的流转操作,这些时候有可能因为某些业务逻辑要对状态进行范围查询或者多值匹配、排除之类的操作。
问题
在进行数据查询时由于过多的状态导致在查询一系列有关状态的数据时会包括或者排除很多状态值,造成代码书写复杂,并且会拖慢查询效率,例如如下的sql操作:
select * from table where state in ('init', 'reject', 'success')
or
select * from table where state not in ('init', 'reject', 'success')
目标
优化状态结构或者对状态进行分组汇总。
工作书写时减少必要的繁琐工作。
提高数据库的查询效率。
工具类通过将想要包括或者排除的状态作为参数,返回分组后的状态
现存的状态
目前存在的状态:
INIT("INIT","初始阶段"),
REAL_NAME("REAL_NAME","实名阶段"),
REAL_NAME_REJECT("REAL_NAME_REJECT","未实名"),
RISK("RISK", "风控阶段"),
RISK_REJECT("RISK_REJECT", "风控拒绝"),
RISK_FEATURE("RISK_FEATURE", "风控策略"),
RISK_WORKFLOW_SUBMIT("CASHBACK_RISK_WORKFLOW_SUBMIT", "风控工作流提交"),
RISK_WORKFLOW_QUERY("CASHBACK_RISK_WORKFLOW_QUERY", "风控工作流结果查询"),
RISK_FEATUR_REJECT("RISK_FEATURE_REJECT", "风控策略拒绝")
思路
三大状态
所有的状态总结来说订单存在三大状态:初始化 -> 过程中 -> 结束
。
数据库层面 在现有的数据库表中新增一列 pack_state
,用来存储 state
分组后的状态:
init
、 process
、 end
代码层面 根据业务查询 pack_state
或者 state
。
思路比较简单,分组情况少,但是结构和分组固定,有一定的局限性。比如不能随意组合,只能根据这三大状态进行查询,相当于缩小版的
state
。
状态计算(1)
通过对所有状态规定一个顺序,例如:
INIT("1","INIT","初始阶段"),
REAL_NAME("2","REAL_NAME","实名阶段"),
REAL_NAME_REJECT("3","REAL_NAME_REJECT","未实名"),
RISK("4","RISK", "风控阶段"),
RISK_REJECT("5","RISK_REJECT", "风控拒绝"),
RISK_FEATURE("6","RISK_FEATURE", "风控策略"),
RISK_WORKFLOW_SUBMIT("7","CASHBACK_RISK_WORKFLOW_SUBMIT", "风控工作流提交"),
RISK_WORKFLOW_QUERY("8","CASHBACK_RISK_WORKFLOW_QUERY", "风控工作流结果查询"),
RISK_FEATUR_REJECT("9","RISK_FEATURE_REJECT", "风控策略拒绝")
数据库层面创建数据库一列 pack_state
,类型为 bit(16)
,即状态类型的顺序则规定了自己在二进制的位置。具体表现为如下方式:
- INIT(“1”,“INIT”,“初始阶段”) -->
0b0000 0000 0000 0001
- RISK(“4”,“RISK”, “风控阶段”) -->
0b0000 0000 0000 1000
- …
查询时如下操作:
where pack_state & #{type}
代码层面这些状态可以随意组合,写一个小工具来进行组合,传入的参数就是要查询的状态,根据顺序进行编排,例如:0b0000 0000 0000 1001
就是要查询 "init"、"risk"
类型的数据。
延伸拓展若要查询非 "init"、"risk"
则对该类型进行排除
状态计算(2)
实现方式同 状态计算(1)。
数据库层面创建数据库一列 pack_state
,类型为 int()
,具体表示为如下方式:
- CASHBACK_INIT(“1”,“INIT”,“初始阶段(暂未使用)”) -->
1
- CASHBACK_RISK(“4”,“RISK”, “风控阶段”) -->
8
- …
查询时利用mysql的函数:
# 传入二进制
where BIN(pack_state) & #{type}
#或者
# 传入十进制
where BIN(pack_state) & BIN(#{type})
代码层面同样可以随意组合,写一个小工具来进行组合,传入的参数就是要查询的状态,根据顺序进行编排,例如:0b0000 0000 0000 0000 0000 0000 0000 1001
那么返回的结果就是 9
, 就是要查询 "init"、"risk"
类型的数据。
注意:返回的结果和状态的排序结果没有不然联系,仅仅是根据需要查询的状态组成的二进制转换成的十进制数罢了。
这两种状态计算的好处就是能够将之前的范围查询和多值匹配变成了定值使用位运算查询,不好的一点就是需要做文档备注维护列内容的描述,列的值不是很好理解,没有做到一目了然。
有另外一种解法就是将顺序和状态维护在字典或者数据库里面,便于找对一个关系。
工具类
package com.xiaoju.manhattan.global.credit.card.process;
import java.util.Arrays;
import java.util.stream.Collectors;
public class StateConvertUtil {
public static void main(String[] args) {
System.out.println(genBinCodeByStatusList(CashBackStateEnum.CASHBACK_INIT, CashBackStateEnum.CASHBACK_RISK));
System.out.println(" =======================================");
System.out.println(genBinCodeByStatusList(false, CashBackStateEnum.CASHBACK_INIT, CashBackStateEnum.CASHBACK_RISK));
System.out.println(" =======================================");
System.out.println(genDecCodeByStatusList(CashBackStateEnum.CASHBACK_INIT, CashBackStateEnum.CASHBACK_RISK));
System.out.println(" =======================================");
System.out.println(genDecCodeByStatusList(false, CashBackStateEnum.CASHBACK_INIT, CashBackStateEnum.CASHBACK_RISK));
System.out.println(" =======================================");
}
/**
* @Author: zhou
* @Description: 工具类日志打印
* @Date: 2024/1/16 11:17 AM
* @Param: include 包含or排除
* @Param: binary 二进制or十进制
* @Param: cashBackStateEnums
* @return: void
*/
private static void logSys(boolean include, boolean binary, CashBackStateEnum[] cashBackStateEnums) {
System.out.printf("是否包含:%s,类型:%s,类型:%s%n", include? "是":"否", binary? "二进制":"十进制", Arrays.stream(cashBackStateEnums).map(item -> item.code).collect(Collectors.joining(",")));
}
/**
* @Author: zhou
* @Description: 根据配置和状态枚举生成十进制code
* @Date: 2024/1/16 11:13 AM
* @Param: cashBackStateEnums 状态枚举
* @return: java.lang.String
*/
public static String genDecCodeByStatusList(CashBackStateEnum ... cashBackStateEnums) {
logSys(true, false, cashBackStateEnums);
return genCodeByStatusList(true, false, cashBackStateEnums);
}
/**
* @Author: zhou
* @Description: 根据配置和状态枚举生成十进制code
* @Date: 2024/1/16 11:19 AM
* @Param: include 包含or排除
* @Param: cashBackStateEnums 状态枚举
* @return: java.lang.String
*/
public static String genDecCodeByStatusList(boolean include, CashBackStateEnum ... cashBackStateEnums) {
logSys(include, true, cashBackStateEnums);
return genCodeByStatusList(include, false, cashBackStateEnums);
}
/**
* @Author: zhou
* @Description: 根据配置和状态枚举生成二进制code
* @Date: 2024/1/16 11:20 AM
* @Param: cashBackStateEnums 状态枚举
* @return: java.lang.String
*/
public static String genBinCodeByStatusList(CashBackStateEnum ... cashBackStateEnums) {
logSys(true, true, cashBackStateEnums);
return genCodeByStatusList(true, true, cashBackStateEnums);
}
/**
* @Author: zhou
* @Description: 根据配置和状态枚举生成二进制code
* @Date: 2024/1/16 11:16 AM
* @Param: include 包含or排除
* @Param: cashBackStateEnums 状态枚举
* @return: java.lang.String
*/
public static String genBinCodeByStatusList(boolean include, CashBackStateEnum... cashBackStateEnums) {
logSys(include, true, cashBackStateEnums);
return genCodeByStatusList(include, true, cashBackStateEnums);
}
/**
* @Author: zhou
* @Description: 根据配置和状态枚举生成code
* @Date: 2024/1/16 11:12 AM
* @Param: include 包含or排除
* @Param: binary 二进制or十进制
* @Param: cashBackStateEnums 状态枚举
* @return: java.lang.String
*/
private static String genCodeByStatusList(Boolean include, Boolean binary, CashBackStateEnum ... cashBackStateEnums) {
int decResult = 0;
for (CashBackStateEnum anEnum : cashBackStateEnums) {
decResult += 1 << anEnum.order - 1;
}
System.out.println("结果:" + decResult);
if (binary ==null || binary) {
System.out.println("结果转二进制:" + Integer.toBinaryString(decResult));
}
if (include == null || !include) {
decResult = ~decResult;
System.out.println("结果取反:" + decResult);
if (binary == null || binary) {
System.out.printf("结果取反二进制:%s,长度:%d%n", Integer.toBinaryString(decResult), Integer.toBinaryString(decResult).length());
}
}
return (binary == null || binary)? Integer.toBinaryString(decResult):Integer.toString(decResult);
}
enum StateEnum {
INIT(1, "INIT", "初始阶段(暂未使用)"),
REAL_NAME(2, "REAL_NAME", "实名阶段"),
REAL_NAME_REJECT(3, "REAL_NAME_REJECT", "未实名"),
RISK(4, "RISK", "风控阶段"),
RISK_REJECT(5, "RISK_REJECT", "风控拒绝"),
RISK_FEATURE(6, "RISK_FEATURE", "风控策略"),
RISK_WORKFLOW_SUBMIT(7, "CASHBACK_RISK_WORKFLOW_SUBMIT", "风控工作流提交"),
RISK_WORKFLOW_QUERY(8, "CASHBACK_RISK_WORKFLOW_QUERY", "风控工作流结果查询"),
RISK_FEATUR_REJECT(9, "RISK_FEATURE_REJECT", "风控策略拒绝");
final int order;
final String code;
final String desc;
CashBackStateEnum(int order, String code, String desc) {
this.order = order;
this.code = code;
this.desc = desc;
}
public static StateEnum safeValueOf(String code) {
return Arrays.stream(StateEnum.values()).filter(
state -> state.getCode().equals(code)
).findFirst().orElse(null);
}
}
}
示例
对状态计算(1)做一个简单的示例:
- 针对表改造新增列
pack_state
,类型为bit(16)
。 - 根据对应关系我们存入
init、 risk、 risk_reject
状态的三条数据。 - 我们要查询包含这三个状态的数据,需要根据工具类生成二进制的code值为
11001
。 - 查询的sql:
select * from table where pack_state & #{pack_state}
。 - 查询的结果可以得到
init、 risk、 risk_reject
状态的三条数据。