Я использую 2 класса с наследованием:
@Entity
@Data
@Inheritance(strategy = InheritanceType.JOINED)
@JsonIdentityInfo(generator = JSOGGenerator.class,
property = "material_id",
scope = Long.class)
@EqualsAndHashCode(exclude = {"materialCommentaries"})
@ToString(exclude = {"materialCommentaries"})
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public abstract class Material {
@Id
@Setter(AccessLevel.NONE)
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@ManyToOne(optional = false)
private Lesson lesson;
@NotNull
private String title;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "material")
private Set<MaterialCommentary> materialCommentaries;
}
@EqualsAndHashCode(callSuper = true)
@Entity
@Data
@ToString(callSuper = true)
public class VideoMaterial extends Material {
private String summary;
@JsonIgnore
@ContentId
private String contentId;
@JsonIgnore
@ContentLength
private long contentLength;
@JsonIgnore
@MimeType
private String mimeType;
}
У меня все нормально JSON с полем @class, когда я даю только VideoMaterial в контроллере:
@PostMapping("video_materials")
public VideoMaterial save(@RequestAttribute("material") VideoMaterial videoMaterial,
@RequestParam("content") MultipartFile multipartFile,
@AuthenticationPrincipal User creator) throws IOException {
validateSavingRequest(videoMaterial, creator);
return videoMaterialService.save(videoMaterial, multipartFile);
}
И JSON в ответ:
{
"@class": "ru.sstu.online_studying.entities.material.hierarchy.VideoMaterial",
"material_id": "1",
"id": 11,
"lesson": {/*A lot of fields here*/},
"title": "Title",
"materialCommentaries": null,
"summary": "Summary"
}
Но когда я пытаюсь вернуть EntityModel, я получаю JSOn без поля @class и получаю исключение:
com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class ru.sstu.online_studying.entities.material.hierarchy.VideoMaterial]: missing type id property '@class' (for POJO property 'content')
at [Source: (String)"{"material_id":"1","id":11,"lesson":{"type":"Lesson","lesson_id":"2","id":10,"title":"TestLesson","description":"Test description","section":{"section_id":"3","id":9,"name":"Test","description":"Test descr","startDate":null,"closeDate":null,"course":{"course_id":"4","id":8,"title":"TestCourse","description":null,"maxNumStudents":null,"requirements":null,"image":null,"teachers":[{"user_id":"5","id":2,"username":"7LAWWtL","password":"$2a$10$o8FNl6krC/gEmXxNfI3ByOlmU7cmJoF9h4jWPztOIZB5opwhd59DK","f"[truncated 1554 chars]; line: 1, column: 2054]
at com.fasterxml.jackson.databind.exc.InvalidTypeIdException.from(InvalidTypeIdException.java:43)
at com.fasterxml.jackson.databind.DeserializationContext.missingTypeIdException(DeserializationContext.java:1771)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingTypeId(DeserializationContext.java:1300)
at com.fasterxml.jackson.databind.jsontype.impl.TypeDeserializerBase._handleMissingTypeId(TypeDeserializerBase.java:299)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedUsingDefaultImpl(AsPropertyTypeDeserializer.java:164)
at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:105)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1196)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:527)
at com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty.deserializeSetAndReturn(ObjectIdReferenceProperty.java:94)
at com.fasterxml.jackson.databind.deser.impl.ObjectIdReferenceProperty.deserializeAndSet(ObjectIdReferenceProperty.java:87)
at com.fasterxml.jackson.databind.deser.impl.UnwrappedPropertyHandler.processUnwrapped(UnwrappedPropertyHandler.java:62)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeWithUnwrapped(BeanDeserializer.java:673)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:321)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3214)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3197)
at ru.sstu.online_studying.integration.controller.material.VideoMaterialControllerTest.save(VideoMaterialControllerTest.java:65)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
С этим JSON:
{
"material_id": "1",
"id": 11,
"lesson": {/*a lot of fields here*/},
"title": "Title",
"materialCommentaries": null,
"summary": "Summary",
"_links": {
"self": {
"href": "http://localhost/api/video_materials/11"
},
"delete": {
"href": "http://localhost/api/video_materials/11"
},
"updateContent": {
"href": "http://localhost/api/video_materials/11/content"
},
"updateSummary": {
"href": "http://localhost/api/video_materials/11/summary"
}
}
}
Я думаю, странно, что JsonIdentityInfo 'material_id' все еще находится в этом JSON, но @class исчез. Мой метод контроллера, который возвращает EntityModel:
@PostMapping("video_materials")
public EntityModel<VideoMaterial> save(@RequestAttribute("material") VideoMaterial videoMaterial,
@RequestParam("content") MultipartFile multipartFile,
@AuthenticationPrincipal User creator) throws IOException {
validateSavingRequest(videoMaterial, creator);
return videoAssembler.toModel(videoMaterialService.save(videoMaterial, multipartFile));
}
Мой метод PresentationModelAssembler:
public EntityModel<VideoMaterial> toModel(VideoMaterial entity) {
return new EntityModel<>(entity,
linkTo(methodOn(controllerClass).getVideoContent(entity.getId(), null)).withSelfRel(),
linkTo(methodOn(controllerClass).deleteVideoMaterial(entity.getId(), null)).withRel("delete"),
linkTo(methodOn(controllerClass).updateVideoContent(entity.getId(), null, null)).withRel("updateContent"),
linkTo(methodOn(controllerClass).updateSummary(entity.getId(), null, null)).withRel("updateSummary")
);
}
Мой макет Mvc запрос:
mockMvc.perform(
multipart("/api/video_materials/")
.file(mockMultipartFile)
.requestAttr("material", videoMaterial)
.with(user(sender))
)
.andExpect(status().isOk())
.andExpect(content().contentType(MediaTypes.HAL_JSON)
.andReturn()
.getResponse()
.getContentAsString()