我试图在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
唯一且正确的方法来执行此操作?)
答案 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-node
由JsonNodeBinaryType
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)