使用Hibernate和JPA持久化JSON对象

时间:2016-11-25 10:23:10

标签: java json spring hibernate jpa

我试图在Spring引导中将JSON对象存储在MySQL数据库中。我知道我做错了什么,但我无法弄清楚它是什么,因为我对Spring很新。

我有一个休息端点,我得到以下JSON对象(通过HTTP PUT),我需要将其存储在数据库中,以便用户以后可以获取它(通过HTTP GET)。

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

请注意,在上述情况下,对象中的数字 可能会有所不同。由于该要求,我使用的是{{ 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"; } 并在db中保留每个HashMap对象。但我正在寻找一种方法来将整个Animal保留在一条记录中。我做了一些阅读,他们建议我使用HashMap映射。

有人能指出我以不同方式坚持@ManyToMany的方向吗? (或正在使用HashMap唯一且正确的方法来执行此操作?)

4 个答案:

答案 0 :(得分:20)

正如我在this article中所解释的那样,使用Hibernate持久化JSON对象非常容易。

  

您不必手动创建所有这些类型,您可以简单地获取   它们通过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 = "jsonb-node", 
    typeClass = JsonNodeBinaryType.class
)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String isbn;

    @Type( type = "jsonb-node" )
    @Column(columnDefinition = "jsonb")
    private JsonNode properties;

    //Getters and setters omitted for brevity
}

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

  • @TypeDef用于定义新的自定义Hibernate类型,jsonb-nodeJsonNodeBinaryType
  • 处理
  • properties属性的jsonb列类型已被映射为杰克逊JsonNode

JsonNodeBinaryType的实现方式如下:

public class JsonNodeBinaryType
    extends AbstractSingleColumnStandardBasicType<JsonNode> {

    public JsonNodeBinaryType() {
        super( 
            JsonBinarySqlTypeDescriptor.INSTANCE, 
            JsonNodeTypeDescriptor.INSTANCE 
        );
    }

    public String getName() {
        return "jsonb-node";
    }
}

JsonBinarySqlTypeDescriptor如下所示:

public class JsonBinarySqlTypeDescriptor 
    extends AbstractJsonSqlTypeDescriptor {

    public static final JsonBinarySqlTypeDescriptor INSTANCE = 
        new JsonBinarySqlTypeDescriptor();

    @Override
    public <X> ValueBinder<X> getBinder(
            final JavaTypeDescriptor<X> javaTypeDescriptor
        ) {
        return new BasicBinder<X>(javaTypeDescriptor, this) {
            @Override
            protected void doBind(
                    PreparedStatement st, 
                    X value, 
                    int index, 
                    WrapperOptions options
                ) throws SQLException {
                st.setObject(
                    index, 
                    javaTypeDescriptor.unwrap(
                        value, 
                        JsonNode.class, 
                        options
                    ), 
                    getSqlType()
                );
            }

            @Override
            protected void doBind(
                    CallableStatement st, 
                    X value, 
                    String name, 
                    WrapperOptions options
                ) throws SQLException {
                st.setObject(
                    name, 
                    javaTypeDescriptor.unwrap(
                        value, 
                        JsonNode.class, 
                        options
                    ), 
                    getSqlType()
                );
            }
        };
    }
}

AbstractJsonSqlTypeDescriptor源代码可以在this article中可视化。

现在,JsonNodeTypeDescriptor负责将JsonNode转换为各种表示形式,这些表示形式可能在绑定参数期间由底层JDBC驱动程序使用,或从基础ResultSet从JSON对象获取

public class JsonNodeTypeDescriptor
        extends AbstractTypeDescriptor<JsonNode> {

    public static final JsonNodeTypeDescriptor INSTANCE = 
        new JsonNodeTypeDescriptor();

    public JsonNodeTypeDescriptor() {
        super( 
            JsonNode.class, 
            new MutableMutabilityPlan<JsonNode>() {
                @Override
                protected JsonNode deepCopyNotNull(
                        JsonNode value
                    ) {
                    return JacksonUtil.clone(value);
                }
            }
        );
    }

    @Override
    public boolean areEqual(JsonNode one, JsonNode another) {
        if ( one == another ) {
            return true;
        }
        if ( one == null || another == null ) {
            return false;
        }
        return
            JacksonUtil.toJsonNode(
                JacksonUtil.toString(one)
            ).equals(
                JacksonUtil.toJsonNode(
                    JacksonUtil.toString(another)
                )
            );
    }

    @Override
    public String toString(JsonNode value) {
        return JacksonUtil.toString(value);
    }

    @Override
    public JsonNode fromString(String string) {
        return JacksonUtil.toJsonNode(string);
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public <X> X unwrap(
            JsonNode value, 
            Class<X> type, 
            WrapperOptions options
        ) {
        if ( value == null ) {
            return null;
        }
        if ( String.class.isAssignableFrom( type ) ) {
            return (X) toString(value);
        }
        if ( JsonNode.class.isAssignableFrom( type ) ) {
            return (X) JacksonUtil.toJsonNode(toString(value));
        }
        throw unknownUnwrap( type );
    }

    @Override
    public <X> JsonNode wrap(X value, WrapperOptions options) {
        if ( value == null ) {
            return null;
        }
        return fromString(value.toString());
    }

}

那就是它!

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

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

所有代码均可在GitHub上使用。

答案 1 :(得分: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";
}

答案 2 :(得分:1)

您可以使用FasterXML(或类似的)将Json解析为实际对象(您需要定义类)并使用Json.toJson(yourObj).toString()来检索Json String。它还简化了对象的使用,因为您的数据类也可能具有功能。

答案 3 :(得分:0)

一只动物是一条记录。您正在保存更多记录,而不是一条记录。您可以在一个事务中提交更多记录。 请参阅:How to persist a lot of entities (JPA)