JSON 字段映射:存取 Java 对象与 Map
阅读需 4 分钟
主题颜色、通知渠道等用户偏好,通常一起读取、一起修改。如果每次都手工把对象序列化成 JSON,再在查询后反序列化,这段转换逻辑很容易散落在多个 DAO 中。
dbVisitor 可以把这项约定写到类型上:一个 Java 对象,对应数据库里的一个字段值。
存储结构
本文使用 H2 内存数据库,避免准备外部服务。表结构为:
CREATE TABLE blog_user_profile (
id INT PRIMARY KEY,
preferences VARCHAR(2000),
attributes VARCHAR(2000)
);
preferences 保存偏好对象,attributes 保存 Map。VARCHAR(2000) 只是足够容纳示例 JSON 的文本列,不会因为字段名或长度而自动映射成对象。
示例依赖 dbVisitor、H2 和 Gson;可直接运行 示例工程(GitHub / Gitee)中的 example.JsonField。
类型注解
@BindTypeHandler(JsonTypeHandler.class)
public class Preferences {
private String theme;
private List<String> channels;
// 标准 getter、setter。
}
注解来自 net.hasor.dbvisitor.types.BindTypeHandler,处理器来自 net.hasor.dbvisitor.types.handler.json.JsonTypeHandler。它声明的是 Preferences 的值转换方式,不是表映射。
外层实体再描述列与属性关系:
@Table("blog_user_profile")
public class UserProfile {
@Column(primary = true)
private Integer id;
private Preferences preferences;
@Column(typeHandler = JsonTypeHandler.class,
specialJavaType = LinkedHashMap.class)
private Map<String, Object> attributes;
// 标准 getter、setter。
}
Preferences 可以在自身类型上加注解,不必在每个属性上重复配置。Map 不能这样做,所以在属性的 @Column 上声明处理器,并指定读取时使用 LinkedHashMap。
对象读写
Preferences prefs = new Preferences();
prefs.setTheme("dark");
prefs.setChannels(List.of("email", "app"));
UserProfile profile = new UserProfile();
profile.setId(1);
profile.setPreferences(prefs);
profile.setAttributes(Map.of("language", "zh-CN"));
LambdaTemplate lambda = new LambdaTemplate(conn);
lambda.insert(UserProfile.class).applyEntity(profile).executeSumResult();
UserProfile loaded = lambda.query(UserProfile.class)
.eq(UserProfile::getId, 1)
.queryForObject();
loaded.getPreferences().getTheme() 为 dark,渠道列表是 [email, app],loaded.getAttributes().get("language") 为 zh-CN。
数据库 preferences 字段保存类似下面的文本,JSON 属性顺序不影响含义:
{"theme":"dark","channels":["email","app"]}