一次mybatis类型构造器的使用实例
背景
存储复杂的数据结构时,存在包含子类,嵌套本类,List存储的复杂情况,写mapper和表结构太费劲,嵌套子类list不想弄子表,嵌套自身list不想弄parentId等等, 于是拟使用类型构造器解决
类结构
base类
public class TestCaseInfo extends CUBaseEntity {
private Long testCaseId;
@ApiModelProperty("测试用例名称")
private String testCaseName;
@ApiModelProperty("测试用例描述")
private String testCaseDesc;
private String testCaseType;
private Long relationId;
@ApiModelProperty("测试用例的选择的型号code")
private String productCode;
@ApiModelProperty("测试用例的型号名称")
private String productName;
private String typeId;
@ApiModelProperty("测试用例中单元之间的间隔时间")
private String intervalTime = "0";
@ApiModelProperty("测试单元间隔时间单位,固定ms")
private String intervalTimeUnit = "ms";
@ApiModelProperty("测试用例的测试单元集")
private List<TestCaseUnit> testCaseUnitList;
}
base类的子类TestCaseUnit
public class TestCaseUnit {
@ApiModelProperty("测试单元名称描述")
public String desc;
@ApiModelProperty("所属测试用例ID")
private Long testCaseId;
@ApiModelProperty("用例测试单元ID")
private Long testCaseUnitId;
@ApiModelProperty("排序,协议测试时需按此顺序")
private int sort;
@ApiModelProperty("单品测试测试单元状态,启用0,停用1")
private String status;
@ApiModelProperty("测试单元对应属性Json")
private JSONObject property;
@ApiModelProperty("用例测试单元类型,simple简易模式下无testCaseParamList,customize自定义")
private String testCaseUnitType;
@ApiModelProperty("测试单元测试动作参数")
private List<TestCaseParam> testCaseParamList;
private List<TestCaseUnit> childTestCaseUnitList;
@ApiModelProperty("子属性集,如果为组命令G简易测试,用到此值")
private JSONArray childPropertyArray;
private Long parentTestCaseUnitId;
}
可以看到base类中包含TestCaseUnit类的List, 就需要主表与子表分别存储Base信息与TestCaseUnit信息, 但每一个TestCaseUnit中又会包含复杂结构,比如自身List以及TestCaseParam类的List, 一般会需要更多子表和关联键来关联信息, 比较麻烦, 对于基本不会作为查询条件的自身List以及TestCaseParam类的List,拟整合mysql的json字段来单纯存储读取,那么如何存储读取就成了关键, 本文使用mybatis的类型构造器进行解析。
JsonTypeHandler
public class JsonTypeHandler<T extends Object> extends BaseTypeHandler<T> {
private static final ObjectMapper mapper = new ObjectMapper();
private Class<T> clazz;
public JsonTypeHandler(Class<T> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.clazz = clazz;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, this.toJson(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
return this.toObject(rs.getString(columnName), clazz);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.toObject(rs.getString(columnIndex), clazz);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return this.toObject(cs.getString(columnIndex), clazz);
}
private T toObject(String content, Class<?> clazz) {
if (content != null && !content.isEmpty()) {
try {
return (T) mapper.readValue(content, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
private String toJson(T object) {
try {
return mapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
JsonArrayHandler
public class JsonArrayHandler<T> extends BaseTypeHandler<List<T>> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private Class<T> type;
public JsonArrayHandler() {
// 添加无参构造函数
}
public JsonArrayHandler(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
//objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
// ObjectMapper.DefaultTyping.NON_FINAL);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType)
throws SQLException {
try {
// 将 List<PropertyCheck> 转换为 JSON 字符串
ps.setString(i, objectMapper.writeValueAsString(parameter));
} catch (JsonProcessingException e) {
throw new SQLException("Failed to convert List<T> to JSON string", e);
}
}
@Override
public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return toList(rs.getString(columnName));
}
@Override
public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return toList(rs.getString(columnIndex));
}
@Override
public List<T> getNullableResult(java.sql.CallableStatement cs, int columnIndex) throws SQLException {
return toList(cs.getString(columnIndex));
}
public List<T> toList(String json) throws SQLException {
if (json == null || json.isEmpty()) {
return null;
}
try {
// 将 JSON 字符串转换为 List<PropertyCheck>
return objectMapper.readValue(json,
objectMapper.getTypeFactory().constructCollectionType(List.class, type));
} catch (IOException e) {
throw new SQLException("Failed to convert JSON string to List<T>", e);
}
}
}
泛型支持:public class JsonArrayHandler extends BaseTypeHandler<List> 中的 表示这是一个泛型类,可以适用于不同类型的 JSON 数组字段。通过使用泛型,可以在运行时指定具体的类型,使得处理不同类型的 JSON 数组变得更加灵活和通用。
构造函数重载:public JsonArrayHandler() 是一个无参构造函数,而 public JsonArrayHandler(Class type) 是一个有参构造函数。通过提供两个构造函数,可以灵活地创建 JsonArrayHandler 的实例。无参构造函数用于在不知道具体类型时创建实例,而有参构造函数用于在已知类型时创建实例。
参数设置:setNonNullParameter 方法用于将 Java 对象转换为存储在数据库中的 JSON 字符串。将 List 对象转换为 JSON 字符串,并将其设置到 PreparedStatement 对象中。
结果获取:getNullableResult 方法用于从数据库中获取 JSON 字符串,并将其转换回 List 对象。将 JSON 字符串解析为 JSONArray,然后将其转换为 List 对象。
LIST存储构造器使用
public class TestUnitListTypeHandler extends JsonArrayHandler<TestUnit> {
public TestUnitListTypeHandler() {
super(TestUnit.class);
}
}
<resultMap id="TestUnitResultMap" type="com.ruoyi.business.model.testcase.relation.TestUnit">
<id column="test_unit_id" property="testUnitId"/>
<result column="relation_id" property="relationId"/>
<result column="property_name" property="propertyName"/>
<result column="property" property="property"
typeHandler="com.ruoyi.common.handler.JsonTypeHandler"/>
<result column="property_type" property="propertyType"/>
<result column="property_write_type" property="propertyWriteType"/>
<result column="test_unit_desc" property="testUnitDesc"/>
<result column="parent_property_name" property="parentPropertyName"/>
<result column="test_unit_property_array" property="testUnitPropertyArray" typeHandler="com.ruoyi.common.handler.JsonTypeHandler"/>
<result column="child_test_unit_list" property="childTestUnitList" typeHandler="com.ruoyi.business.handler.testcase.TestUnitListTypeHandler"/>
</resultMap>
<insert id="insertRelationUnitBatch" useGeneratedKeys="true" keyProperty="testUnitList.testUnitId">
<if test="testUnitList != null and testUnitList.size() != 0">
insert into test_instruct (relation_id, property_name, property, property_type, property_write_type,
test_unit_desc,
parent_property_name, test_unit_property_array, child_test_unit_list) values
<foreach collection="testUnitList" item="testUnit" separator=",">
(#{relationId}, #{testUnit.propertyName},
#{testUnit.property, typeHandler=com.ruoyi.common.handler.JsonTypeHandler},
#{testUnit.propertyType}, #{testUnit.propertyWriteType},
#{testUnit.testUnitDesc}, #{testUnit.parentPropertyName},
#{testUnit.testUnitPropertyArray, typeHandler=com.ruoyi.common.handler.JsonTypeHandler},
#{testUnit.childTestUnitList, typeHandler=com.ruoyi.business.handler.testcase.TestUnitListTypeHandler})
</foreach>
</if>
</insert>