在Spring項目中使用關聯實體能夠享受到面向對象設計(OOD)帶來的優點, 但也會面臨一些問題,例如在大量的關聯會致使必定的性能降低,配置錯誤則會帶來更嚴重的性能問題。非瞬時數據在離開內存後主要流向兩個方向,數據庫和網絡。數據庫與對象的轉換由JPA規範處理,如何處理網絡傳輸時的序列化是另外一個重要問題,其中JSON是事實標準且十分簡潔,Jackson是Spring Boot標定的行業「習慣」,用來處理JSON序列化。java
完整的Jackson知識可能在後續系列中介紹,本文主要介紹如何定義一對關聯對象,但在序列化與反序列化時,關聯對象都以Id來表示(與採用非關聯方案的系統在序列化時一致)spring
@Entity public class Human extends Carrier { @Id private String id; @JsonIdentityReference(alwaysAsId = true) //(1) @OneToOne private Head head; private String hand; }
@JsonIdentityInfo( //(2) generator = ObjectIdGenerators.PropertyGenerator.class,//TODO test property = "id") @Entity public class Head { @Id private String id; }
經過在被管理實體上增長@JsonIdentityInfo(2)指明哪一列表明本類序列化結果的id列,並在主實體的關聯屬性上標註@JsonIdentityReference(alwaysAsId = true)(1)指明該對象屬性將在序列化過程當中被其Id替換。數據庫
如上述例子獲得的json是{id:'Bob', head:'bob_head_id', hand: 'bob_hand'},
而不是{id:'Bob', head:{id:'bob_head_id'}, hand: 'bob_hand'}。json
完成第一節後,關聯對象能夠正常的序列化,但當你試圖經過在第一步中取得的JSON對象更新這個實體,如 PUT: /humans BODY:{id:'Bob', head:bob_head_id, hand: 'bob_new_hand'},你將得到一個報錯,聲稱string 類型的屬性 head 沒法反序列化爲 Head 對象。這是由於你沒有顯示的通知Jackson如何處理反序列化。網絡
這時你要爲被管理的實體補充必須的信息,即提供一個包含反序列化時如何將Id轉化爲其所表明的對象的方法的類(3),這個方法一般都是從數據庫中讀取這個實體。app
@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", resolver = EntityIdResolver.class, //(3) scope=Head.class) @Entity public class Head { @Id private String id; }
經過scope指明咱們要操做的實體,並提供給Jackson 一個自定義的解析類(3),Jackson將在反序列化時經過咱們定義的方法把id轉換爲關聯實體。轉化類以下,ide
@Component @Scope("prototype") // must not be a singleton component as it has state public class EntityIdResolver extends SimpleObjectIdResolver { private EntityManager entityManager; @Autowired public EntityIdResolver (EntityManager entityManager) { this.entityManager = entityManager; } @Override public void bindItem(final ObjectIdGenerator.IdKey id, final Object pojo { super.bindItem(id, pojo); } @Override public Object resolveId(ObjectIdGenerator.IdKey id) { Object resolved = super.resolveId(id); if (resolved == null) { resolved = _tryToLoadFromSource(id); bindItem(id, resolved); } return resolved; } private Object _tryToLoadFromSource(ObjectIdGenerator.IdKey idKey) { requireNonNull(idKey.scope, "global scope does not supported"); return entityManager.find(idKey.scope, idKey.key); } @Override public ObjectIdResolver newForDeserialization(Object context) { return new EntityIdResolver(entityManager); } @Override public boolean canUseFor(ObjectIdResolver resolverType) { return Objects.equals(resolverType.getClass(), EntityManager.class); } }
這裏是一個ObjectIdResolver的實現,這個接口是Jackson提供的,在反序列化帶有@JsonIdentityReference(alwaysAsId = true)註解的屬性時,Jackson會實例化這個接口的實現完成轉換工做,咱們使用Jpa查詢轉換Id爲實體.性能
當你在一個非Spring環境中時,你已經完成了所有的工做。不幸的是,在第二步的例子中EntityManager是由Spring管理的bean,而咱們實現的ObjectIdResolver的子類 EntityIdResolver 必須由Jackson直接實例化,這將致使EntityIdResolver中的EntityManager對象被調用時拋出一個NPE。ui
爲此咱們但願Jackson能從Spring 上下文中獲取EntityIdResolver,而不是本身進行實例化,經過在裝配Jackson的工做對象(ObjectMapper)時提供一個自定義的 HandlerInstantiator能夠實現這個的功能。
HandlerInstantiator中列出了Jackson的大量須要用戶提供工做類來拓展功能的接口(常見的實現形式是如本例同樣在註解中指出自定義的類,由Jackson在運行時動態加載), 覆寫該類中的方法將自定義Jackson實例化這些拓展類的方式。查看源碼發現 @JsonIdentityInfo中使用的ObjectIdResolver也包含在HandlerInstantiator定義的方法resolverIdGeneratorInstance()中。HandlerInstantiator一個簡單的例子以下,this
@Component public class SpringBeanHandlerInstantiator extends HandlerInstantiator { @Autowired private ApplicationContext applicationContext; public ObjectIdResolver resolverIdGeneratorInstance(MapperConfig<?> config, Annotated annotated, Class<?> implClass) { try { return (EntityIdResolver)applicationContext.getBean("EntityIdResolver"); } catch(e) { return null; } } ... }
不要忘了把這個自定義的實例化模版添加到Jackson的構造器中。
@Configuration public class JacksonConfiguration { @Autowired SpringBeanHandlerInstantiator springBeanHandlerInstantiator; @Bean public Jackson2ObjectMapperBuilder jacksonBuilder(HandlerInstantiator handlerInstantiator) { return new Jackson2ObjectMapperBuilder().handlerInstantiator(handlerInstantiator); } }
固然實際使用中,你沒必要建立自定義HandlerInstantiator,由於Spring已經實現了一個SpringHandlerInstantiator,覆寫了所有方法。你只須要Spring配置中將它鏈接到Jackson2ObjectMapperBuilder.
@Configuration public class JacksonConfiguration { @Bean public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) { return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory()); } @Bean public Jackson2ObjectMapperBuilder jacksonBuilder(HandlerInstantiator handlerInstantiator) { return new Jackson2ObjectMapperBuilder().handlerInstantiator(handlerInstantiator); } }
未研究的部分包括
1> @JsonIdentityInfo 註解中generator的使用
2> SpringHandlerInstantiator的實現
關於Jackson在關聯關係中的完整應用請參考這篇文章
https://www.baeldung.com/jack...
本文的思路來源於這篇文章
https://stackoverflow.com/que...