javascript
Spring data JPA 之 Jackson 在实体里面的注解详解
8 Spring data JPA 之 Jackson 在實體里面的注解詳解
經過前?課時的講解,相信你已經對實體??的 JPA 注解有了?定的了解,但是實際?作中你會發現實體??不僅有 JPA 的注解,也會?到很多 JSON 相關的注解。
我們? Spring Boot ??默認集成的 fasterxml.jackson 加以說明,這看似和 JPA 沒什么關系,但是?旦我們和 @Entity ?起使?的時候,就會遇到?些問題,特別是新?同學,我們這?課時詳細介紹?下?法。先來跟著我了解?下 Jackson 的基本語法。
8.1 Jackson 的基本語法
當我們? spring boot starter 的時候就會默認加載 fasterxml 相關的 jar 包模塊,包括核?模塊以及 jackson 提供的?些擴展 jar 包,下?詳細介紹。
8.1.1 三個核心模塊
jackson-core:核?包,提供基于“流模式”解析的相關 API,它包括 JsonPaser 和 JsonGenerator。Jackson 內部實現正是通過?性能的流模式 API 的 JsonGenerator 和 JsonParser 來?成和解析 json。
jackson-annotations:注解包,提供標準注解功能,這是我們必須要掌握的基礎語法。
jackson-databind:數據綁定包,提供基于“對象綁定”解析的相關 API(ObjectMapper) 和“樹模型”解析的相關 API(JsonNode);基于“對象綁定”解析的API 和“樹模型”解析的 API 依賴基于“流模式”解析的 API。如下圖中?些標準的類型轉換:
8.1.2 Jackson 提供的擴展 jar 包
- jackson-datatype-jdk8:是對 jdk8 語法??的?些 Optional、Stream 等?些新的類型做的?些?持。
- jackson-datatype-jsr310:是對 jdk8 中的 JSR310 時間協議做了?持,如 Duration、Instant、LocalDate、Clock 等時間類型的序列化、反序列化。
- .jackson-datatype-hibernate5:是對Hibernate的??的?些數據類型的序列化、反序列化,如 HibernateProxy 等。
剩下不常?的咱們就不說了,jackson-datatype 其實就是對?些常?的數據類型做序列化、反序列化,省去了我們??寫序列化、反序列化的過程。所以在我們?作中,如果需要?定義序列化的時候,可以參考這些源碼。
知道了這些脈絡之后,剩下的就是我們要掌握的注解有哪些了,下?我來介紹?下。
8.1.3 Jackson 中常用的一些注解
正如上?所說,我們打開 jackson-annotations,就可以看到有哪些注解了,??了然,閑著沒事的時候就可以到這??看看,這樣你會越來越熟悉。下?我們挑選?些常?的介紹?下。
Jackson ??常?的注解如下所示:
| @JsonProperty | 用于屬性,把屬性的名稱序列化給 JSON 字符串時轉換為另外一個名稱 @JsonProperty private String userName |
| @JsonFormat | 用于屬性或者方法,把屬性的格式序列化時指定成指定的格式 @JsonFormat private Date getCreateDate() |
| @JsonPropertyOrder | 用于類,指定屬性在序列化時 JSON 中的順序 @JsonPropertyOrder({“birth_date”,“name”}) private class User |
| @JsonCreator | 用于構造方法,和 @JsonProperty 配合使用,適用有參數的構造方法 @JsonCreator private User(@JsonProperty(“name”)String name) {} |
| @JsonAnySetter | 用于屬性或者方法,設置未反序列化的屬性名和值作為鍵值存儲到 map 中 @JsonAnySetter private void set(String key, Object value) { map.put(key, value); } |
| @JsonAnyGetter | 用于方法,獲取所有未序列化的屬性,一般與 @JsonAnySetter 成對出現 @JsonAnyGetter private Map<String,Object> get() { return map; } |
| @JsonIgnore | 用于告訴 Jackson 在序列化,反序列化時忽略 Java 對象的某個屬性(字段) @JsonIgnore private long personId = 0; |
| @JsonIgnoreProperties | 注解在類聲明上方,用于指定要忽略的類的屬性列表 @JsonIgnoreProperties({“birth_date”,“name”}) private class User |
| @JsonAutoDetect | 用于告訴 Jackson 在讀寫對象時,包括非 public 修飾的屬性 |
| @JsonDeserialize,@JsonSerialize | 用戶指定字段的自定義序列化、反序列化 |
| @JsonInclude | 用于告訴 Jackson 包括那些情況下的屬性,例如,僅僅顯示非空的字段 @JsonInclude(JsonInclude.Include.NON_EMPTY) |
8.1.4 實例
?先,新建?個 UserJson 實體對象,將它轉成 Json 對象,如下所示:
@Entity @Data @Builder @AllArgsConstructor @NoArgsConstructor @JsonPropertyOrder({"createDate", "email"}) @JsonIgnoreProperties({"hibernateLazyInitializer"}) public class UserJson implements Serializable {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@JsonProperty("my_name")private String name;private Instant createDate;@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")private Date updateDate;private String email;@JsonIgnoreprivate String sex;@JsonCreatorpublic UserJson(@JsonProperty("email") String email) {System.out.println("其他業務邏輯");this.email = email;}@Transient@JsonAnySetterprivate Map<String, Object> other = new HashMap<>();@JsonAnyGetterpublic Map<String, Object> getOther() {return other;} }然后,我們寫?個測試?例,看?下運?結果。
@DataJpaTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class UserJsonRepositoryTest {@Autowiredprivate UserJsonRepository userJsonRepository;@BeforeAll@Rollback(false)@Transactionalvoid init() {UserJson user = UserJson.builder().name("jackxx").createDate(Instant.now()).updateDate(new Date()).sex("men").email("123456@126.com").build();userJsonRepository.saveAndFlush(user);}@Test@Rollback(false)public void testUserJson() throws JsonProcessingException {UserJson userJson = userJsonRepository.getById(1L);userJson.setOther(Maps.newHashMap("address", "shanghai"));ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(new JavaTimeModule());System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(userJson));} }最后,運??下可以看到如下結果。
{"createDate" : 1646107023.872000000,"email" : "123456@126.com","id" : 1,"updateDate" : "2022-03-01 11:57","my_name" : "jackxx","address" : "shanghai" }我們通過例?可以很容易想到使?場景是 SpringMvc 的情況下,在 get 請求的時候我們要?到序列化;在 post 請求的時候我們要?到反序列化,將 json 字符串反向轉化成實體對象。
那么在 Spring ?? Jackson 都有哪些應?場景呢?我們來看?下。
8.2 Jackson 和 Spring 的關系
8.2.1 應用場景一:Spring MVC 的 View 層
在Spring MVC中,我們需要知道Mvc的JSON視圖的加載原理。我們看?下源碼,mvc 對象的轉化類:HttpMessageConvertersAutoConfiguration,??要利?JacksonHttpMessageConvertersConfiguration,如下所示:
???的 MappingJackson2HttpMessageConverter 正是采? fasterxml.jackson 進?轉化的,看下?的圖?。
8.2.2 應用場景二:Open-Feign
我們在微服務之間相互調?的時候,都會?到 HttpMessageConverter ??的JacksonHttpMessageConverter 進?轉化。特別是在? open-feign ??的 Encode 和 Decode 的時候,我們就可以看到如下應?場景:
8.2.3 應用場景三:Redis 里面
redis、cacheable 都會?到 value 的序列化,都離不開 JSON 的序列化。
8.2.4 應用場景四:JMS 消息序列化
當我們項?之間解耦?到消息隊列的時候,可能會基于 JMS 消息協議發送消息,其也是基于 JSON 的序列化機制來繼續 converter 的,它在? JmsTemplate 的時候也會遇到同樣情況。
綜上四個場景所述,我們是經常和 Entity 打交道的,? @Entity ?要在各種場景轉化成 JSONString,所以 Jackson 的原理我們還是要掌握?些的,下?來分析?個?較重要的。
8.3 Jackson 的原理分析
8.3.1 Jackson 的可見性原理分析
前?我們看到了注解 @JsonAutoDetect JsonAutoDetect.Visibility 類包含與 Java 中的可?性級別匹配的常量,表示 ANY、DEFAULT、NON_PRIVATE、NONE、PROTECTED_AND_PRIVATE和PUBLIC_ONLY。
那么我們打開這個類,看?下源碼:
這??的代碼并不復雜,通過JsonAutoDetect 我們可以看到,Jackson 默認不是所有的屬性都可以被序列化和反序列化。默認的屬性可視化的規則如下:
所以我們可以通過私有字段的 public get 和 public set ?法控制是否可以序列化。這?可以和我們前?講到的“JPA 實體??的注解?效?式”做?下對?,也可以通過直接更改 ObjectMapper 設置可視化策略,如下所示:
ObjectMapper mapper = new ObjectMapper(); // PropertyAccessor ?持的類型有 ALL,CREATOR,FIELD,GETTER,IS_GETTER,NONE,SETTER // Visibility ?持的類型有 ANY,DEFAULT,NON_PRIVATE,NONE,PROTECTED_AND_PUBLIC,PUBLIC_ONLY mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);這樣,就可以直接看到所有字段了,包括私有字段。接著我們說?下反序列化相關?法。
8.3.2 反序列化最重要的方法
我們在做反序列化的時候要?到的三個重要?法如下所示。
public <T> T readValue(String content, Class<T> valueType) public <T> T readValue(String content, TypeReference<T> valueTypeRef) public <T> T readValue(String content, JavaType valueType)可以看出,反序列化的時候要知道 java 的 Type 是很重要的,如下:
// 單個對象的寫法: UserJson user = objectMapper.readValue(json, UserJson.class); // 返回List的返回結果的寫法: List<UserJson> personList2 = objectMapper.readValue(jsonListString, new TypeReference<List<UserJson>>(){}); // 返回List的返回結果,性能更快的寫法: List<UserJson> users = Arrays.asList(objectMapper.readValue(jsonListString, UserJson[].class));我們也可以看一下 AbstractJackson2HttpMessageConverter ??的?法。
這個時候你應該很好奇,readValue ??是如何判斷 java 類型的呢?我們看下 ObjectMapper 的源碼??做了如下操作:
public <T> T readValue(InputStream src, Class<T> valueType)throws IOException, StreamReadException, DatabindException {_assertNotNull("src", src);return (T) _readMapAndClose(_jsonFactory.createParser(src), _typeFactory.constructType(valueType)); }到這?,我們看到 typeFactory ??的 constructType 可以取到各種 type,那么點擊進去看看。
可以看到??處理各種 java 類型和泛型的情況,當我們??寫反射代碼的時候可以參考這?段,或者直接調?。此外,ObjectMapper ??還?個重要的概念就是 Moduel,我們來看下。
8.3.3 Module 的加載機制
ObejctMapper ??可以擴展很多 datatype,?不同的 datatype 封裝到了不通的 modules ??,我們可以 register 注冊進去不同的 module,從?處理不同的數據類型。
?前 Modules 官??站提供了很多內容,具體你可以查看這個?址:https://github.com/FasterXML/jackson#third-party-datatype-modules。這?我們重點說?下常?的加載機制。
我們通過在代碼??設置?個斷點,就可以很清楚地知道常?的 ModuleType 都有哪些,如 Jdk8、jsr310、Hibernate5 等。在MVC ??默認的 Module 也是圖上那些,Hibernate5 是我們??引?的,具體解決什么問題和如何?定義的呢?我們接著往下看。
我們通過在代碼??設置?個斷點,就可以很清楚地知道常?的 ModuleType 都有哪些,
如 Jdk8、jsr310、Hibernate5 等。在MVC ??默認的 Module 也是圖上那些,Hibernate5 是我們??引?的,具體解決什么問題和如何?定義的呢?我們接著往下看。
8.4 Jackson 與 JPA 常見的問題
我們? JPA 的時候,特別是關聯關系的時候,最常?的就是死循環了,你在使?時?定要注意。
8.4.1 如何解決死循環問題
第?種情況:我們在寫 ToString ?法,特別是 JPA 的實體的時候,很容易陷?死循環,因為實體之間的關聯關系配置是雙向的,我們就需要 ToString 的時候把??排除掉,如下所示:
第?種情況:在轉化JSON的時候,雙向關聯也會死循環。按照我們上?講的?法,這是時候我們要想到通過 @JsonIgnoreProperties(value={"address"}) 或者字段上?配置 @JsonIgnore,如下:
@JsonIgnore private List<UserAddress> address;此外,通過 @JsonBackReference 和 @JsonManagedReference 注解也可以解決死循環。
public class UserAddress {@JsonManagedReferenceprivate User user; ...}public class User implements Serializable {@OneToMany(mappedBy = "user",fetch = FetchType.LAZY)@JsonBackReferenceprivate List<UserAddress> address; ...}如上述代碼,也可以達到 @JsonIgnore 的效果,具體你可以??操作?下試試,原理都是?樣的,都是利?排除?法。那么接下來我們看下 HibernateModel5 是怎么使?的。
8.4.2 JPA 實體 JSON 序列化的常見報錯及解決方法
我們在實際跑之前講過的 user 對象,或者是類似帶有 lazy 對象關系的時候,經常會遇到下?的錯誤:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.example.jpa.example1.entity.User$HibernateProxy$4u6Wef9i["hibernateLazyInitializer"])這個時候該怎么辦呢?下?介紹?個解決辦法,第?個可以引? Hibernate5Module。
解決?法?:引? Hibernate5Module
代碼如下:
ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new Hibernate5Module()); String json = objectMapper.writeValueAsString(user); System.out.println(json);這樣?就不會報錯了。
Hibernate5Module ??還有很多 Feature 配置,例如 FORCE_LAZY_LOADING,強制 lazy ??加載就不會有上?的問題了。但是這個會有性能問題,我不建議使?。
還有 USE_TRANSIENT_ANNOTATION,利? JPA 的 @Transient 注解配置,這個默認是開啟的。所以基本上 feature 默認配置都是 ok 的,不需要我們動?,只要知道這回事就?了。
解決?法?:關閉 SerializationFeature.FAIL_ON_EMPTY_BEANS 的 feature
代碼如下:
ObjectMapper objectMapper = new ObjectMapper(); // 直接關閉 SerializationFeature.FAIL_ON_EMPTY_BEANS objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); String json = objectMapper.writeValueAsString(user); System.out.println(json);因為是 lazy,所以 empty 的 bean 的時候不報錯也可以。
解決?法三:對象上?排除“hibernateLazyInitializer”“handler”“fieldHandler”等
代碼如下:
@JsonIgnoreProperties(value = {"address", "hibernateLazyInitializer", "handler", "fieldHandler"}) public class User implements Serializable { }那有沒有其他 ObjectMapper 的推薦配置了呢?
8.4.3 推薦的配置項
ObjectMapper objectMapper = new ObjectMapper(); // empty beans不需要報錯,沒有就是沒有了 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); // 遇到不可識別字段的時候不要報錯,因為前端傳進來的字段不可信,可以不要影響正常業務邏輯 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 遇到不可以識別的枚舉的時候,為了保證服務的強壯性,建議也不要關?未知的,甚?給個默認的,特別是微服務?家的枚舉值隨時在變,但是?的服務是不需要跟著?起變的 objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true); objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true);時間類型的最佳實踐,如何返回 ISO 格式的標準時間
有的時候我們會發現,默認的 ObjectMapper ??的 module 提供的時間轉化格式可能不能滿?我們的要求,可能要進?擴展,這里提供?個?定義 module 返回 ISO 標準時間格式的?個案例,如下:
@Test @Rollback(false) public void testUserJson2() throws JsonProcessingException {UserJson userJson = userJsonRepository.findById(1L).get();userJson.setOther(Maps.newHashMap("address", "shanghai"));//?定義 myInstant 解析序列化和反序列化 DateTimeFormatter.ISO_ZONED_DATE_TIME 這種格式SimpleModule myInstant = new SimpleModule("instant", Version.unknownVersion()).addSerializer(java.time.Instant.class, new JsonSerializer<Instant>() {@Overridepublic void serialize(java.time.Instant instant,JsonGenerator jsonGenerator,SerializerProvider serializerProvider)throws IOException {if (instant == null) {jsonGenerator.writeNull();} else {jsonGenerator.writeObject(instant.toString());}}}).addDeserializer(Instant.class, new JsonDeserializer<Instant>() {@Overridepublic Instant deserialize(JsonParser jsonParser,DeserializationContext deserializationContext) throws IOException {Instant result = null;String text = jsonParser.getText();if (!StringUtils.hasText(text)) {result = ZonedDateTime.parse(text,DateTimeFormatter.ISO_ZONED_DATE_TIME).toInstant();}return result;}});ObjectMapper objectMapper = new ObjectMapper();// 注冊?定義的moduleobjectMapper.registerModule(myInstant);String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(userJson);System.out.println(json); }我們利?上?的 UserJson 案例,在測試?例???定義了 myInstant 來進?序列化和反序列化Instant這種類型,然后我們通過 objectMapper.registerModule(myInstant); 注冊進去。那么我們看?下運?結果:
{"createDate" : "2022-03-20T01:46:49.466Z","email" : "123456@126.com","id" : 1,"updateDate" : "2022-03-20 09:46","my_name" : "jackxx","address" : "shanghai" }這時你會發現 createDate 的格式發?了變化,這樣?的話,任何?看到我們這樣的 JSON 結構就不必問我們到底是哪個時區的問題了。
8.5 本章小結
到這?,關于 Spring Data JPA 的基礎知識也告?段落,這?課時詳細講解了 Jackson 的原理,分析了?下 JPA ??經常會遇到的問題,并推薦了?些常?配置。有個需要注意的點就是雙向關聯關系,如果你暫時不得要領的話,我建議不要為了???,我們就按照 DB 的真實映射寫法就可以,類似 MyBatis ?樣,只不過不需要我們去關?和配置映射關系。
這?我還想說?個解題思路,就是當我們遇到問題的時候,要學著挖?挖問題的根源,這樣解決問題才能夠游刃有余。
總結
以上是生活随笔為你收集整理的Spring data JPA 之 Jackson 在实体里面的注解详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MacW资讯:微软确认没有启动Mac版O
- 下一篇: 云南农职《JavaScript交互式网页