Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map a map JSON column to Java Object with JPA

Tags:

java

json

orm

jpa

We have a big table with a lot of columns. After we moved to MySQL Cluster, the table cannot be created because of:

ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 14000. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs

As an example:

@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;
}

It's a table for storing configuration parameters. I was thinking that we can combine some columns into one and store it as JSON object and convert it to some Java object.

For example:

@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;
}

Where we have defined:

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

By using this we can combine all columns into one and create our table. Or we can split the whole table into several tables. Personally I prefer the first solution.

Anyway my question is how to map the Params column which is text and contains JSON string of a Java object?

like image 354
Rad Avatar asked Sep 09 '14 07:09

Rad


3 Answers

You can use a JPA converter to map your Entity to the database. Just add an annotation similar to this one to your params field:

@Convert(converter = JpaConverterJson.class)

and then create the class in a similar way (this converts a generic Object, you may want to specialize it):

@Converter(autoApply = true)
public class JpaConverterJson implements AttributeConverter<Object, String> {

  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;
    }
  }

}

That's it: you can use this class to serialize any object to json in the table.

like image 136
Alessandro Polverini Avatar answered Nov 16 '22 09:11

Alessandro Polverini


The JPA AttributeConverter is way too limited to map JSON object types, especially if you want to save them as JSON binary.

You don’t have to create a custom Hibernate Type to get JSON support, All you need to do is use the Hibernate Types OSS project.

For instance, if you're using Hibernate 5.2 or newer versions, then you need to add the following dependency in your Maven pom.xml configuration file:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version> 
</dependency> 

Now, you need to declare the new type either at the entity attribute level or, even better, at the class level in a base class using @MappedSuperclass:

@TypeDef(name = "json", typeClass = JsonType.class)

And the entity mapping will look like this:

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

If you're using Hibernate 5.2 or later, then the JSON type is registered automatically by MySQL57Dialect.

Otherwise, you need to register it yourself:

public class MySQLJsonDialect extends MySQL55Dialect {

    public MySQLJsonDialect() {
        super();
        this.registerColumnType(Types.JAVA_OBJECT, "json");
    }
}

And, set the hibernate.dialect Hibernate property to use the fully-qualified class name of the MySQLJsonDialect class you have just created.

like image 41
Vlad Mihalcea Avatar answered Nov 16 '22 07:11

Vlad Mihalcea


If you need to map json type property to json format when responding to the client (e.g. rest API response), add @JsonRawValue as the following:

@Column(name = "params", columnDefinition = "json")
@JsonRawValue
private String params;

This might not do the DTO mapping for server-side use, but the client will get the property properly formatted as json.

like image 13
mhsallam Avatar answered Nov 16 '22 08:11

mhsallam