将JPA实体的JSON字符串列自动映射到Java对象

7

我有一个JPA实体对象,其结构如下:

@Table(name="item_info")
class Item(){
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @Column(name="item_name")
    private String itemName;

    @Column(name="product_sku")
    private String productSku;

    @Column(name="item_json")
    private String itemJsonString;

    @Transient
    private ItemJson itemJson;

    //Getters and setters

}

itemJsonString字段包含一个json字符串值,例如'{"key1":"value1","key2":"value2"}'

而itemJson字段包含对应于json字符串的映射对象。

我从数据库中获取此实体对象的方式如下:

Item item = itemRepository.findOne(1L);    // Returns item with id 1

现在,itemJson字段为空,因为它是一个瞬态字段。我必须使用Jackson的ObjectMapper手动设置它,如下所示:

itemJson = objectMapper.readValue(item.getItemJsonString(), ItemJson.class);

当我执行itemRepository.findOne()时,如何使其自动将itemJson字段映射到Item对象并返回该对象?

3个回答

14

你最好实现一个javax.persistence.Converter。大致看起来应该像这样:

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

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

    @Override
    public String convertToDatabaseColumn(ItemJson entityValue) {
        if( entityValue == null )
            return null;

        ObjectMapper mapper = new ObjectMapper();

        return mapper.writeValueAsString(entityValue);
    }

    @Override
    public ItemJson convertToEntityAttribute(String databaseValue) {
        if( databaseValue == null )
            return null;

        ObjectMapper mapper = new ObjectMapper();

        return mapper.readValue(databaseValue, ItemJson.class);

    }
}

我曾经在WildFly上使用过它,除了将其放在我部署的war文件中之外,不需要做任何事情。


我尝试使用@Autowired注解来注入ObjectMapper:@Autowired private ObjectMapper om,但是没有成功。只有在将mapper定义为静态变量private static ObjectMapper om并通过autowired setter进行初始化时才能正常工作。您能否解释一下为什么简单的字段自动装配不起作用?我之所以问这个问题,是因为您在每个转换器方法中都使用了ObjectMapper mapper = new ObjectMapper();而不是使用自动装配。 - chill appreciator
@standalone 在这个例子中,我没有使用基于Spring的解决方案,而是使用了标准的JEE。在这个类中,一个单独的private static final ObjectMapper根据您的用例可能更有效率。 - stdunbar
我不知道为什么,但对于我来说,在每个转换器方法中使用 ObjectMapper mapper = new ObjectMapper(); 就是不起作用。映射器无法转换我需要的内容。但是当我自动装配映射器时,它可以工作。在自动装配的情况下,我担心像 registerModule()setDateFormat() 这样的操作会导致副作用。因为我使用调试器检查过,自动装配的 ObjectMapper 对象始终是相同的对象。你认为修改自动装配的 ObjectMapper 对象的状态是不好的吗? - chill appreciator

3
这里是 AttributeConverter+ JPA + Kotlin 的完整工作版本。
实体类:
在我的情况下,数据库使用的是 MySQL (8.x),支持将 JSON 作为列定义的底层数据类型,并且我们可以使用 @Convert 注解应用自定义转换器。
@Entity
data class Monitor (
    @Id
    val id: Long? = null,

    @Column(columnDefinition = "JSON")
    @Convert(converter = AlertConverter::class)
    var alerts: List<Alert> = emptyList(),

    var active: Boolean = false
)

转换器定义 属性转换器需要指定从数据到数据库的转换机制以及反向转换。我们使用Jackson将Java对象转换为字符串格式,反之亦然。

@Converter(autoApply = true)
class AlertConverter : AttributeConverter<List<Alert>, String> {

private val objectMapper = ObjectMapper()

 override fun convertToDatabaseColumn(data: List<Alert>?): String {
    return if (data != null && !data.isEmpty())
        objectMapper.writeValueAsString(data)
    else ""
 }

 override fun convertToEntityAttribute(dbData: String?): List<Alert> {
    if (StringUtils.isEmpty(dbData)) {
        return emptyList()
    }
    return objectMapper.readValue(dbData, object : TypeReference<List<Alert>>() {})
 }
}

0
你可以使用回调函数来在实体加载后进行操作。因此,在你的实体类中尝试像这样做:
@PostLoad
public void afterLoad() {
    ObjectMapper mapper = new ObjectMapper();
    itemJson = mapper.readValue(item.getItemJsonString(), ItemJson.class);
}

我原本计划使用PostLoad,但不确定它是否涵盖了所有情况。 - drunkenfist
每次 EntityManager 加载实体时,它都会调用 post load 方法,即使是查询操作也是如此。请参见此答案 https://dev59.com/kWkv5IYBdhLWcg3w1kSr - Petr Mensik
使用了@stdunbar的方法,因为我也必须在将JSON存储到数据库之前将其转换回字符串。 - drunkenfist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接