Spring Data Rest:能够在OneToMany关联中创建子项而无需设置exported = false

问题描述 投票:0回答:1

我有两个实体。父母是:

public class One implements Serializable {
@Id
private Long id;


@OneToMany(mappedBy="one", cascade = CascadeType.ALL)
@RestResource(exported = false)
private List<Two> twos;
}

孩子是:

@Entity
@IdClass(TwoPk.class)
public class Two implements Serializable {
@Id
@ManyToOne
@JoinColumn(name="ONE")
private One one;
}

@Id
@ManyToOne
@JoinColumn(name="OTHER_OBJ")
private OtherObj otherObj;
}

关键类TwoPk是:

@Embeddable
public class TwoPK implements Serializable {


@Column(name="ONE")
private Long one;

@Column(name="OTHER_OBJ")
private Long otherObj;
}

构成密钥的第三个对象具有以下结构:

@Entity
public class OtherObj implements Serializable {
@Id
private Long id;
}

我有两个实体的存储库。

感谢@RestResource(exported = false)我可以在一次调用中发布一个包含所有两个孩子的One实体。但是我想通过不使用“exported = false”来完成同样的事情,因为我想看到链接。此外,我还想导出两个存储库。可能吗?

如果有一个反序列化器可以同时使用URI和完整的JSON对象作为“twos”数组的子代,那将是很好的。

java spring spring-data spring-data-jpa spring-data-rest
1个回答
0
投票

我结束了自己的解决方案。我们的想法是创建一个Deserializer Modifier,它可以创建一个特殊的序列化程序:

  • 在字符串的情况下,我想它是一个URI,我将其解析为URI;
  • 如果是另一件事(复杂对象),则使用普通的BeanDeserializer。

最后,您需要使用@UriOrBean注释将在更新(POST,PUT或PATCH)期间接受URI或复杂对象的字段。

无需使用@RestResource(exported = false)

首先,我创建一个注释,以注释desider属性:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UriOrBean {

}

然后我创建一个特殊的两遍反序列化器,第一个传递创建一个上下文反序列化器:

public class UriOrBeanDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {

    private PersistentProperty<?> property;

    private ConversionService conversionService;

    public UriOrBeanDeserializer(PersistentProperty<?> property, ConversionService conversionService) {
        this.property = property;
        this.conversionService = conversionService;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JsonMappingException {
        JavaType propertyType = property.getType();
        JavaType contentType = propertyType;
        if (propertyType.isCollectionLikeType()) {
            contentType = propertyType.getContentType();
        }
        JsonDeserializer<Object> delegatee = ctxt.findNonContextualValueDeserializer(contentType);
        UriOrBeanDeserializerInternal objectDeserializer = new UriOrBeanDeserializerInternal(delegatee, new UriStringDeserializer(this.property, conversionService));

        JsonDeserializer<?> result;
        if (propertyType.isCollectionLikeType()) {
            CollectionLikeType collectionType = ctxt.getTypeFactory().constructCollectionLikeType(propertyType.getRawClass(),
                    contentType);
            ValueInstantiator instantiator = new CollectionValueInstantiator(this.property);
            result = new CollectionDeserializer(collectionType, objectDeserializer, null, instantiator);
        } else {
            result = objectDeserializer;
        }

        return result;
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return null;
    }
}

第二遍是真正的反序列化器,有两个委托,用于URI和实际对象。

private static class UriOrBeanDeserializerInternal extends DelegatingDeserializer {

    private static final long serialVersionUID = 2633089079583425906L;

    private JsonDeserializer<?> uriStringDeserializer;

    public UriOrBeanDeserializerInternal(JsonDeserializer<?> delegatee, JsonDeserializer<?> uriStringDeserializer) {
        super(delegatee);
        this.uriStringDeserializer = uriStringDeserializer;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
            throws JsonMappingException {
        return this;
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        JsonToken token = p.currentToken();
        if (token.isScalarValue()) {
            return uriStringDeserializer.deserialize(p, ctxt);
        }
        return _delegatee.deserialize(p, ctxt);
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
        return new UriOrBeanDeserializerInternal(_delegatee, uriStringDeserializer);
    }

}

从Spring Data Rest(C)Springframework复制的几个类:

/**
 * Classe quasi completamente copiata dall'equivalente in Spring Data Rest.

 * {@link ValueInstantiator} to create collection or map instances based on the type of the configured
 * {@link PersistentProperty}.
 *
 * @author Oliver Gierke
 */
private static class CollectionValueInstantiator extends ValueInstantiator {

    private final PersistentProperty<?> property;

    /**
     * Creates a new {@link CollectionValueInstantiator} for the given {@link PersistentProperty}.
     *
     * @param property must not be {@literal null} and must be a collection.
     */
    public CollectionValueInstantiator(PersistentProperty<?> property) {

        Assert.notNull(property, "Property must not be null!");
        Assert.isTrue(property.isCollectionLike() || property.isMap(), "Property must be a collection or map property!");

        this.property = property;
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.deser.ValueInstantiator#getValueTypeDesc()
     */
    @Override
    public String getValueTypeDesc() {
        return property.getType().getName();
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.deser.ValueInstantiator#createUsingDefault(com.fasterxml.jackson.databind.DeserializationContext)
     */
    @Override
    public Object createUsingDefault(DeserializationContext ctxt) throws IOException, JsonProcessingException {

        Class<?> collectionOrMapType = property.getType();

        return property.isMap() ? CollectionFactory.createMap(collectionOrMapType, 0)
                : CollectionFactory.createCollection(collectionOrMapType, 0);
    }
}

/**
 * Classe quasi completamente copiata dall'equivalente in Spring Data Rest.
 *
 * Custom {@link JsonDeserializer} to interpret {@link String} values as URIs and resolve them using a
 * {@link UriToEntityConverter}.
 *
 * @author Oliver Gierke
 * @author Valentin Rentschler
 */
private static class UriStringDeserializer extends StdDeserializer<Object> {

    private static final long serialVersionUID = -2175900204153350125L;
    private static final String UNEXPECTED_VALUE = "Expected URI cause property %s points to the managed domain type!";
    private static final TypeDescriptor URI_DESCRIPTOR = TypeDescriptor.valueOf(URI.class);

    private final PersistentProperty<?> property;
    private final ConversionService conversionService;

    /**
     * Creates a new {@link UriStringDeserializer} for the given {@link PersistentProperty} using the given
     * {@link UriToEntityConverter}.
     *
     * @param property must not be {@literal null}.
     * @param converter must not be {@literal null}.
     */
    public UriStringDeserializer(PersistentProperty<?> property, ConversionService conversionService) {

        super(property.getActualType());

        this.property = property;
        this.conversionService = conversionService;
    }

    /*
     * (non-Javadoc)
     * @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
     */
    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {

        String source = jp.getValueAsString();

        if (!StringUtils.hasText(source)) {
            return null;
        }

        try {
            URI uri = new UriTemplate(source).expand();
            TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(property.getActualType());

            return conversionService.convert(uri, URI_DESCRIPTOR, typeDescriptor);
        } catch (IllegalArgumentException o_O) {
            throw ctxt.weirdStringException(source, URI.class, String.format(UNEXPECTED_VALUE, property));
        }
    }

    /**
     * Deserialize by ignoring the {@link TypeDeserializer}, as URIs will either resolve to {@literal null} or a
     * concrete instance anyway.
     *
     * @see com.fasterxml.jackson.databind.deser.std.StdDeserializer#deserializeWithType(com.fasterxml.jackson.core.JsonParser,
     *      com.fasterxml.jackson.databind.DeserializationContext,
     *      com.fasterxml.jackson.databind.jsontype.TypeDeserializer)
     */
    @Override
    public Object deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
            throws IOException {
        return deserialize(jp, ctxt);
    }
}

DeserializerModifier:

@Component
public class UriOrBeanDeserializerModifier extends BeanDeserializerModifier implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public BeanDeserializerBuilder updateBuilder(DeserializationConfig config, BeanDescription beanDesc,
            BeanDeserializerBuilder builder) {
        PersistentEntities entities = applicationContext.getBean(PersistentEntities.class);
        Associations associationLinks = applicationContext.getBean(Associations.class);
        ConversionService conversionService = applicationContext.getBean("defaultConversionService", ConversionService.class);
        Iterator<SettableBeanProperty> properties = builder.getProperties();
        PersistentEntity<?, ?> entity = entities.getPersistentEntity(beanDesc.getBeanClass());

        if (entity == null) {
            return builder;
        }

        while (properties.hasNext()) {

            SettableBeanProperty property = properties.next();
            PersistentProperty<?> persistentProperty = entity.getPersistentProperty(property.getName());

            if (associationLinks.isLookupType(persistentProperty) || !associationLinks.isLinkableAssociation(persistentProperty)) {
                continue;
            }

            if (property.getAnnotation(UriOrBean.class) != null) {
                UriOrBeanDeserializer deserializer = new UriOrBeanDeserializer(persistentProperty, conversionService);
                builder.addOrReplaceProperty(property.withValueDeserializer((JsonDeserializer<?>) deserializer), false);
            }
        }

        return builder;
    }
}

最后是RepositoryRestConfigurerAdapter中的配置:

@Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
    objectMapper.registerModule(new Module() {

        @Override
        public String getModuleName() {
            return "it.eng.intesasanpaolo.reng0.be";
        }

        @Override
        public Version version() {
            return Version.unknownVersion();
        }
        @Override
        public void setupModule(SetupContext context) {
            context.addBeanDeserializerModifier(uriOrBeanDeserializerModifier);
        }

    });
}
© www.soinside.com 2019 - 2024. All rights reserved.