JPA日期时间转化
之前讲过可以 JPA 通过内部转化实现自动映射, 如以下情况:
@Entity
@Table(name = "tbl_admin_info")
public class AdminInfoModel {
/**
* 自定义时间转化器
*/
@Convert(converter = ConvertDateAndTimestamp.class)
@Column(nullable = false, columnDefinition = "INT COMMENT '最后更新时间'")
private Date updateTime = new Date(0L);
}
但是如果响应 JSON 数据的时候就带来麻烦了, 默认响应 JSON 的情况会直接将 new Date(0) 格式转化为默认时间戳时间, 也就是 1900-xx-xx xx:xx:xx 格式.
在多次实践后发现自定义转化的问题特别多, 包括
JSON格式的转化没办法通过JPA进行精准|模糊查询等(因为内部转化 Map|List 是没办法加入 CrudRepository 作为条件检索, MySQL|MariaDB后续更新的JSON查询语法都没法调用).
也就是这种情况之下开始回过头来审视 Convert 自定义转化利弊.
AttributeConverter 功与过
类似于 JSON 格式当中解构, 如下面的 JSON 数组解析器:
/**
* List转化JSON数据
*/
public class ConvertListAndJson implements AttributeConverter<List<Object>, String> {
final static ObjectMapper mapper = new ObjectMapper();
@Override
public String convertToDatabaseColumn(List<Object> strings) {
try {
return mapper.writeValueAsString(strings);
} catch (JsonProcessingException exception) {
throw new RuntimeException("数据格式错误");
}
}
@Override
public List<Object> convertToEntityAttribute(String s) {
try {
return mapper.readValue(s, new TypeReference<ArrayList<Object>>() {
});
} catch (JsonProcessingException exception) {
throw new RuntimeException("数据格式错误");
}
}
}
然后应用到 JPA 当中:
@Entity
@Table(name = "tbl_event_info")
public class EventInfoModel {
/**
* 这里也是 Browse - Install - Register 归因主要字段
* 内部采用 [ md5('xxx'),md5('yyy'),md5('zzz'),... ] 放置多个哈希特征值, 可能是 imei|idfa|androidId|IpAddress|UserAgent|finger等
* 主要关键就是匹配出 Browse - Install - Register 整个流程跑完的用户, 确定归因点击广告等行为触发注册的玩家
* 利用 MySQL8 之后的 JSON 特性匹配出 endTime < 当前事件 且 keyboards IN(xxx) 的关联数据
*/
@Convert(converter = ConvertListAndJson.class)
@Column(nullable = false, columnDefinition = "JSON COMMENT '归因字段,imei|idfa|androidId|IpAddress|UserAgent|finger之类做MD5关联, 然后检索指定32位字符串'")
private List<String> keyboards = new ArrayList<>();
}
这时候这种解析看起来很完美对不对, 但是这里隐藏很大的坑点: 查询无法匹配自定义类型;
可以看到SQL注释指名了这个 JSON 数组是需要模糊匹配查询出指定数据, 但是在编写 CrudRepository 数据工厂会一直报错找不到对应类型匹配条件.
在这种 JSON 格式之中, 新版本数据库支持以下 JSON 查询:
# 最新版本查询
SELECT * FROM tbl_event_info WHERE JSON_CONTAINS(keyboards,'"5b42eb43695678968bc68e4e5a700a05"');
# 哪怕老版本可以把字段认为是TEXT模糊匹配
SELECT * FROM tbl_event_info WHERE keyboards LIKE "%5b42eb43695678968bc68e4e5a700a05%";
但是在 JPA 如果使用自定义转化器将会完全失效, 因为在调用 CrudRepository 的 like 查询的时候会默认 keyboards 为字符串导致和自定义 converter 出现类型冲突直接报错.
当然可能会有人说用 @Query(native = true) 直接编写原生语句就可以了吧, 事实上也不行的.
/**
* 事件仓库
*/
public interface EventInfoRepository extends CrudRepository<EventInfoModel, Long>,JpaSpecificationExecutor<EventInfoModel> {
/**
* Like只认准字段必须为String对象
*/
List<EventInfoModel> findAllByKeyboardsLike(String keyboard);
/**
* 也是异常类型错误, 找不到对应数据
*/
@Query(nativeQuery = true,value = "SELECT * FROM tbl_event_info WHERE keyboards LIKE \"%?1%\"")
List<EventInfoModel> findKeyboard(String keyboard);
}
这种情况之下 keyboards 字段完全处于不可检索状态, 那么最后不得不将其改回 String 类型从而支持 LIKE 模糊匹配查询.
这个坑点在网上找到很多资料之后还是没办法处理, 最后只能转回原生的String才能完美实现功能.
包括之前的提供的 Date 自动转化情况, 在作为字段查询检索情况十分不理想, 同时还会导致结构体作为数据响应给客户端的情况默认转化为 1900 这个初始时间戳格式.
但是如果不参与查询的时候, converter 直接默认对数据做好转化, 不需要去编写多余工具代码从而让其直接取出数据库 JSON 就能映射 MAP|LIST 对象从而精简好用.
到这里就看出问题了, 如果自定义格式对于参与查询检索是十分受限的, 而且在项目初期是没办法预估是否字段会参与查询的, 所以直接盲目引入自定义转化器对于后期维护回带来大量问题.
也就是踩过这次坑之后, 对于
converter如果真的迫不得已, 否则千万不要去优先采用自定义转化方案!