由于碰到了这样的需求,我又有点设计强迫症,故研究了一下。灵感来自我之前写的一个小工具 pypadb。实际上这种方法只适用于简单地查个数据然后返回给前端,要做修改的话还是需要判断查出来的类型再做强制类型转换。

业务需求:根据前端传的一个代表数据种类的字符串和若干查询参数来查询不同表里的数据。

一般来说可以用 if else 判断字符串来处理,但是这样在表比较多的时候会造成 if else 过多,而且 if else 里面还有可能嵌套其他的 if else,看着就头大。所以这次来介绍一个用枚举类来优化 if else 分支的方法。

使用了 Mybatis-Pluslombok 简化演示的代码,Spring Boot Starter Test 用来单元测试。

预设

先上表结构(预先填了一列数据):

表 a
+-----+--------+
| id  | data_a |
+-----+--------+
| 123 | aaaaaa |
+-----+--------+

表 b
+-----+--------+
| id  | data_b |
+-----+--------+
| 123 | bbbbbb |
+-----+--------+

实体类:

@Data
@TableName("a")
public class AEntity {
    @TableId
    String id;
    String dataA;
}

@Data
@TableName("b")
public class BEntity {
    @TableId
    String id;
    String dataB;
}

由于只是演示作用,就没写 Service 层了。实际开发中可以让所有 Service 或 Dao 都实现或继承一个接口,然后实现接口里的查询方法。

@Mapper
public interface ADao extends BaseMapper<AEntity> {
}

@Mapper
public interface BDao extends BaseMapper<BEntity> {
}

关键代码

枚举类:

public enum TableEnum {
    a(ADao.class),
    b(BDao.class);

    private Class daoClass;

    private volatile BaseMapper dao;

    TableEnum(Class daoClass) {
        this.daoClass = daoClass;
    }

    public BaseMapper getDao() {
        if (dao == null) {
            synchronized (TableEnum.class) {
                if (dao == null) {
                    dao = (BaseMapper) IOCConfig.getBean(daoClass);
                }
            }
        }
        return dao;
    }
}

用到的单例模式加上了双重验证来防止并发问题。

对应的单元测试:

@SpringBootTest
class BlogdemosApplicationTests {
    public static String a = "a";
    public static String b = "b";
    public static String id = "123";

    @Test
    void test1() {
        Object o1 = TableEnum.valueOf(a).getDao().selectById(id);
        Object o2 = TableEnum.valueOf(b).getDao().selectById(id);

        System.out.println("------- o1 -------");
        System.out.println(JSONObject.toJSONString(o1, true));
        System.out.println("------- o2 -------");
        System.out.println(JSONObject.toJSONString(o2, true));

        // 要修改查出来的数据只能判断类型然后强转。
        if (o1 instanceof AEntity) {
            System.out.println("o1 instance AEnitty");
        }
        if (o2 instanceof BEntity) {
            System.out.println("o2 instance BEnitty");
        }
    }
}

一些思考

关于查出来的数据只能是 Object 类型这个问题,我想了一下,包括使用带泛型的普通类来模拟枚举类的行为,然后想通过反射来获取 BaseMapper 里对应的泛型,返回正确的类型。但是没想到怎么写。从宏观的角度看,想要解决这个问题,Jvm 就需要根据一个不知道是什么内容的字符串确定一个类型,静态类型语言的 Java 好像做不到这一点。

使用一般类来模拟枚举类的行为:

@Slf4j
public class Tables {
    private static Map<String, Class> classMap = new ConcurrentHashMap<>();
    private static Map<String, BaseMapper> daoMap = new ConcurrentHashMap<>();

    private static void initClassMap(Object... args) {
        if (args == null || args.length < 2 || (args.length & 1) == 1) {
            log.error("Invalid arguments.");
            return;
        }
        for (int i = 0; i < args.length; i += 2) {
            classMap.put((String) args[i], (Class) args[i + 1]);
        }
    }

    static {
        initClassMap(
                "a", ADao.class,
                "b", BDao.class
        );
    }

    public static BaseMapper valueOf(String table) {
        if (!classMap.containsKey(table)) {
            return null;
        }
        BaseMapper dao = daoMap.get(table);
        if (dao == null) {
            dao = (BaseMapper) IOCConfig.getBean(classMap.get(table));
            daoMap.put(table, dao);
        }
        return dao;
    }
}

不像枚举类,一般类可以一步就拿到对应的 Dao,只不过后期想要修改或添加其他的表需要改 static 代码块里面的代码,相比于枚举类来说少了一点优雅。并发问题就交给 ConcurrentHashMap 来解决。

2022-03-25