Ich versuche, ein JSON-Objekt im Spring Boot in der MySQL-Datenbank zu speichern. Ich weiß, dass ich etwas falsch mache, aber ich kann nicht herausfinden, was es ist, weil ich für Spring ziemlich neu bin.
Ich habe einen Rest-Endpunkt, an dem das folgende JSON-Objekt (über HTTP PUT) abgerufen wird, und ich muss es in der Datenbank speichern, damit der Benutzer es später abrufen kann (über HTTP GET).
{
"A": {
"Name": "Cat",
"Age": "1"
},
"B": {
"Name": "Dog",
"Age": "2"
},
"C": {
"Name": "Horse",
"Age": "1"
}
}
Beachten Sie, dass in diesem Fall die number von keys im Objekt variieren können. Aufgrund dieser Anforderung verwende ich ein HashMap
-Objekt, um das Objekt im Controller abzufangen.
@RequestMapping(method = RequestMethod.POST)
public String addPostCollection(@RequestBody HashMap<String, Animal> hp) {
hp.forEach((x, y) -> {
postRepository.save(hp.get(x));
});
return "OK";
}
Wie Sie in der Methode sehen können, kann ich HashMap
durchlaufen und jedes Animal
-Objekt in db beibehalten. Aber ich suche nach einer Möglichkeit, die gesamte HashMap
in einem einzigen Datensatz zu erhalten. Ich habe etwas gelesen und es wird empfohlen, eine @ManyToMany
-Zuordnung zu verwenden.
Kann mich jemand in eine Richtung weisen, um die HashMap
anders zu verharren? (oder ist der @ManyToMany
der einzige und richtige Weg, dies zu tun?)
Wie ich in diesem Artikel erklärt habe, ist es sehr einfach, JSON-Objekte mit Hibernate zu persistieren.
Sie müssen nicht alle diese Typen manuell erstellen, sondern Sie erhalten einfach über Maven Central mit folgender Abhängigkeit:
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>${hibernate-types.version}</version> </dependency>
Weitere Informationen finden Sie im Open-Source-Projekt hibernate-types .
Nun, um zu erklären, wie alles funktioniert.
Angenommen, Sie haben die folgende Entität:
@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
}
Beachten Sie im Code-Snippet oben zwei Dinge:
@TypeDef
wird verwendet, um einen neuen benutzerdefinierten Ruhezustandstyp jsonb-node
zu definieren, der von JsonNodeBinaryType
verarbeitet wird.properties
hat einen Spaltentyp jsonb
und ist als Jackson JsonNode
zugeordnet.Der JsonNodeBinaryType
ist wie folgt implementiert:
public class JsonNodeBinaryType
extends AbstractSingleColumnStandardBasicType<JsonNode> {
public JsonNodeBinaryType() {
super(
JsonBinarySqlTypeDescriptor.INSTANCE,
JsonNodeTypeDescriptor.INSTANCE
);
}
public String getName() {
return "jsonb-node";
}
}
Der JsonBinarySqlTypeDescriptor
sieht folgendermaßen aus:
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()
);
}
};
}
}
Der AbstractJsonSqlTypeDescriptor-Quellcode kann in diesem Artikel visualisiert werden.
Nun ist JsonNodeTypeDescriptor
für die Umwandlung von JsonNode
in verschiedene Repräsentationen verantwortlich, die der zugrunde liegende JDBC-Treiber während der Bindungsparameter oder das Abrufen des JSON-Objekts vom zugrunde liegenden ResultSet
verwenden kann.
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());
}
}
Das ist es!
Wenn Sie nun eine Entität speichern:
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 generiert die folgende SQL-Anweisung:
INSERT INTO
book
(
isbn,
properties,
id
)
VALUES
(
'978-9730228236',
'{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',
1
)
Und Sie können es auch wieder laden und ändern:
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/\"" +
"}"
)
);
In den Ruhezustand nehmen Sie die Anweisung UPDATE
für Sie mit:
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
Der gesamte Code steht auf GitHub zur Verfügung.
Ihr JSON ist gut strukturiert, so dass normalerweise nicht die gesamte Karte in einem einzigen Datensatz gespeichert werden muss. Sie können die Hibernate/JPA-Abfragefunktionen nicht verwenden und vieles mehr.
Wenn Sie wirklich die gesamte Map in einem einzigen Datensatz beibehalten möchten, können Sie die Map in ihrer String-Darstellung beibehalten und, wie bereits vorgeschlagen, einen JSON-Parser wie Jackson verwenden, um Ihre HashMap neu zu erstellen
@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);
}
}
Ihre Tierklasse:
public class Animal {
private String name;
private int age;
/* getter and setter */
/* ... */
}
Und Sie könnten Ihre Controller-Methode in ändern
@RequestMapping(method = RequestMethod.POST)
public String addPostCollection(@RequestBody String hp) {
Animals animals = new Animals();
animals.setAnimalsString(hp);
animalsRepository.save(hp);
return "OK";
}
Sie können FasterXML (oder ähnliches) verwenden, um den Json in ein tatsächliches Objekt zu parsen (Sie müssen die Klasse definieren), und mit Json.toJson(yourObj).toString()
den Json-String abrufen. Es vereinfacht auch die Arbeit mit den Objekten, da Ihre Datenklasse möglicherweise auch über Funktionen verfügt.
Ein Tier ist ein Rekord. Sie speichern mehr Datensätze, nicht einen Datensatz. Sie können mehrere Datensätze in einer Transaktion festschreiben. Siehe: So bleiben viele Entitäten bestehen (JPA)