Spring Data Rest Repository with abstract class / inheritance
Solution 1
I'm using Spring Boot 1.5.1
and Spring Data Release Ingalls
.
KeyValueRepository
doesn't work with inheritance. It uses the class name of every saved object to find the corresponding key-value-store. E.g. save(new Foo())
will place the saved object within the Foo
collection. And abstractFoosRepo.findAll()
will look within the AbstractFoo
collection and won't find any Foo
object.
Here's the working code using MongoRepository:
Application.java
Default Spring Boot Application Starter.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
AbstractFoo.java
I've tested
include = JsonTypeInfo.As.EXISTING_PROPERTY
andinclude = JsonTypeInfo.As.PROPERTY
. Both seem to work fine!It's even possible to register the Jackson SubTypes with a custom JacksonModule.
IMPORTANT:
@RestResource(path="abstractFoos")
is highly recommended. Else the_links.self
links will point to/foos
and/bars
instead of/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
Nothing special here
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
- Without this part, the output of the objects would be separated in two arrays under
_embedded.foos
and_embedded.bars
. - The
supports
method ensures that for all classes which extendAbstractFoo
, the objects will be placed within_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);
}
}
EDIT
- Added
@Persistent
toFoo.java
andBar.java
. (Adding it toAbstractFoo.java
doesn't work). Without this annotation I got NullPointerExceptions when trying to use JSR 303 Validation Annotations within inherited classes.
Example code to reproduce the error:
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 { }
}
Solution 2
Please see the discussion in this resolved jira task for details of what is currently supported in spring-data-rest regarding JsonTypeInfo
. And this jira task on what is still missing.
To summarize - only @JsonTypeInfo
with include=JsonTypeInfo.As.EXISTING_PROPERTY
is working for serialization and deserialization currently.
Also, you need spring-data-rest 2.5.3 (Hopper SR3) or later to get this limited support.
Please see my sample application - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot
With include=JsonTypeInfo.As.EXISTING_PROPERTY
the type information is extracted from a regular property. An example helps getting the point of this way of adding type information:
The abstract class:
@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();
}
And the subclass:
@Entity @DiscriminatorValue("D")
@Getter @Setter
public class DecimalValue extends Value {
@Column(name = "DECIMAL_VALUE")
private BigDecimal value;
public String getType() {
return "DECIMAL";
}
}
Benjamin M
Updated on July 08, 2022Comments
-
Benjamin M almost 2 years
I can't get Spring Data Rest with class inheritance working.
I'd like to have a single JSON Endpoint which handles all my concrete classes.
Repo:
public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}
Abstract class:
@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; }
Concrete class:
public class MyFoo extends AbstractFoo { }
Now when calling
POST /abstractFoos
with{"type":"MY_FOO"}
, it tells me:java.lang.IllegalArgumentException: PersistentEntity must not be null!
.This seems to happen, because Spring doesn't know about
MyFoo
.Is there some way to tell Spring Data REST about
MyFoo
without creating a Repository and a REST Endpoint for it?(I'm using Spring Boot 1.5.1 and Spring Data REST 2.6.0)
EDIT:
Application.java:
@SpringBootApplication @EnableMapRepositories public class Application { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
-
Benjamin M about 7 yearsThis doesn't work. I'm using spring-data-rest 2.6.0. And use
KeyValueRepository
(no JPA). MyAbstractFoo
class has nowinclude=JsonTypeInfo.As.EXISTING_PROPERTY
and an abstract methodpublic abstract String getType()
. When posting data to/abstractFoos
I still getPersistentEntity must not be null!
. (Also tried Hopper SR7 with Spring Boot 1.4.4) -
Mathias Dpunkt about 7 yearsDo you have the full source code on github? Would like to have a closer look. Otherwise, it is hard to guess what's going wrong.
-
Benjamin M about 7 yearsI just switched from
KeyValueRepository
toMongoRepository
and now it's kinda working! Seems likeKeyValueRepository
can't handle inheritance. Still some issue: When executingGET /abstractFoos
my Objects are located under_embedded.foos
and_embedded.bars
. Why aren't they just a single collection? Now I can't sort them. (Can be fixed: stackoverflow.com/questions/29021221/… ) -
Benjamin M about 7 yearsI'll post an answer that behaves correctly for
MongoRepository
. I came across lot's of pitfalls... Thanks for your help. You'll get the reward :) -
Rossiar over 6 yearsHeads up if using this for moving data in and out of mongodb with spring, you will need this as well: gist.github.com/letalvoj/978e6c975398693fc6843c5fe648416d
-
Charlie over 6 years@Benjamin I saw ur JIRA issue about inheritance in SDR and there you mentioned you were able to use projection with inheritance hierarchy. I would be grateful if you could update it here as to how you got the projections to work
-
IPP Nerd over 3 yearsThe simple class name can also be used as discrminator "as existing property": public String getType2() { return getClass().getSimpleName().toUpperCase();}