如何使用 JPA 和 Hibernate 将 MySQL JSON 列映射到 Java 实体属性
Posted
技术标签:
【中文标题】如何使用 JPA 和 Hibernate 将 MySQL JSON 列映射到 Java 实体属性【英文标题】:How to map a MySQL JSON column to a Java entity property using JPA and Hibernate 【发布时间】:2017-11-02 15:21:00 【问题描述】:我有一个声明为 JSON 类型的 mysql 列,但在将其映射到 JPA/Hibernate 时遇到问题。我在后端使用 Spring Boot。
这是我的一小部分代码:
@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;
程序返回错误并告诉我无法映射列。
在mysql表中列定义为:
json_value JSON NOT NULL;
【问题讨论】:
尝试使用 columnDefinition = "json" : @Column(name = "json_value", columnDefinition = "json") 请分享您收到的例外情况。 问题可以是 JSONArray 类型,因为如果我用 String 代替,一切正常。 异常:无法确定类型:org.json.JSONArray,在表:some_table_name。 【参考方案1】:你不必手动创建所有这些类型,你可以简单地得到 他们通过 Maven Central 使用以下依赖项:
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>$hibernate-types.version</version> </dependency>
欲了解更多信息,请查看Hibernate Types open-source project。
现在,解释一下它是如何工作的。
假设您有以下实体:
@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
注意上面代码sn-p中的两点:
@TypeDef
用于定义新的自定义 Hibernate 类型,json
,由 JsonType
处理
properties
属性具有json
列类型,它被映射为String
就是这样!
现在,如果你保存一个实体:
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 将生成以下 SQL 语句:
INSERT INTO
book
(
isbn,
properties,
id
)
VALUES
(
'978-9730228236',
'"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99',
1
)
你也可以加载回来修改它:
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 为您处理 UPDATE
声明:
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
GitHub 上提供的所有代码。
【讨论】:
打破我的 H2 单元测试:JdbcSQLNonTransientException: Unknown data type: "JSON";
@TiggerToo,当然,它会破坏你的 H2 测试。 MySQL 函数和特性旨在在 MySQL 上进行测试,而不是在 H2 上进行测试。除非您在生产中使用 H2(这不太可能),否则您没有理由要针对与生产中使用的数据库引擎不同的数据库引擎来测试数据访问层。
我相信是你我在另一个问题中说过这个,但是是的,有一个很好的理由使用不同的数据库引擎,它被称为单元测试。
测试数据库称为集成测试。单元测试是针对单元的,比如当你测试 POJO 的方法或使用 Mocks 的服务时。
它适用于 String、DTO、Map 或其他实体属性【参考方案2】:
在 Kotlin 中,上述建议的以下变体/组合对我有用:
@Entity
@Table(name = "product_menu")
@TypeDef(name = "json", typeClass = JsonStringType::class)
data class ProductMenu(
@Type(type = "json")
@Column(name = "menu_json", columnDefinition = "json")
@Convert(attributeName = "menuJson", converter = JsonToMapConverter::class)
val menuJson: HashMap<String, Any> = HashMap()
) : Serializable
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import java.io.IOException
import javax.persistence.AttributeConverter
class JsonToMapConverter : AttributeConverter<String, HashMap<String, Any>>
companion object
private val LOGGER = LoggerFactory.getLogger(JsonToMapConverter::class.java)
override fun convertToDatabaseColumn(attribute: String?): HashMap<String, Any>
if(attribute == null)
return HashMap()
try
val objectMapper = ObjectMapper()
@Suppress("UNCHECKED_CAST")
return objectMapper.readValue(attribute, HashMap::class.java) as HashMap<String, Any>
catch (e: IOException)
LOGGER.error("Convert error while trying to convert string(JSON) to map data structure.")
return HashMap()
override fun convertToEntityAttribute(dbData: HashMap<String, Any>?): String?
return try
val objectMapper = ObjectMapper()
objectMapper.writeValueAsString(dbData)
catch (e: JsonProcessingException)
LOGGER.error("Could not convert map to json string.")
return null
【讨论】:
【参考方案3】:Heril Muratovic's answer 很好,但我认为JsonToMapConverter
应该实现AttributeConverter<Map<String, Object>, String>
,而不是AttributeConverter<String, Map<String, Object>>
。这是适合我的代码
@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;
【讨论】:
我在使用上述类后收到此错误。我的问题是 - 我的 postgres 表有一个 JSON 列,需要将它们映射到 aggEntity 类中。所以使用了上面的类但是出错了。【参考方案4】:我更喜欢这样做:
创建从 Map 到 String 的转换器(属性转换器),反之亦然。 在域(实体)类中使用Map映射mysql JSON列类型代码如下。
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;
域(实体映射)类的一部分
...
@Column(name = "meta_data", columnDefinition = "json")
@Convert(attributeName = "data", converter = JsonToMapConverter.class)
private Map<String, Object> metaData = new HashMap<>();
...
这个解决方案非常适合我。
【讨论】:
嗨,我收到了这个错误。Unexpected error occurred: Error attempting to apply AttributeConverter
@Heril 相同
您混淆了转换器的定义。它应该实现AttributeConverter<Map<String, Object>, String>
对我不起作用。如果我将数据库数据类型更改为字符串,那么它可以工作。
DTO 中的数据类型是什么。【参考方案5】:
对于任何人都无法制作@J。王答题:
尝试添加这个依赖(它适用于hibernate 5.1和5.0,其他版本检查here)
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-5</artifactId>
<version>1.2.0</version>
</dependency>
并将这一行添加到实体中
@TypeDef(name = "json", typeClass = JsonStringType.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;
我使用 spring boot 1.5.9 和 hibernate-types-5 1.2.0 测试代码。
【讨论】:
这也是一个很好的解决方案,使用休眠类型,但我认为使用属性转换器和映射可能更好。在下面查看我的答案。【参考方案6】:如果你的 json 数组中的值是简单的字符串,你可以这样做:
@Type( type = "json" )
@Column( columnDefinition = "json" )
private String[] jsonValue;
【讨论】:
@Type
是 org.hibernate.annotations.Type
以上是关于如何使用 JPA 和 Hibernate 将 MySQL JSON 列映射到 Java 实体属性的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 JPA 和 Hibernate 将 MySQL JSON 列映射到 Java 实体属性
Spring boot、JPA、Hibernate 和 H2 DB 测试 - 序列不起作用 - 意外的代码路径”;SQL 语句:调用 seq_my_jobs [50000-193] 的下一个值
如何使用 JPA 和 Hibernate 映射 PostgreSQL 枚举