2016-11-25 100 views
3

我試圖在春季啓動時在MySQL數據庫中存儲JSON對象。我知道自己做錯了什麼,但我無法弄清楚它是什麼,因爲我對Spring很陌生。使用休眠和JPA持久化JSON對象

我有一個休息端點,我得到下面的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和DB堅持每個Animal對象。但我正在尋找一種方法將整個HashMap保存在一個記錄中。我已經做了一些閱讀,他們建議我使用@ManyToMany映射。

任何人都可以用不同的方式指向我以堅持HashMap的方向嗎? (或正在使用該@ManyToMany做到這一點的唯一和正確的方式?)

+0

「整個HashMap在單個記錄中」?或者單個數據庫事務中的整個hashmap? –

+0

我的意思是單一記錄。 – Fawzan

回答

8

正如我在this article中解釋的那樣,使用Hibernate持久化JSON對象非常容易。

您不必手動創建所有這些類型的,你可以通過Maven的中央使用以下依賴簡單地得到 他們:

<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

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

休眠會生成以下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/\"" + 
     "}" 
    ) 
); 

休眠正在爲您的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

謝謝你的提示!但是,您可以使用不知道任何JSON類型的H2數據庫進行此項工作嗎?我使用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字符串。它還簡化了對象的工作,因爲您的數據類也可能具有功能。

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

我認爲這對我來說是最好的選擇。將對象保存爲字符串並將其映射到Map。 – Fawzan