Jackson處理關聯映射與Spring

0. 序言

在Spring項目中使用關聯實體能夠享受到面向對象設計(OOD)帶來的優點, 但也會面臨一些問題,例如在大量的關聯會致使必定的性能降低,配置錯誤則會帶來更嚴重的性能問題。非瞬時數據在離開內存後主要流向兩個方向,數據庫和網絡。數據庫與對象的轉換由JPA規範處理,如何處理網絡傳輸時的序列化是另外一個重要問題,其中JSON是事實標準且十分簡潔,Jackson是Spring Boot標定的行業「習慣」,用來處理JSON序列化。java

完整的Jackson知識可能在後續系列中介紹,本文主要介紹如何定義一對關聯對象,但在序列化與反序列化時,關聯對象都以Id來表示(與採用非關聯方案的系統在序列化時一致)spring

1. @JsonIdentityReference 註解

@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

2. ObjectIdResolver

完成第一節後,關聯對象能夠正常的序列化,但當你試圖經過在第一步中取得的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爲實體.性能

3. HandlerInstantiator

當你在一個非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);
    }
}

4. SpringHandlerInstantiator

固然實際使用中,你沒必要建立自定義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);
        }
    }

5. 總結與參考文獻

未研究的部分包括
1> @JsonIdentityInfo 註解中generator的使用
2> SpringHandlerInstantiator的實現

關於Jackson在關聯關係中的完整應用請參考這篇文章
https://www.baeldung.com/jack...
本文的思路來源於這篇文章
https://stackoverflow.com/que...

相關文章
相關標籤/搜索