如何使用JPA和Hibernate将MySQL JSON列Map到Java实体属性

q5lcpyga  于 2023-03-03  发布在  Mysql
关注(0)|答案(7)|浏览(209)

我有一个MySQL列声明为类型JSON,我有问题,以Map它与JPA/Hibernate。我正在使用Sping 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;

程序返回一个错误,告诉我不能Map该列。
在mysql表中,该列定义为:

json_value JSON NOT NULL;
mhd8tkvw

mhd8tkvw1#

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

erhoui1w2#

在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
        }
    }
}
hm2xizp9

hm2xizp93#

对于任何无法让@J. Wang回答奏效的人:
尝试添加此依赖项

<dependency>
  <groupId>com.googlecode.json-simple</groupId>
  <artifactId>json-simple</artifactId>
  <version>1.1.1</version>
</dependency>

创建转换器
JSONObjectConverter.java

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

 private static final Logger logger = (Logger) LoggerFactory.getLogger(JSONArrayConverter.class);

 @Override
 public String convertToDatabaseColumn(JSONObject obj)
 {
    String data = null;
    try
    {
        data = obj.toString();
    }
    catch (final Exception e)
    {
        logger.error("JSON writing error", e);
    }

    return data;
 }

 @Override
 public JSONObject convertToEntityAttribute(String data)
 {
    JSONObject obj = null;

    try
    {
        Object temp = JSONValue.parse(data);
        obj = (JSONObject) temp ;
    }
    catch (final Exception e)
    {
        logger.error("JSON reading error", e);
    }

    return obj;
 }
}

域(实体Map)类的一部分

...

@Lob
@Column(name = "pano_data", columnDefinition = "JSON", nullable = true)
@Convert(converter = JSONObjectConverter.class)
private JSONObject panoData;

...

这个解决方案非常适合我。

qvsjd97n

qvsjd97n4#

您不必手动创建所有这些类型,只需使用以下依赖项通过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
}

请注意上面代码片段中的两件事:

  • @TypeDef用于定义新的自定义Hibernate类型json,该类型由JsonType处理
  • properties属性具有json列类型,并且它被Map为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上提供。

l2osamch

l2osamch5#

我更喜欢这样做:

  • 创建从Map到String的转换器(属性转换器),反之亦然。
  • 使用Map在域(实体)类中Mapmysql 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;
        }
    }
}

域(实体Map)类的一部分

...

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

...

这个解决方案非常适合我。

dsf9zpds

dsf9zpds6#

对于任何无法让@ J. Wang回答奏效的人:
尝试添加此依赖项(适用于hib5.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测试了这段代码。

jckbn6z7

jckbn6z77#

如果json数组中的值是简单的字符串,则可以执行以下操作:

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

相关问题