- 业务场景
同一字段有多个枚举值且要支持复选的情况。例如:充电线接口类型有USB、Type-C、Lightning这三种类型,现在有多款充电线产品,有单接口的,有双接口的,也有三合一。则对于产品支持接口类型这一属性该怎么存储呢。
- 常用方法
常用方法:
- 将三种类型对应枚举值1 USB、2 Type-C、3 Lightning,然后再库中存储以特定字符分割的枚举值串,如三合一充电线会存储1,2,3。
2、创建一个属性表,该表将一个产品的一个接口类型作为一条数据(主键id,产品id,属性枚举值),如三合一充电线会存储三条数据。
- 推荐方法
主要思想:采用位运算的方法(位运算可参照下方5、位运算介绍)。将每一个枚举值对应为二进制数列上的一位,然后通过与运算来判断该枚举值对应的选项是否被选中。
方法步骤:
- 将枚举类和枚举值对应,因为要基于二进制与运算来判断是否存在,所以枚举值需要是2的整次幂。枚举值可定义为以下:1 USB、2 Type-C、4 Lightning
- 数据库定义充电线接口类型为int型,存储的值应为所有该充电线支持的接口类型的枚举值之和。如下图:
正在上传…重新上传取消
- 对于2中的数据进行常用几种查询方法如下
1、输入一个枚举类型,查询出包含该枚举类型的数据(以包含USB为例)语句如下:
SELECT * FROM line_interface WHERE interface_type&1 = 1 或
SELECT * FROM line_interface WHERE interface_type&1 > 0
Sql解读:传入USB的枚举值是1,则与库中的各列接口类型值做&运算,因为接口类型中包含USB的话,这个接口类型值转为二进制后在最低位上值为1和1做&后值必然为1,反之亦然。
2、输入多个枚举类型,查询出同时包含输入的枚举类型的数据(以包含USB、Lightning为例)语句如下:
SELECT * FROM line_interface WHERE interface_type&5 = 5
Sql解读:传入的枚举值和是5,则与库中的各列接口类型值做&运算,因为接口类型中包含USB、Lightning的话,这个接口类型值转为二进制后在最低位和从低位开始的第三位上值为1和5做&后值可能为5、4、1,只有满足值为5的才是完全匹配。
3、输入多个枚举类型,查询出包含一个或多个输入的枚举类型的数据(以包含USB、Lightning为例)语句如下:
SELECT * FROM line_interface WHERE interface_type&5 > 0
Sql解读:传入的枚举值和是5,则与库中的各列接口类型值做&运算,因为接口类型中包含USB、Lightning的话,这个接口类型值转为二进制后在最低位或从低位开始的第三位上值为1和5做&后值可能为5、4、1,值只要是其中之一就是满足条件的。
- 枚举类和工具类
- 枚举抽象类,提供获取枚举类中定义的名字和值的方法。
public interface EnumBase {
/**
* @return Enum中定义的名字
*/
String getName();
/**
* @return Enum中定义的索引
*/
Integer getIndex();
}
- 枚举类,其中定义了具体的枚举类型和枚举值对应关系。
public enum EnumObject implements EnumBase {
USB("USB",1),
TYPEC("Type-C",2),
LIGHTNING("Lightning",4);
private Integer index;
private String name;
EnumObject(String name, Integer index) {
this.name = name;
this.index = index;
}
@Override
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 枚举工具类,主要提供枚举类型和枚举值之和相互转化的方法。
public class EnumUtils {
/**
* 传入枚举值之和,获取枚举类型集合
* @param clazz 枚举类
* @param valueTotal 枚举值之和
* @param <T>
* @return
*/
public static<T extends EnumBase> List<String> getNameList(Class<T> clazz,Integer valueTotal){
List<String> list = new ArrayList<>();
if(clazz.isEnum()){
for(T each:clazz.getEnumConstants()){
if((valueTotal&(each.getIndex()))>0){
list.add(each.getName());
}
}
}else {
throw new RuntimeException("参数类型不是枚举类!");
}
return list;
}
/**
* 传入枚举值之和,获取枚举值集合
* @param clazz 枚举类
* @param valueTotal 枚举值之和
* @param <T>
* @return
*/
public static<T extends EnumBase> List<Integer> getValueList(Class<T> clazz,Integer valueTotal){
List<Integer> list = new ArrayList<>();
if(clazz.isEnum()){
for(T each:clazz.getEnumConstants()){
if((valueTotal&(each.getIndex()))>0){
list.add(each.getIndex());
}
}
}else {
throw new RuntimeException("参数类型不是枚举类!");
}
return list;
}
/**
* 根据传入的枚举类型集合,计算枚举值之和
* @param clazz 枚举类
* @param list 枚举类型集合
* @param <T>
* @return
*/
public static<T extends EnumBase> Integer getValueTotal(Class<T> clazz,List<String> list){
Integer valueTotal = 0;
if(clazz.isEnum()){
for(T each:clazz.getEnumConstants()){
for (int i = 0;i<list.size();i++ ) {
if(Objects.equals(each.getName(),list.get(i))){
valueTotal += each.getIndex();
}
}
}
}else {
throw new RuntimeException("参数类型不是枚举类!");
}
return valueTotal;
}
/**
* 根据传入的枚举类型集合,获取not in的枚举值之和
* @param clazz
* @param list
* @param <T>
* @return
*/
public static<T extends EnumBase> Integer getValueTotalNotIn(Class<T> clazz,List<String> list){
Integer valueTotal = 0;
Integer allTotal = 0;
if(clazz.isEnum()){
for(T each:clazz.getEnumConstants()){
allTotal += each.getIndex();
for (int i = 0;i<list.size();i++ ) {
if(Objects.equals(each.getName(),list.get(i))){
valueTotal += each.getIndex();
}
}
}
}else {
throw new RuntimeException("参数类型不是枚举类!");
}
return allTotal^valueTotal;
}
/**
* 根据传入的枚举值之和,获取not in的枚举值之和
* @param clazz
* @param valueTotal
* @param <T>
* @return
*/
public static<T extends EnumBase> Integer getValueTotalNotIn(Class<T> clazz,Integer valueTotal){
Integer allTotal = 0;
if(clazz.isEnum()){
for(T each:clazz.getEnumConstants()){
allTotal += each.getIndex();
}
}else {
throw new RuntimeException("参数类型不是枚举类!");
}
return allTotal^valueTotal;
}
}
- 测试类
public class EnumTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add(EnumObject.LIGHTNING.getName());
//根据传入的枚举类型集合,计算枚举值之和
System.out.println(EnumUtils.getValueTotal(EnumObject.class,list));
//根据传入的枚举类型集合,获取not in的枚举值之和
System.out.println(EnumUtils.getValueTotalNotIn(EnumObject.class,list));
//根据传入的枚举值之和,获取not in的枚举值之和
System.out.println(EnumUtils.getValueTotalNotIn(EnumObject.class,7));
//传入枚举值之和,获取枚举类型集合
System.out.println(EnumUtils.getNameList(EnumObject.class,7));
//传入枚举值之和,获取枚举值集合
System.out.println(EnumUtils.getValueList(EnumObject.class,3));
}
}
- 优劣点
优点:
- 代码友好,不会存在如常规方法一中,处理需要截取这种情况。
- 不需要像方法二中增加表,降低维护成本。
- 不需要复杂的SQL关联与处理参数的函数使用。
- 存储空间相对节省,能满足枚举值不是超大量的情况。
- 位运算效率相对较高。
- 通过工具类,只要定义枚举类,通过工具类基本能实现所有业务场景。
缺点:
- 枚举值数量有一定限制。
- SQL查看表数据,枚举值展示不够直观。
- 需要了解位运算相关知识。
- 位运算介绍
程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。
首先是将十进制转为二进制,java中转换方法Integer.toBinaryString(),十进制转换为二进制逻辑上就是对十进制数进行除以2操作,有余记余无余记零。以整数10为例,10除以2得5无余记0,5除以2得2有余记1,2除以2得1无余记0,1不足以除以2就记为1,因为出的次数越多,在二进制中占位越高,所以将记得数由后到前整理得出10的对应的二进制数为1010。
位运算是对于二进制数进行的逻辑运算,java提供的位运算有以下几种:左移( << )、右移( >> ) 、无符号右移( >>> ) 、位与( & ) 、位或( | )、位非( ~ )、位异或( ^ ),除了位非( ~ )是一元操作符外,其它的都是二元操作符。而我们针对本次用到的与、或、异或进行简单介绍。
与运算,使二进制数低位对齐,上下两数各对应位都为1则结果为1,否则为0。以下为12和9做与运算结果为8
12转换为二进制:0000 0000 0000 0000 0000 0000 0000 1100
9转换为二进制:0000 0000 0000 0000 0000 0000 0000 1001
-------------------------------------------------------------------------------------
8转换为二进制:0000 0000 0000 0000 0000 0000 0000 1000
或运算,使二进制数低位对齐,上下两数各对应位有一个为1则结果为1,否则为0。以下为12和9做或运算结果为13
12转换为二进制:0000 0000 0000 0000 0000 0000 0000 1100
9转换为二进制:0000 0000 0000 0000 0000 0000 0000 1001
-------------------------------------------------------------------------------------
8转换为二进制:0000 0000 0000 0000 0000 0000 0000 1101
异或运算,使二进制数低位对齐,上下两数各对应位数字不同则结果为1,否则为0。以下为12和9做或运算结果为5
12转换为二进制:0000 0000 0000 0000 0000 0000 0000 1100
9转换为二进制:0000 0000 0000 0000 0000 0000 0000 1001
-------------------------------------------------------------------------------------
8转换为二进制:0000 0000 0000 0000 0000 0000 0000 0101