JPA将JSON列映射到Java Object

我们有一个包含大量列的大表。 在我们转移到MySQL Cluster之后,由于以下原因无法创建表:

ERROR 1118(42000):行大小太大。 所使用的表类型的最大行大小(不包括BLOB)是14000.这包括存储开销,请查看手册。 您必须将某些列更改为TEXT或BLOB

举个例子:

@Entity @Table (name = "appconfigs", schema = "myproject") public class AppConfig implements Serializable { @Id @Column (name = "id", nullable = false) @GeneratedValue (strategy = GenerationType.IDENTITY) private int id; @OneToOne @JoinColumn (name = "app_id") private App app; @Column(name = "param_a") private ParamA parama; @Column(name = "param_b") private ParamB paramb; } 

它是用于存储配置参数的表。 我想我们可以将一些列组合成一个并将其存储为JSON对象并将其转换为某个Java对象。

例如:

 @Entity @Table (name = "appconfigs", schema = "myproject") public class AppConfig implements Serializable { @Id @Column (name = "id", nullable = false) @GeneratedValue (strategy = GenerationType.IDENTITY) private int id; @OneToOne @JoinColumn (name = "app_id") private App app; @Column(name = "params") //How to specify that this should be mapped to JSON object? private Params params; } 

我们定义的地方:

 public class Params implements Serializable { private ParamA parama; private ParamB paramb; } 

通过使用它,我们可以将所有列合并为一个并创建我们的表。 或者我们可以将整个表分成几个表。 我个人更喜欢第一种解决方案。

无论如何我的问题是如何映射Params列,它是文本并包含Java对象的JSON字符串?

您可以使用JPA转换器将实体映射到数据库。 只需在params字段中添加与此类似的注释:

 @Convert(converter = JpaConverterJson.class) 

然后以类似的方式创建类(这会转换一个通用的Object,你可能想要专门化它):

 @Converter(autoApply = true) public class JpaConverterJson implements AttributeConverter { private final static ObjectMapper objectMapper = new ObjectMapper(); @Override public String convertToDatabaseColumn(Object meta) { try { return objectMapper.writeValueAsString(meta); } catch (JsonProcessingException ex) { return null; // or throw an error } } @Override public Object convertToEntityAttribute(String dbData) { try { return objectMapper.readValue(dbData, Object.class); } catch (IOException ex) { // logger.error("Unexpected IOEx decoding json from database: " + dbData); return null; } } } 

就是这样:您可以使用此类将任何对象序列化为表中的json。

正如我在本文中解释的那样,JPA AttributeConverter太局限于映射JSON对象类型,特别是如果要将它们保存为JSON二进制文件。

您不必手动创建所有这些类型,只需使用以下依赖项通过Maven Central获取它们:

  com.vladmihalcea hibernate-types-52 ${hibernate-types.version}  

有关更多信息,请查看hibernate类型的开源项目 。

现在,解释它是如何工作的。

我写了一篇关于如何在PostgreSQL和MySQL上映射JSON对象的文章 。

对于PostgreSQL,您需要以二进制forms发送JSON对象:

 public class JsonBinaryType extends AbstractSingleColumnStandardBasicType implements DynamicParameterizedType { public JsonBinaryType() { super( JsonBinarySqlTypeDescriptor.INSTANCE, new JsonTypeDescriptor() ); } public String getName() { return "jsonb"; } @Override public void setParameterValues(Properties parameters) { ((JsonTypeDescriptor) getJavaTypeDescriptor()) .setParameterValues(parameters); } } 

JsonBinarySqlTypeDescriptor如下所示:

 public class JsonBinarySqlTypeDescriptor extends AbstractJsonSqlTypeDescriptor { public static final JsonBinarySqlTypeDescriptor INSTANCE = new JsonBinarySqlTypeDescriptor(); @Override public  ValueBinder getBinder( final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder(javaTypeDescriptor, this) { @Override protected void doBind( PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { st.setObject(index, javaTypeDescriptor.unwrap( value, JsonNode.class, options), getSqlType() ); } @Override protected void doBind( CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { st.setObject(name, javaTypeDescriptor.unwrap( value, JsonNode.class, options), getSqlType() ); } }; } } 

JsonTypeDescriptor这样:

 public class JsonTypeDescriptor extends AbstractTypeDescriptor implements DynamicParameterizedType { private Class jsonObjectClass; @Override public void setParameterValues(Properties parameters) { jsonObjectClass = ( (ParameterType) parameters.get( PARAMETER_TYPE ) ) .getReturnedClass(); } public JsonTypeDescriptor() { super( Object.class, new MutableMutabilityPlan() { @Override protected Object deepCopyNotNull(Object value) { return JacksonUtil.clone(value); } }); } @Override public boolean areEqual(Object one, Object another) { if ( one == another ) { return true; } if ( one == null || another == null ) { return false; } return JacksonUtil.toJsonNode(JacksonUtil.toString(one)).equals( JacksonUtil.toJsonNode(JacksonUtil.toString(another))); } @Override public String toString(Object value) { return JacksonUtil.toString(value); } @Override public Object fromString(String string) { return JacksonUtil.fromString(string, jsonObjectClass); } @SuppressWarnings({ "unchecked" }) @Override public  X unwrap(Object value, Class type, WrapperOptions options) { if ( value == null ) { return null; } if ( String.class.isAssignableFrom( type ) ) { return (X) toString(value); } if ( Object.class.isAssignableFrom( type ) ) { return (X) JacksonUtil.toJsonNode(toString(value)); } throw unknownUnwrap( type ); } @Override public  Object wrap(X value, WrapperOptions options) { if ( value == null ) { return null; } return fromString(value.toString()); } } 

现在,您需要在类级别或package-info.java包级别描述符中声明新类型:

 @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) 

实体映射将如下所示:

 @Type(type = "jsonb") @Column(columnDefinition = "json") private Location location; 

如果您使用的是Hibernate 5或更高版本,则Postgre92Dialect会自动注册 JSON类型。

否则,您需要自己注册:

 public class PostgreSQLDialect extends PostgreSQL91Dialect { public PostgreSQL92Dialect() { super(); this.registerColumnType( Types.JAVA_OBJECT, "json" ); } } 

我有一个类似的问题,并通过使用@Externalizer注释和Jackson解决它来序列化/反序列化数据(@Externalizer是OpenJPA特定的注释,所以你必须检查你的JPA实现类似的可能性)。

 @Persistent @Column(name = "params") @Externalizer("toJSON") private Params params; 

参数类实现:

 public class Params { private static final ObjectMapper mapper = new ObjectMapper(); private Map map; public Params () { this.map = new HashMap(); } public Params (Params another) { this.map = new HashMap(); this.map.putAll(anotherHolder.map); } public Params(String string) { try { TypeReference> typeRef = new TypeReference>() { }; if (string == null) { this.map = new HashMap(); } else { this.map = mapper.readValue(string, typeRef); } } catch (IOException e) { throw new PersistenceException(e); } } public String toJSON() throws PersistenceException { try { return mapper.writeValueAsString(this.map); } catch (IOException e) { throw new PersistenceException(e); } } public boolean containsKey(String key) { return this.map.containsKey(key); } // Hash map methods public Object get(String key) { return this.map.get(key); } public Object put(String key, Object value) { return this.map.put(key, value); } public void remove(String key) { this.map.remove(key); } public Object size() { return map.size(); } } 

HTH