使用Hibernate和JPA持久化JSON对象 [英] Persisting a JSON Object using Hibernate and JPA

查看:724
本文介绍了使用Hibernate和JPA持久化JSON对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在春季启动时将JSON对象存储在MySQL数据库中.我知道我做错了事,但是我无法弄清楚到底是什么原因,因为我对Spring还很陌生.

I am trying to store a JSON object in MySQL database in spring boot. I know I am doing something wrong but I a can't figure out what it is because I am fairly new to Spring.

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

I have a rest endpoint where I get the following JSON object (via HTTP PUT) and I need to store it in database so that the user can fetch it later (via HTTP GET).

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

请注意,在上述情况下,对象中的数量 可能会有所不同,由于该要求,我使用的是在控制器中捕获对象.

Note that in the above case The number of keys in the object may vary, Due to that requirement I am using a HashMap to catch the object in the controller.

@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对象保留在db中.但是我正在寻找一种将整个HashMap保留在一条记录中的方法.我读了一些书,他们建议我使用@ManyToMany映射.

As you can see in the method, I can iterate the HashMap and persist each Animal object in db. But I am looking for a way to persist the entire HashMap in a single record. I have did some reading and they suggest me to use a @ManyToMany mapping.

有人可以指出我以不同的方式坚持HashMap的方向吗? (或者使用@ManyToMany是唯一正确的方法吗?)

Can anyone point me in a direction to persist the HashMap in a different way? (or is using the @ManyToMany the only and right way to do this?)

推荐答案

正如我在

As I explained in this article, it's very easy to persist JSON objects using Hibernate.

您不必手动创建所有这些类型,只需获取 通过Maven Central使用以下依赖项:

You don’t have to create all these types manually, you can simply get them via Maven Central using the following dependency:

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

有关更多信息,请查看休眠类型开源项目.

For more info, check out the hibernate-types open-source project.

现在,解释一下它们是如何工作的.

Now, to explain how it all works.

假设您具有以下实体:

@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
}

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

Notice two things in the code snippet above:

  • @TypeDef用于定义新的自定义休眠类型jsonb-node,由JsonNodeBinaryType
  • 处理
  • properties属性的列类型为jsonb,并且已映射为Jackson JsonNode
  • the @TypeDef is used to define a new custom Hibernate Type, jsonb-node which is handled by the JsonNodeBinaryType
  • the properties attribute has a jsonb column type and it's mapped as a Jackson 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()
                );
            }
        };
    }
}

可以在这篇文章.

现在,JsonNodeTypeDescriptor负责将JsonNode转换为各种表示形式,在绑定参数或从底层ResultSet的JSON对象获取参数时,底层JDBC驱动程序可能会使用它们.

Now, the JsonNodeTypeDescriptor is responsible for transforming the JsonNode into various representations which might be used by the underlying JDBC Driver during binding parameters or fetching from the JSON object from the underlying ResultSet.

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());
    }

}

就是这样!

现在,如果您保存实体:

Now, if you save an entity:

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语句:

Hibernate is going to generate the following SQL statement:

INSERT INTO
    book 
(
    isbn, 
    properties, 
    id
) 
VALUES
(
    '978-9730228236', 
    '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',  
    1
)

您还可以将其重新加载并进行修改:

And you can also load it back and modify it:

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/\"" +
        "}"
    )
);

休眠为您处理UPDATE语句的问题:

Hibernate taking caare of the UPDATE statement for you:

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

可在 查看全文

登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆