一、功能介绍
此模式并不属于设计模式的范畴(自封设计模式哈哈),想到该应用场景是由于我在接到新项目需求的时候,UI设计图中涉及到高达二十多种表单,为了“偷懒”,将表单数据入库数据进行抽象。假设每张表单会涉及到一张数据表,那么我们在开发的时候可能会去建立二十多个dao接口和二十多个mapper文件、二十多个sql,就算使用mybatis-plus,也是需要写二十多个dao接口的,更难受的是测试的时候需要测试二十多张表单的新增、回显等功能。解决方案就是将增删改查的接口全部抽象出来,达到的效果就是只需要一个方法就可以实现二十多张表单的提交、查询、修改和删除。
二、环境搭建
- mysql 5.7 + (涉及到虚拟列)
- mybatis-plus (主要使用到了它的两个注解,当然可以自己设计注解,下面有介绍)
- 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
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/192061.html