J'essaie de stocker un objet JSON dans la base de données MySQL au démarrage de printemps. Je sais que je fais quelque chose de mal, mais je ne peux pas comprendre ce que c'est parce que je suis assez nouveau au printemps.
J'ai un noeud final où j'obtiens l'objet JSON suivant (via HTTP PUT) et je dois le stocker dans une base de données pour que l'utilisateur puisse le récupérer plus tard (via HTTP GET).
{
"A": {
"Name": "Cat",
"Age": "1"
},
"B": {
"Name": "Dog",
"Age": "2"
},
"C": {
"Name": "Horse",
"Age": "1"
}
}
Notez que dans le cas ci-dessus, le nombre _ {declés de l'objet peut varier. En raison de cette exigence, j'utilise une HashMap
pour intercepter l'objet dans le contrôleur. .
@RequestMapping(method = RequestMethod.POST)
public String addPostCollection(@RequestBody HashMap<String, Animal> hp) {
hp.forEach((x, y) -> {
postRepository.save(hp.get(x));
});
return "OK";
}
Comme vous pouvez le constater dans la méthode, je peux effectuer une itération sur HashMap
et conserver chaque objet Animal
dans la base de données. Mais je cherche un moyen de conserver toute la HashMap
dans un seul enregistrement. J'ai lu un peu et ils me suggèrent d'utiliser un mappage @ManyToMany
.
Quelqu'un peut-il m'indiquer dans une direction de persister la HashMap
d'une manière différente? (ou utilisez-vous le @ManyToMany
la seule et bonne façon de faire cela?)
Comme je l'ai expliqué dans cet article , il est très facile de conserver un objet JSON à l'aide de Hibernate.
Vous n’avez pas à créer tous ces types manuellement, vous pouvez simplement obtenir les via Maven Central en utilisant la dépendance suivante:
<dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>${hibernate-types.version}</version> </dependency>
Pour plus d'informations, consultez le projet open-source hibernate-types .
Maintenant, pour expliquer comment tout cela fonctionne.
En supposant que vous ayez l'entité suivante:
@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
}
Remarquez deux choses dans l'extrait de code ci-dessus:
@TypeDef
est utilisé pour définir un nouveau type personnalisé Hibernate, jsonb-node
, qui est géré par la JsonNodeBinaryType
properties
a un type de colonne jsonb
et il est mappé en tant que Jackson JsonNode
La JsonNodeBinaryType
est implémentée comme ceci:
public class JsonNodeBinaryType
extends AbstractSingleColumnStandardBasicType<JsonNode> {
public JsonNodeBinaryType() {
super(
JsonBinarySqlTypeDescriptor.INSTANCE,
JsonNodeTypeDescriptor.INSTANCE
);
}
public String getName() {
return "jsonb-node";
}
}
La JsonBinarySqlTypeDescriptor
se présente comme suit:
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()
);
}
};
}
}
Le code source AbstractJsonSqlTypeDescriptor peut être visualisé dans cet article .
Désormais, la JsonNodeTypeDescriptor
est responsable de la transformation de la JsonNode
en diverses représentations pouvant être utilisées par le pilote JDBC sous-jacent lors de la liaison de paramètres ou lors de l'extraction de l'objet JSON à partir de la ResultSet
sous-jacente.
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());
}
}
C'est tout!
Maintenant, si vous enregistrez une entité:
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 va générer l'instruction SQL suivante:
INSERT INTO
book
(
isbn,
properties,
id
)
VALUES
(
'978-9730228236',
'{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',
1
)
Et vous pouvez aussi le charger et le modifier:
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 prenant caare de la déclaration UPDATE
pour vous:
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
Tout le code est disponible sur GitHub .
Votre JSON est bien structuré, il n'est donc généralement pas nécessaire de conserver toute la carte dans un seul enregistrement. Vous ne pourrez pas utiliser les fonctions de requête Hibernate/JPA et plus encore.
Si vous voulez vraiment conserver la carte entière dans un seul enregistrement, vous pouvez conserver la carte dans sa représentation sous forme de chaîne et, comme déjà proposé, utiliser un analyseur JSON comme Jackson pour reconstruire votre carte Hash
@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);
}
}
Votre classe d'animaux:
public class Animal {
private String name;
private int age;
/* getter and setter */
/* ... */
}
Et vous pouvez changer votre méthode de contrôleur pour
@RequestMapping(method = RequestMethod.POST)
public String addPostCollection(@RequestBody String hp) {
Animals animals = new Animals();
animals.setAnimalsString(hp);
animalsRepository.save(hp);
return "OK";
}
Vous pouvez utiliser FasterXML (ou similaire) pour analyser le Json dans un objet réel (vous devez définir la classe) et utiliser Json.toJson(yourObj).toString()
pour récupérer la chaîne Json. Cela simplifie également le travail avec les objets, car votre classe de données peut aussi avoir des fonctionnalités.
Un animal est un enregistrement. Vous enregistrez plusieurs enregistrements, pas un seul enregistrement. Vous pouvez valider plusieurs enregistrements en une seule transaction. Voir: Comment conserver beaucoup d’entités (JPA)