Building a RESTful application in Spring Boot that reads data from a database requires the definition of some ORM mappings. Even though this is generally a boring task, it is crucial for the proper functioning of the web application.
The ORM used by Spring Boot 3 with JPA 3 is Hibernate 6, which changed the way some column types need to be mapped. This is the case of JSON data type. In detail, you can now map a JSON column in Hibernate to a String
, a Map
, or a custom object.
Let’s learn the different approaches you can follow for JSON mapping in Hibernate 6 and JPA 3!
Getting Started
This article refers to Spring Boot 3 with JPA 3.1 and Hibernate 6.2. If you have a Spring Boot 2 project, follow the official guide to migrate it to version 3.0. Keep in mind that Spring Boot JPA 3 uses Hibernate 6 by default.
After setting up a Spring Boot 3 project, you will need a database involving JSON data to connect to. Any DBMS technologies supporting a data type for JSON will do. For example, suppose you have a configs
table in a PostgreSQL database defined by the following two columns:
Note the data
columns of type jsonb
. That is the most complete data type for storing JSON data in PostgreSQL. Check out our guide to learn more about JSON vs JSONB in Postgres.
In detail, take a look at the query below:
This is the JSON data stored in a record of the config
table:
{
"graphics": {
"resolution": "1920x1080",
"quality": "high",
"anti_aliasing": true
},
"gameplay": {
"difficulty": "normal",
"achievements": ["tutorial_passed", "chapter_1_mastered"]
}
}
You now have everything you need to get started. There are three different approaches to JSON mapping in Hibernate. Time to explore them all!
Method #1: Map a JSON Column to a String
The easiest way to map a JSON column in Hibernate is to use a String
. This is a straightforward and simple approach, but you should only use it when dealing with read-only JSON data. The reason is that changing and exploring the JSON data read at the application layer will involve cumbersome operations on a raw string.
You can map a JSON column to a String
in a JPA entity as follows:
package com.dbvis.demo.entities;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
@Entity(name = "configs")
public class Config {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "data")
private String data;
// getters and setters omitted for brevity...
}
As you can see, to activate JSON mapping in Hibernate you only need to annotate your column attribute with @JdbcTypeCode(SqlTypes.JSON)
. Hibernate will now use Jackson, the standard JSON library coming with Spring Boot, to serialize and deserialize the attribute’s value to the type specified. In particular, it will convert data
to a String when reading and transform it to JSON when writing.
Note that SqlTypes.JSON
represent the generic SQL type JSON. Specifically, the default JSON data type used behind the scene changes according to the RDBMS dialect configured. For PostgreSQL, that is JSONB
data type.
Now, assume you have a /api/configs/1
endpoint that returns the first record of the config
table. Call it and you would get:
{
"id": 1,
"data": "{\"gameplay\": {\"difficulty\": \"normal\", \"achievements\": [\"tutorial_passed\", \"chapter_1_mastered\"]}, \"graphics\": {\"quality\": \"high\", \"resolution\": \"1920x1080\", \"anti_aliasing\": true}}"
}
As expected, the data
field in the JSON response produced by Spring Boot is a string containing the JSON content.
Pros
- Easy to implement
- JSON schema agnostic
Cons
- Basically limited to read-only JSON columns
- JSON content is returned as a raw string
Method #2: Map a JSON Column to a Java Map
Another possible solution supported by Hibernate is to map the JSON content to a Map<String, String>
object. Take a look at the JPA entity below:
package com.dbvis.demo.entities;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import java.util.Map;
@Entity(name = "configs")
public class Config {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "data")
private Map<String, String> data;
// getters and setters omitted for brevity...
}
Notice the type declared on the data
field. When retrieving JSON data, Spring Boot will now instantiate a Map
object containing the JSON values in <key, value>
string pairs. Similarly, Jackson will serialize the Map
object associated with the data
field to JSON when writing a new record to the database.
If you now call the /api/configs/1
API, you will get the following error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
That occurs because this approach to JSON mapping in JPA does not support JSON values involving arrays or nested objects. Thus, it only works with flat JSON data consisting of primitive fields.
Assume your data
column stores the following flat JSON data:
{
"difficulty": "normal",
"quality": "high",
"resolution": "1920x1080",
"anti_aliasing": true
}
In this case, the endpoint will return:
{
"id": 1,
"data": {
"quality": "high",
"difficulty": "normal",
"resolution": "1920x1080",
"anti_aliasing": "true"
}
}
Note that the data
column containing JSON data was correctly serialized to JSON by Jackson. With this approach, the data
field in the JSON response generated by the server is a proper object and no longer a simple string.
Pros
- JSON content gets serialized as expected
- Support for CRUD operation on the JSON content via the
Map
API
Cons
- Only supports flat JSON data involving primitive values
Method #3: Map a JSON Column to an Object
To map a JSON column to a custom Java object, you only have to specify it as the field type:
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
// import the custom data type to map the JSON field to
import com.dbvis.demo.data.ConfigData;
@Entity(name = "configs")
public class Config {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "data")
private ConfigData data;
// getters and setters omitted for brevity...
}
When querying some data, Jackson will now create a ConfigData
object mapping the JSON content retrieved from the database. If you instead want to write JSON data, you will have to instantiate a new ConfigData
object with the desired info. Jackson will automatically convert it to JSON for you.
ConfigData
is nothing more than a POJO for data mapping. Defining POJO classes is a tedious process that involves boilerplate code. For this reason, you should use one of the many JSON to POJO converters available online.
In detail, this is what ConfigData
looks like:
package com.dbvis.demo.data;
public class ConfigData {
private ConfigDataGameplay gameplay;
private ConfigDataGraphics graphics;
// getters and setters...
}
Since the JSON data stored in the database contains two nested objects, you need two custom Java classes:
-
ConfigDataGameplay
:
package com.dbvis.demo.data; import java.util.List; public class ConfigDataGameplay { private String difficulty; private List<String> achievements; // getters and setters... }
-
ConfigDataGraphics
:
package com.dbvis.demo.data; import com.fasterxml.jackson.annotation.JsonProperty; public class ConfigDataGraphics { private String quality; private String resolution; @JsonProperty("anti_aliasing") private boolean antiAliasing; // getters and setters... }
Note the use of @JsonProperty
Jackson annotation to specify custom mapping behavior. Do not forget that Hibernate will use Jackson for serialization and deserialization.
The /api/configs/1
endpoint will now return:
{
"id": 1,
"data": {
"gameplay": {
"difficulty": "normal",
"achievements": [
"tutorial_passed",
"chapter_1_mastered"
]
},
"graphics": {
"quality": "high",
"resolution": "1920x1080",
"anti_aliasing": true
}
}
}
Et voilà! This is exactly the JSON data contained in the database.
Pros
- Support for any type of JSON data
- Flexible and customizable
- Enables typed JSON data management at the application level
Cons
- Defining mapping POJO classes is cumbersome
- Instantiating POJOs has an impact on performance
- JSON data in the database must follow the schema defined by the POJOs
Explore JSON Data With DbVisualizer
As seen above, dealing with JSON with Hibernate is not that complex. At the same time, you may need a tool to explore the JSON data stored in your database. This is where an advanced database client such as DbVisualizer comes into play!
DbVisualizer is a powerful database management tool that supports a wide range of databases, such as MySQL, Oracle, Microsoft SQL server, PostgreSQL, and more. It comes with advanced data exploration features that allow you to visually see, edit, create, and delete data. Also, DbVisualizer has in-depth support for SQL data types, including JSON types.
Here are the steps to explore JSON data using DbVisualizer:
- Connect to the database: Follow the wizard to set up a connection to your database.
- Navigate to the desired table: Once you have connected to the database, use the “Databases” dropdown on the left to navigate to the table that contains the JSON data.
- View the JSON data: Once you have navigated to the table, open it in a new tab, reach the “Data” tab, and double-click on the JSON data contained in some row. DbVisualizer allows you to view and edit it as a formatted JSON.
With DbVisualizer, dealing with JSON data has never been easier!
Conclusion
In this tutorial, you learned how to map JSON columns in Spring Boot. In detail, you saw three different approaches to represent JSON in Hibernate 6 and JPA 3. You can now map a JSON column to a String
, a Map
, and a custom Java object.
Also, you learned how important it is to have a tool to explore JSON data visually. Luckily, there is DbVisualizer, a fully-featured database client that comes with advanced data exploration capabilities. Try DbVisualizer for free!
FAQ
Let’s now answer some common questions about JSON mapping in Hibernate.
Is JSON column mapping possible in Spring Boot?
Yes, thanks to JPA and Hibernate, you can map JSON columns in Spring Boot. In particular, JPA 3 and Hibernate 6 have made JSON mapping easier and more straightforward.
Is it possible to define custom mapping behavior for JSON columns in Hibernate?
Yes, you can specify custom mapping behavior thanks to the annotations offered by Jackson or the JSON library configured in Hibernate by overriding the hibernate.type.json_format_mapper
setting.
Is it possible to map JSON columns as non-string in Hibernate?
Yes, you can map JSON columns in Hibernate to different types. All you have to is specify the desired type on the @Column
field. In detail, Hibernate supports Map
and custom POJO type for JSON mapping.
Can I map nested JSON fields using JPA and Hibernate in Spring Boot?
You can map nested JSON fields in JPA and Hibernate by associating your JSON field in the JPA entity to a custom POJO class. This class can then involve fields of custom types. Basically, you have to break down your nested JSON structure into several data-mapping POJOs.
What are the benefits of mapping JSON columns in JPA?
Mapping JSON columns in JPA allows you to perform CRUD operations on those fields at the application layer. Also, if mapping the JSON columns correctly, JPA will automatically serialize and deserialize it for you.
Top comments (0)