使用Hibernate和JPA持久化JSON对象

16

我在尝试在Spring Boot中将JSON对象存储到MySQL数据库中。我知道我做错了什么,但由于我对Spring还比较陌生,所以无法找出问题所在。

我有一个rest端点,在那里我通过HTTP PUT获取以下JSON对象,并且需要将其存储在数据库中,以便用户稍后可以通过HTTP GET获取它。

{
  "A": {
    "Name": "Cat",
    "Age": "1"
  },
  "B": {
    "Name": "Dog",
    "Age": "2"
  },
  "C": {
    "Name": "Horse",
    "Age": "1"
  }
}

请注意,在上述情况下,对象中的键数可能会有所变化。为了满足这个要求,我在控制器中使用了HashMap来捕获对象。

@RequestMapping(method = RequestMethod.POST)
    public String addPostCollection(@RequestBody HashMap<String, Animal> hp) {

        hp.forEach((x, y) -> {
            postRepository.save(hp.get(x));
        });

        return "OK";

    }

可以看到在这个方法中,我可以遍历 HashMap 并将每个 Animal 对象保存到数据库中。但是我正在寻找一种将整个 HashMap 保存在单个记录中的方法。经过一些阅读,他们建议我使用 @ManyToMany 映射。

有没有人能指导我用不同的方式保存 HashMap?(或者使用 @ManyToMany 是唯一正确的方法吗?)


在单个记录中存储整个HashMap?还是在单个数据库事务中存储整个HashMap? - Ramanujan R
我是指单条记录。 - Fawzan
4个回答

30

Maven 依赖

第一件事是在你的项目的 pom.xml 配置文件中设置以下Hibernate Types Maven 依赖:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version>
</dependency>

领域模型

假设您拥有以下实体:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
    typeClass = JsonType.class, 
    defaultForType = JsonNode.class
)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String isbn;

    @Column(columnDefinition = "jsonb")
    private JsonNode properties;

    //Getters and setters omitted for brevity
}

注意,@TypeDef 用于指示Hibernate使用Hibernate Types项目提供的 JsonTypeJsonNode 对象映射到实体属性中。

测试时间

现在,如果您保存一个实体:

Book book = new Book();
book.setIsbn( "978-9730228236" );
book.setProperties(
    JacksonUtil.toJsonNode(
        "{" +
        "   \"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
)

你也可以重新加载它并进行修改:

Session session = entityManager.unwrap( Session.class );
 
Book book = session
    .bySimpleNaturalId( Book.class )
    .load( "978-9730228236" );
 
LOGGER.info( "Book details: {}", book.getProperties() );
 
book.setProperties(
    JacksonUtil.toJsonNode(
        "{" +
        "   \"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
 
-- Book details: {"price":44.99,"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon"}
 
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

谢谢您的提示!不过,您能让它与不支持任何JSON类型的H2数据库一起工作吗?我在测试中使用Spring Boot和H2。也许可以以某种方式覆盖H2方言,将JSON类型视为文本?但是我还没有想出来...谢谢。 - vernjan
@VladMihalcea 我的集成测试确实使用MySQL数据库,但在我的JPA单元测试中使用MySQL将是痛苦和荒谬的。 - TiggerToo

2
你可以使用 FasterXML(或类似的工具)将 Json 解析为实际对象(需要定义类),并使用 Json.toJson(yourObj).toString() 来检索 Json 字符串。这还简化了与对象一起工作的过程,因为你的数据类也可能有功能。

2

您的JSON结构良好,因此通常不需要在一个记录中持久化整个映射。您将无法使用Hibernate/JPA查询功能等许多功能。

如果您真的想在一个记录中持久化整个映射,则可以将映射以其字符串表示形式持久化,并像已经建议的那样,使用类似Jackson的JSON解析器来重建HashMap。

@Entity
public class Animals {

  private String animalsString;

  public void setAnimalsString(String val) {
    this.animalsString = val;
  }

  public String getAnimalsString() {
    return this.animalsMap;
  }

  public HashMap<String, Animal> getAnimalsMap() {
    ObjectMapper mapper = new ObjectMapper();
    TypeReference<HashMap<String,Animal>> typeRef = new TypeReference<HashMap<String,Animal>>() {};
    return mapper.readValue(animalsString, typeRef); 
  }

}

你的动物类:

public class Animal {

  private String name;
  private int age;

  /* getter and setter */
  /* ... */
}

你可以将你的控制器方法更改为

@RequestMapping(method = RequestMethod.POST)
public String addPostCollection(@RequestBody String hp) {
  Animals animals = new Animals();
  animals.setAnimalsString(hp);
  animalsRepository.save(hp);
  return "OK";
}

我认为这对我来说是最好的选择。将对象保存为字符串并映射到Map中。 - Fawzan

0

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