Elegant Ways to Persist JSON in Spring Boot: 4 Practical Implementations
This article demonstrates four techniques for persisting flexible JSON data in Spring Boot 3.5.0—using JPA @Convert with AttributeConverter, Hibernate @JdbcTypeCode, a custom MyBatis TypeHandler, and MyBatis‑Plus annotations—complete with code snippets, test cases, and execution results.
Introduction
In many business scenarios, semi‑structured data such as user configurations, product attributes, or log details need flexible storage. Traditional relational tables with fixed schemas are unsuitable, while JSON offers readability and extensibility. This article presents four ways to persist JSON objects in a Spring Boot 3.5.0 application using JPA and MyBatis.
2.1 Persisting JSON with JPA
@Convert annotation
The @Convert annotation allows custom conversion of entity attributes. By implementing AttributeConverter, Java types can be transformed to a database‑compatible format and back, enabling seamless handling of complex objects such as maps.
@Entity
@Table(name = "t_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
@Convert(converter = CustomerAttributesConverter.class)
private Map<String, Object> customerAttributes;
}The converter implementation uses Jackson to serialize/deserialize the map:
@Component
public class CustomerAttributesConverter implements AttributeConverter<Map<String, Object>, String> {
private final Logger logger = LoggerFactory.getLogger(CustomerAttributesConverter.class);
private final ObjectMapper objectMapper;
public CustomerAttributesConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public String convertToDatabaseColumn(Map<String, Object> customerInfo) {
try {
return objectMapper.writeValueAsString(customerInfo);
} catch (Exception e) {
logger.error("Serialization error", e);
return null;
}
}
@Override
public Map<String, Object> convertToEntityAttribute(String customerInfoJSON) {
try {
return objectMapper.readValue(customerInfoJSON, new TypeReference<HashMap<String, Object>>() {});
} catch (Exception e) {
logger.error("Deserialization error", e);
return null;
}
}
}Test cases show automatic conversion during save and query:
@Test
public void testSave() {
Customer customer = new Customer();
customer.setName("Pack_xg");
customer.setAge(33);
customer.setCustomerAttributes(Map.of(
"nickName", "xxxooo",
"address", "新疆乌鲁木齐"));
this.customerRepository.saveAndFlush(customer);
}
@Test
public void testQuery() {
System.err.println(this.customerRepository.findById(1L).get());
}Execution screenshots confirm the JSON is stored and retrieved correctly.
2.2 Persisting JSON with Hibernate @JdbcTypeCode
Hibernate 6+ introduces @JdbcTypeCode to specify the JDBC type directly. Setting the column type to SQLTypes.JSON lets a Map be serialized to JSON without extra converters.
@Entity
@Table(name = "t_person")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
@JdbcTypeCode(SqlTypes.JSON)
private Map<String, String> attributes = new HashMap<>();
}Saving an entity is straightforward:
@Resource
private PersonRepository personRepository;
@Test
public void testSave() {
Person person = new Person();
person.setAge(33);
person.setName("Pack_xg");
person.setAttributes(Map.of(
"nickName", "xxxooo",
"address", "新疆乌鲁木齐"));
this.personRepository.saveAndFlush(person);
}The resulting database entry contains the JSON representation of the map.
2.3 Persisting JSON with MyBatis Custom TypeHandler
When using pure MyBatis, a custom TypeHandler can convert between JSON strings and Java objects.
public interface StudentMapper {
@Insert("INSERT INTO t_student (name, details) VALUES (#{name}, #{details, typeHandler=com.pack.json.way3.JsonTypeHandler})")
@Options(useGeneratedKeys = true, keyProperty = "id")
Long insert(Student student);
@Select("SELECT id, name, details FROM t_student WHERE id = #{id}")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "details", column = "details", typeHandler = JsonTypeHandler.class)
})
Student selectById(Long id);
}The handler implementation uses Jackson for (de)serialization:
@MappedTypes({ Map.class, Object.class })
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonTypeHandler<T> extends BaseTypeHandler<T> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private Class<T> type;
public JsonTypeHandler(Class<T> type) { this.type = Objects.requireNonNull(type); }
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, toJson(parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
return parseJson(json);
}
private String toJson(T object) {
try { return objectMapper.writeValueAsString(object); }
catch (Exception e) { throw new RuntimeException("Failed to serialize object to JSON", e); }
}
private T parseJson(String json) {
if (json == null || json.isEmpty()) return null;
try { return objectMapper.readValue(json, type); }
catch (Exception e) { throw new RuntimeException("Failed to deserialize JSON to " + type.getSimpleName(), e); }
}
}Test code inserts a student with a map of details and verifies the stored JSON.
2.4 Persisting JSON with MyBatis‑Plus Annotation
MyBatis‑Plus provides a built‑in JacksonTypeHandler. By annotating the field with @TableField(typeHandler = JacksonTypeHandler.class), the map is automatically serialized to JSON.
@TableName("t_student")
public class Student {
private Long id;
private String name;
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> details;
}The mapper simply extends BaseMapper<Student>:
public interface StudentMapper extends BaseMapper<Student> {}Test insertion populates the map and saves the entity; the resulting row contains the JSON string.
Conclusion
The article provides four concrete approaches for persisting JSON data in Spring Boot applications: JPA’s @Convert with an AttributeConverter, Hibernate’s @JdbcTypeCode, a custom MyBatis TypeHandler, and MyBatis‑Plus’s built‑in JacksonTypeHandler. Each method includes full entity definitions, converter or handler code, and test snippets that demonstrate successful storage and retrieval of JSON‑encoded maps.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
