it-swarm.com.de

JSON-Objekt mit Hibernate und JPA beibehalten

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?) 

5
Fawzan

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.
  • das Attribut 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.

16
Vlad Mihalcea

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";
}
2
kaaas

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.

1
Petre Popescu

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)

0
Peter Šály