大批量表单入库抽象模式

一、功能介绍

此模式并不属于设计模式的范畴(自封设计模式哈哈),想到该应用场景是由于我在接到新项目需求的时候,UI设计图中涉及到高达二十多种表单,为了“偷懒”,将表单数据入库数据进行抽象。假设每张表单会涉及到一张数据表,那么我们在开发的时候可能会去建立二十多个dao接口和二十多个mapper文件、二十多个sql,就算使用mybatis-plus,也是需要写二十多个dao接口的,更难受的是测试的时候需要测试二十多张表单的新增、回显等功能。解决方案就是将增删改查的接口全部抽象出来,达到的效果就是只需要一个方法就可以实现二十多张表单的提交、查询、修改和删除。

二、环境搭建

  1. mysql 5.7 + (涉及到虚拟列)
  2. mybatis-plus (主要使用到了它的两个注解,当然可以自己设计注解,下面有介绍)
  3. springboot

三、原理

1.数据库

数据库涉及到虚拟列的概念,只有5.7以上的版本才支持,也就是为什么需要mysql5.7 以上的版本。不知道什么是虚拟列的小伙伴可以自行查资料。
就那表car和person举例子:
在这里插入图片描述

id和data_info是实体字段,brand和price为虚拟字段,实体字段存数据,虚拟字段从实体字段中取数据。所有虚拟字段的数据以json的形式存入data_info中,虚拟字段以brand为例通过json_unquote(json_extract(data_info,‘$.brand’))获取数据。
在这里插入图片描述

表person同样:
在这里插入图片描述

2. 反射+注解+抽象model父类

数据表设计好之后,原理就是每次入库都是直接给id和data_info字段插入数据。就是将我们设计model类中每个字段组装成json插入库中即可。重点就是在这,需要抽象的方法就是将这部操作完成。

① 定义所有model的父类
/**
 * The type Base data model.
 *
 * @Description 所有model类的父类
 * @author: jiangtao
 * @create-date: 2022 /11/26 21:06
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseDataModel {
    // 主键id
    private String id;
    // 存储数据的字段
    private String dataInfo;
    // 数据库的表名    
    private String dataSource;

}
②car和person实体类

car和person的model都要继承父类,并且通过mybatis-plus提供的注解@TableName(value = “car”)指定表名。(当然可以自己定义一个注解,不用必要用mybatis-plus的注解)
注意事项:
1. @TableName注解指定表名(必须)
2. @TableField注解指定字段名(非必须)不加注解也可以,若不加注解或者加了注解不指定字段名的话,会将model类中的属性名由驼峰转换为下划线。
3.@TableField如果exist = false,那么在组装json的时候就会忽略该字段。

/**
 * @Description 车辆信息model
 * @author: jiangtao
 * @create-date: 2022/11/27 20:02
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName(value = "car")
@ToString(callSuper = true)
public class CarModel extends BaseDataModel {

    /**
     * 品牌
     */
    private String brand;

    /**
     * 价格
     */
    private BigDecimal price;

}
/**
 * @Description 人员信息
 * @author: jiangtao
 * @create-date: 2022/11/26 23:40
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "person")
public class PersonModel extends BaseDataModel {

    /**
     * 姓名
     */
    @TableField(value = "name")
    private String name1;

    /**
     * 年龄
     */
    @TableField(exist = false)
    private Integer age;

    /**
     * 家庭住址
     */
    private String homeLocation;

}

当然也支持多层继承,比如man继承person类。

/**
 * @Description 男人员信息
 * @author: jiangtao
 * @create-date: 2022/11/27 15:32
 */
@Data
@TableName("person")
@NoArgsConstructor
@ToString(callSuper = true)
public class ManModel extends PersonModel {

    /**
     * 男人特有属性,是否是父亲
     */
    private String isFather;

    public ManModel(String name1, Integer age, String homeLocation, String isFather) {
        super(name1, age, homeLocation);
        this.isFather = isFather;
    }
}
③实现插入方法

大体思路是通过反射获取model类和成员属性上的注解,根据通过注解可以获取到插入model的表,每个字段对应的数据库的列名,以及每个字段的值。将每个字段和值组合成json(这里建议用alibaba的json序列化工具),并赋值给dataInfo,将表名赋值给dataSource。

    @Autowired
    private EasyDataMapper easyDataMapper;

    /**
     * 新增数据
     *
     * @param dataModel 继承BaseDataModel的子类对象
     * @param <T>       继承BaseDataModel的子类
     * @return 插入数据成功条数
     */
	public <T extends BaseDataModel> int insertData(T dataModel) {
        // 判断是否有自定义主键id
        String id = dataModel.getId();
        if (StringUtils.isBlank(id)) {
            dataModel.setId(UUID.randomUUID().toString());
        }
        // 将dataModel中的属性放入dataInfo
        solveData(dataModel);
        return easyDataMapper.insertData(dataModel);
    }

/**
     * 将dataModel中涉及到的成员变量属性组合成json
     *
     * @param dataModel
     * @param <T>
     */
    private <T extends BaseDataModel> void solveData(T dataModel) {
        /**通过反射获取将对象中的成员属性值转换为json,并赋值到dataInfo*/
        Class<?> modelClass = dataModel.getClass();
        // 获取对象要插入的表名
        getDataSource(dataModel);
        // dataInfo
        JSONObject dataInfo = new JSONObject();
        // 考虑到多继承的情况,将BaseDataModel所有子类的字段都入库
        Class<?> baseClass = null;
        try {
            baseClass = Class.forName("com.jt.model.BaseDataModel");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("找不到基类BaseDataModel!");
        }
        // 当此类型不为父类BaseDataModel时执行
        while (!modelClass.equals(baseClass)) {
            Field[] fields = modelClass.getDeclaredFields();
            for (Field field : fields) {
                // 给予权限
                field.setAccessible(true);
                // 获取变量值
                Object value = null;
                try {
                    value = field.get(dataModel);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("字段权限不足!");
                }
                // 判断变量值是否为空
                if (!Objects.isNull(value)) {
                    // 不为空
                    // 看是否有@TableField注解
                    boolean isPresent = field.isAnnotationPresent(TableField.class);
                    if (isPresent) {
                        // 使用了@TableField注解
                        TableField tableField = field.getAnnotation(TableField.class);
                        // 是否为数据库字段
                        boolean exist = tableField.exist();
                        if (!exist) {
                            continue;
                        }
                        // 是否指定数据库字段名
                        String fieldName = tableField.value();
                        if (StringUtils.isBlank(fieldName)) {
                            // 未指定则将驼峰转换成下划线格式
                            toUnderlineCase(dataInfo, field, value);
                        } else {
                            // 指定则按照指定的名称
                            dataInfo.put(fieldName, value);
                        }
                    } else {
                        // 没有使用@TableField注解
                        toUnderlineCase(dataInfo, field, value);
                    }
                }
            }
            modelClass = modelClass.getSuperclass();
        }
        // 将字段放入model
        dataModel.setDataInfo(dataInfo.toString());
    }

easyDataMapper定义的sql

//  dao层的sql
int insertData(@Param("model") BaseDataModel baseDataModel);

//  mapper中的sql
    <insert id="insertData" parameterType="com.jt.model.BaseDataModel">
        insert into ${model.dataSource}(id, data_info)
        values (#{model.id}, #{model.dataInfo})
    </insert>
④测试

同一个方法插入两个不同的model类。

    @org.junit.Test
    public void easyDataInsertTest() {
        ManModel manModel = new ManModel("赵六", 50, "河北省", "否");
        // 插入数据
        easyDataService.insertData(manModel);

        CarModel car = CarModel.builder()
                .brand("宝马一系")
                .price(new BigDecimal("210000.00"))
                .build();
        // 插入数据
        easyDataService.insertData(car);
    }

看数据库结果
在这里插入图片描述
在这里插入图片描述
因为age这个字段用了@TableField(exist = false),所以并没有将age的字段组装到json中,数据库中age字段自然为空。

四、拓展

按照同样的思路,可以将删改查的方法抽象出来,感兴趣的伙伴可以去我的仓库将代码拉下来研究一下。
https://gitee.com/jiangtao_eiji/learn-demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值