How to map a MySQL JSON column to a Java entity property using JPA and Hibernate

43,601

Solution 1

I prefer to do this way:

  • Creating converter (attribute converter) from Map to String and vice versa.
  • Using Map to map mysql JSON column type in domain (entity) class

The code is bellow.

JsonToMapConverted.java

@Converter
public class JsonToMapConverter 
                    implements AttributeConverter<String, Map<String, Object>> 
{
    private static final Logger LOGGER = LoggerFactory.getLogger(JsonToMapConverter.class);

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, Object> convertToDatabaseColumn(String attribute)
    {
        if (attribute == null) {
           return new HashMap<>();
        }
        try
        {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(attribute, HashMap.class);
        }
        catch (IOException e) {
            LOGGER.error("Convert error while trying to convert string(JSON) to map data structure.");
        }
        return new HashMap<>();
    }

    @Override
    public String convertToEntityAttribute(Map<String, Object> dbData)
    {
        try
        {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(dbData);
        }
        catch (JsonProcessingException e)
        {
            LOGGER.error("Could not convert map to json string.");
            return null;
        }
    }
}

Part of domain (entity-mapping) class

...

@Column(name = "meta_data", columnDefinition = "json")
@Convert(attributeName = "data", converter = JsonToMapConverter.class)
private Map<String, Object> metaData = new HashMap<>();

...

This solution perfectly works for me.

Solution 2

You don’t have to create all these types manually, you can simply get them via Maven Central using the following dependency:

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

For more info, check out the Hibernate Types open-source project.

Now, to explain how it all works.

Assuming you have the following entity:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
    name = "json", 
    typeClass = JsonType.class
)
public class Book {
 
    @Id
    @GeneratedValue
    private Long id;
 
    @NaturalId
    private String isbn;
 
    @Type(type = "json")
    @Column(columnDefinition = "json")
    private String properties;
 
    //Getters and setters omitted for brevity
}

Notice two things in the code snippet above:

  • the @TypeDef is used to define a new custom Hibernate Type, json which is handled by the JsonType
  • the properties attribute has a json column type and it's mapped as a String

That's it!

Now, if you save an entity:

Book book = new Book();
book.setIsbn("978-9730228236");
book.setProperties(
    "{" +
    "   \"title\": \"High-Performance Java Persistence\"," +
    "   \"author\": \"Vlad Mihalcea\"," +
    "   \"publisher\": \"Amazon\"," +
    "   \"price\": 44.99" +
    "}"
);
 
entityManager.persist(book);

Hibernate is going to generate the following SQL statement:

INSERT INTO
    book 
(
    isbn, 
    properties, 
    id
) 
VALUES
(
    '978-9730228236', 
    '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',  
    1
)

And you can also load it back and modify it:

Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(Book.class)
    .load("978-9730228236");
     
book.setProperties(
    "{" +
    "   \"title\": \"High-Performance Java Persistence\"," +
    "   \"author\": \"Vlad Mihalcea\"," +
    "   \"publisher\": \"Amazon\"," +
    "   \"price\": 44.99," +
    "   \"url\": \"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" +
    "}"
);

Hibernate taking caare of the UPDATE statement for you:

SELECT  b.id AS id1_0_
FROM    book b
WHERE   b.isbn = '978-9730228236'
 
SELECT  b.id AS id1_0_0_ ,
        b.isbn AS isbn2_0_0_ ,
        b.properties AS properti3_0_0_
FROM    book b
WHERE   b.id = 1    
 
UPDATE
    book 
SET
    properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/"}'
WHERE
    id = 1

All code available on GitHub.

Solution 3

If the values inside your json array are simple strings you can do this:

@Type( type = "json" )
@Column( columnDefinition = "json" )
private String[] jsonValue;

Solution 4

Heril Muratovic's answer is good, but I think the JsonToMapConverter should implement AttributeConverter<Map<String, Object>, String>, not AttributeConverter<String, Map<String, Object>>. Here is the code that works for me

@Slf4j
@Converter
public class JsonToMapConverter implements AttributeConverter<Map<String, Object>, String> {
    @Override
    @SuppressWarnings("unchecked")
    public Map<String, Object> convertToEntityAttribute(String attribute) {
        if (attribute == null) {
            return new HashMap<>();
        }
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(attribute, HashMap.class);
        } catch (IOException e) {
            log.error("Convert error while trying to convert string(JSON) to map data structure.", e);
        }
        return new HashMap<>();
    }

    @Override
    public String convertToDatabaseColumn(Map<String, Object> dbData) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(dbData);
        } catch (JsonProcessingException e) {
            log.error("Could not convert map to json string.", e);
            return null;
        }
    }
}

Solution 5

For anyone can't make @J. Wang answer work :

Try add this dependency(it's for hibernate 5.1 and 5.0, other version check here)

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-5</artifactId>
    <version>1.2.0</version>
</dependency>

And add this line to the entity

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

So full version of the entity class :

@Entity
@Table(name = "some_table_name")
@TypeDef(name = "json", typeClass = JsonStringType.class)
public class MyCustomEntity implements Serializable {

   private static final long serialVersionUID = 1L;

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private Long id;

   @Type( type = "json" )
   @Column( columnDefinition = "json" )
   private List<String> jsonValue;
}

I test the code with spring boot 1.5.9 and hibernate-types-5 1.2.0 .

Share:
43,601
Heril Muratovic
Author by

Heril Muratovic

Working Experience: 2014-2016 Software developer at Information Technology Systems Technology stack: PHP HTML5, CSS3 JavaScript / jQuery MySQL Linux 2016-present Software developer at Logate d.o.o Technology stack: Spring Boot Framework Android (Native and Hybrid) development MySQL Web Services AngularJS Linux Server Administration LinkedIn profile: https://me.linkedin.com/in/heril-muratovic-021097132

Updated on January 07, 2022

Comments

  • Heril Muratovic
    Heril Muratovic over 2 years

    I have a MySQL column declared as type JSON and I have problems to map it with JPA/Hibernate. I'm using Spring Boot on back-end.

    Here is small part of my code:

    @Entity
    @Table(name = "some_table_name")
    public class MyCustomEntity implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    @Column(name = "json_value")
    private JSONArray jsonValue;
    

    The program returns me an error and tells me that I can't map the column.

    In mysql table the column is defined as:

    json_value JSON NOT NULL;