Spring Data Rest Repository с абстрактным классом / наследованием

Я не могу получить Spring Data Rest с работающим наследованием классов.

Я хотел бы иметь одну конечную точку JSON, которая обрабатывает все мои конкретные классы.

Repo:

public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}

Абстрактный класс:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes({
  @JsonSubTypes.Type(value = MyFoo.class, name = "MY_FOO")
})
public abstract class AbstractFoo {
  @Id public String id;
  public String type;
}

Конкретный класс:

public class MyFoo extends AbstractFoo { }

Теперь при звонке POST /abstractFoos с {"type":"MY_FOO"}, он говорит мне: java.lang.IllegalArgumentException: PersistentEntity must not be null!,

Это, кажется, происходит, потому что Spring не знает о MyFoo,

Есть ли способ рассказать Spring Data REST о MyFoo без создания репозитория и конечной точки REST для него?

(Я использую Spring Boot 1.5.1 и Spring Data REST 2.6.0)

РЕДАКТИРОВАТЬ:

Application.java:

@SpringBootApplication
@EnableMapRepositories
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

2 ответа

Решение

Я использую Spring Boot 1.5.1 и Spring Data Release Ingalls,

KeyValueRepository не работает с наследованием. Он использует имя класса каждого сохраненного объекта, чтобы найти соответствующее хранилище ключей-значений. Например save(new Foo()) поместит сохраненный объект в Foo коллекция. А также abstractFoosRepo.findAll() будет смотреть в AbstractFoo коллекция и не найдет Foo объект.

Вот рабочий код с использованием MongoRepository:

Application.java

По умолчанию Spring Boot Application Starter.

@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

AbstractFoo.java

  • Я проверял include = JsonTypeInfo.As.EXISTING_PROPERTY а также include = JsonTypeInfo.As.PROPERTY, Оба, кажется, работают нормально!

  • Можно даже зарегистрировать подтипы Jackson с помощью пользовательского модуля JacksonModule.

  • ВАЖНЫЙ: @RestResource(path="abstractFoos") настоятельно рекомендуется. Остальное _links.self ссылки будут указывать на /foos а также /bars вместо /abstractFoos,


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
@JsonSubTypes({
  @JsonSubTypes.Type(value = Foo.class, name = "MY_FOO"),
  @JsonSubTypes.Type(value = Bar.class, name = "MY_Bar")
})
@Document(collection="foo_collection")
@RestResource(path="abstractFoos")
public abstract class AbstractFoo {
  @Id public String id;
  public abstract String getType();
}

AbstractFooRepo.java

Ничего особенного здесь

public interface AbstractFooRepo extends MongoRepository<AbstractFoo, String> { }

Foo.java & Bar.java

@Persistent
public class Foo extends AbstractFoo {
  @Override
  public String getType() {
    return "MY_FOO";
  }
}

@Persistent
public class Bar extends AbstractFoo {
  @Override
  public String getType() {
    return "MY_BAR";
  }
}

FooRelProvider.java

  • Без этой части выходные данные объектов будут разделены на два массива под _embedded.foos а также _embedded.bars,
  • supports метод гарантирует, что для всех классов, которые расширяются AbstractFoo, объекты будут размещены в _embedded.abstractFoos,

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class FooRelProvider extends EvoInflectorRelProvider {
  @Override
  public String getCollectionResourceRelFor(final Class<?> type) {
    return super.getCollectionResourceRelFor(AbstractFoo.class);
  }

  @Override
  public String getItemResourceRelFor(final Class<?> type) {
    return super.getItemResourceRelFor(AbstractFoo.class);
  }

  @Override
  public boolean supports(final Class<?> delimiter) {
    return AbstractFoo.class.isAssignableFrom(delimiter);
  }
}

РЕДАКТИРОВАТЬ

  • добавленной @Persistent в Foo.java а также Bar.java, (Добавление его в AbstractFoo.java не работает). Без этой аннотации я получил NullPointerExceptions при попытке использовать аннотации проверки JSR 303 в унаследованных классах.

Пример кода для воспроизведения ошибки:

public class A {
  @Id public String id;
  @Valid public B b;

  // @JsonTypeInfo + @JsonSubTypes
  public static abstract class B {
    @NotNull public String s;
  }

  // @Persistent <- Needed!
  public static class B1 extends B { }
}

Пожалуйста, смотрите обсуждение в этой решенной задаче jira для деталей о том, что в настоящее время поддерживается в spring-data-rest относительно JsonTypeInfo, И это задание Джира о том, чего еще не хватает.

Подводя итог - только @JsonTypeInfo с include=JsonTypeInfo.As.EXISTING_PROPERTY в настоящее время работает для сериализации и десериализации.

Кроме того, вам нужна пружина data-rest 2.5.3 (Hopper SR3) или новее, чтобы получить эту ограниченную поддержку.

Пожалуйста, посмотрите мой пример приложения - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot

С include=JsonTypeInfo.As.EXISTING_PROPERTY информация о типе извлекается из обычного свойства. Пример помогает понять смысл этого способа добавления информации о типе:

Абстрактный класс:

@Entity @Inheritance(strategy= SINGLE_TABLE)
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
        include=JsonTypeInfo.As.EXISTING_PROPERTY,
        property="type")
@JsonSubTypes({
        @Type(name="DECIMAL", value=DecimalValue.class),
        @Type(name="STRING", value=StringValue.class)})
public abstract class Value {

    @Id @GeneratedValue(strategy = IDENTITY)
    @Getter
    private Long id;

    public abstract String getType();
}

И подкласс:

@Entity @DiscriminatorValue("D")
@Getter @Setter
public class DecimalValue extends Value {

    @Column(name = "DECIMAL_VALUE")
    private BigDecimal value;

    public String getType() {
        return "DECIMAL";
    }
}
Другие вопросы по тегам