2016-11-25 2 views
3

Я пытаюсь сохранить объект JSON в базе данных MySQL в весенней загрузке. Я знаю, что делаю что-то неправильно, но я не могу понять, что это такое, потому что я довольно новичок в Spring.Сохранение объекта JSON с использованием Hibernate и JPA

У меня есть конечная точка отдыха, где я получаю следующий объект JSON (через HTTP PUT), и мне нужно сохранить его в базе данных, чтобы пользователь мог получить его позже (через HTTP GET).

{ 
    "A": { 
    "Name": "Cat", 
    "Age": "1" 
    }, 
    "B": { 
    "Name": "Dog", 
    "Age": "2" 
    }, 
    "C": { 
    "Name": "Horse", 
    "Age": "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"; 

    } 

Как вы можете видеть в методе, я могу итерируем HashMap и сохраняются каждый Animal объект в БД. Но я ищу способ сохранить весь HashMap в одной записи. Я сделал некоторое чтение, и они предлагают мне использовать отображение @ManyToMany.

Может ли кто-нибудь указать мне в направлении, чтобы сохранить HashMap по-другому? (или использует @ManyToMany единственный и правильный способ сделать это?)

+0

"весь HashMap в одной записи" ?? Или весь хэш-файл в одной транзакции db? –

+0

Я имел в виду одну запись. – Fawzan

ответ

8

Как я объяснил в this article, очень легко сохранить объект JSON с помощью Hibernate.

Вам не нужно создавать все эти типы вручную, вы можете просто получить их через 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 и отображается как Jackson JsonNode

The 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 Driver во время привязки параметров или извлечения из объекта JSON от лежащих в основе 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()); 
    } 

} 

Вот и все!

Теперь, если вы сохраните объект:

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.

+0

Спасибо за отзыв! Однако можете ли вы сделать эту работу с базой данных H2, которая не знает тип JSON? Я использую Spring Boot и H2 для тестирования. Возможно, переопределив диалект H2 так или иначе, чтобы рассматривать тип JSON как текст? Но не понял, как .. Спасибо – vernjan

+0

Если вы не запускаете H2 в производстве, и используете его для тестирования, в то время как ваша производственная система использует PostgreSQL или MySQL, ваши тесты интеграции не имеют никакой ценности. Вам нужно использовать тот же БД, который вы используете в производстве для тестирования. Проверьте [эту статью] (https://vladmihalcea.com/2017/02/09/how-to-run-integration-tests-at-warp-speed-with-docker-and-tmpfs/), чтобы узнать, как вы cna запускает интеграционные тесты на PostgreSQL и MySQL почти так же быстро, как и в БД в памяти. –

1

Вы можете использовать FasterXML (или аналогичный) для синтаксического анализа Json в реальном объекте (вам нужно определить класс) и использовать Json.toJson(yourObj).toString() для извлечения Json String. Это также упрощает работу с объектами, так как ваш класс данных также может иметь функциональность.

0

Одно животное - одна запись. Вы сохраняете больше записей, а не одну запись. Вы можете совершать больше записей в одной транзакции. How to persist a lot of entities (JPA)

1

Ваш JSON хорошо структурирован, поэтому обычно не нужно сохранять всю карту в одной записи. Вы не сможете использовать функции запроса Hibernate/JPA и многое другое.

Если вы действительно хотите, чтобы сохраняться всю карту в одной записи, вы могли бы сохраняться карту в его строковое представление и, как уже было предложено, использовать 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"; 
} 
+0

Я думаю, что это лучший вариант для меня. Сохранение объектов как строки и отображение его на карту. – Fawzan

Смежные вопросы