5

I have entities with joined inheritance:

Supporter

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "supporterType")
@JsonSubTypes({
 @JsonSubTypes.Type(value = PersonSupporterEntity.class, name = "PERSON"),
 @JsonSubTypes.Type(value = CompanySupporterEntity.class, name = "COMPANY")
})
@DiscriminatorColumn(name="supporter_type")
@Table(name = "supporter")
public class SupporterEntity extends UpdatableEntity {
 private long id;
 private SupporterType supporterType;
 private PartnerEntity partner;
...
}

PersonSupporter

@Entity
@DiscriminatorValue("PERSON")
@Table(name = "person_supporter")
public class PersonSupporterEntity extends SupporterEntity {
...
}

CompanySupporter

@Entity
@DiscriminatorValue("COMPANY")
@Table(name = "company_supporter")
public class CompanySupporterEntity extends SupporterEntity {
...
}

I have another entity which references SupporterEntity

@Entity
@Table(name = "contact")
public class ContactEntity extends UpdatableEntity {
 private long id;
 private SupporterEntity supporter;
...
 @ManyToOne // same error with @OneToOne
 @JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
 public SupporterEntity getSupporter() {
 return supporter;
 }
...
}

Repositories

@Transactional
@RepositoryRestResource(collectionResourceRel = "supporters", path = "supporters")
public interface SupporterEntityRepository extends JpaRepository<SupporterEntity, Long> {
 @Transactional(readOnly = true)
 @RestResource(path = "by-partner", rel = "by-partner")
 public Page<SupporterEntity> findByPartnerName(@Param("name") String name, Pageable pageable);
}
@Transactional
@RepositoryRestResource(collectionResourceRel = "person_supporters", path = "person_supporters")
public interface PersonSupporterEntityRepository extends JpaRepository<PersonSupporterEntity, Long> {
}
@Transactional
@RepositoryRestResource(collectionResourceRel = "company_supporters", path = "company_supporters")
public interface CompanySupporterEntityRepository extends JpaRepository<CompanySupporterEntity, Long> {
}
@Transactional
@RepositoryRestResource(collectionResourceRel = "contacts", path = "contacts")
public interface ContactEntityRepository extends JpaRepository<ContactEntity, Long> {
 @Transactional(readOnly = true)
 @RestResource(path = "by-supporter", rel = "by-supporter")
 public ContactEntity findBySupporterId(@Param("id") Long id);
}

I use Spring Boot, Spring Data REST, Spring Data JPA, Hibernate, Jackson. When I try to create a new ContactEntity with a post request like this:

{
 "supporter":"/supporters/52",
 "postcode":"1111",
 "city":"Test City 1",
 "address":"Test Address 1",
 "email":"[email protected]",
 "newsletter":true
}

I get this exception:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'supporterType' that is to contain type id (for class com.facer.domain.supporter.SupporterEntity)
 at [Source: HttpInputOverHTTP@4321c221; line: 1, column: 2] (through reference chain: com.facer.domain.supporter.ContactEntity["supporter"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.4.4.jar:2.4.4]

After 2 days of debugging I found a way, but I kinda guessed it. So if I post it like this:

{
 "supporter":{
 "supporterType":"PERSON",
 "id":"52"
 },
 "postcode":"1111",
 "city":"Test City 1",
 "address":"Test Address 1",
 "email":"[email protected]",
 "newsletter":true
}

It works, but I don't know why. What's wrong with the other request? It works like that everywhere else when the referenced entity does not have inheritance.

asked Mar 12, 2015 at 22:10
2
  • @zeroflagL I added the repositories and the relation Commented Mar 14, 2015 at 11:28
  • Just a side note, it's better to remove @Transactional from your repository interfaces, first, because it's an interface, second, because the service layer should be the master of transaction. On a custom repo implementation, I would suggest to use @Transactional(propagation=SUPPORTS) to enable non-transactional reads if needed to better performance. See stackoverflow.com/questions/3120143/… Commented Oct 28, 2015 at 23:25

3 Answers 3

3

Just another workaround using a RelProvider:

  1. Do not use @JsonTypeInfo
  2. Create a RelProvider for SupporterEntity sub-classes

    @Component
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class SupporterEntityRelProvider implements RelProvider {
     @Override
     public String getCollectionResourceRelFor(final Class<?> type) {
     return "supporters";
     }
     @Override
     public String getItemResourceRelFor(final Class<?> type) {
     return "supporter";
     }
     @Override
     public boolean supports(final Class<?> delimiter) {
     return org.apache.commons.lang3.ClassUtils.isAssignable(delimiter, SupporterEntity.class);
     }
    }
    

See also:

answered Oct 28, 2015 at 23:13
Sign up to request clarification or add additional context in comments.

1 Comment

It seems like RelProvider -> LinkRelationProvider: docs.spring.io/spring-hateoas/docs/current/api/org/…. I'm not going to edit this answer though since I'm not 100% certain.
1

It looks like a Jackson problem. To be specific, it's the following code in com.fasterxml.jackson.databind.deser.SettableBeanProperty:

if (_valueTypeDeserializer != null) {
 return _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
}
return _valueDeserializer.deserialize(jp, ctxt);

Without inheritance _valueDeserializer.deserialize would be called which in turn runs some Spring code to convert the URI to a Supporter.

With inheritance _valueDeserializer.deserializeWithType is called and vanilla Jackson, of course, expects an object, not a URI.

If supporter was nullable you could first POST to /contacts and then PUT the supporter's URI to /contacts/xx/supporter. Unfortunately I am not aware of any other solution.

answered Mar 14, 2015 at 19:14

3 Comments

I do not think it's Jackson issue. Good old Jackson works just fine. It's an issue with a custom Jackson URL serialiser from Spring DATA REST. Seems like this use-case is just not covered yet but there're workarounds.
@aux The problem occurs while deserializing the request body, so what exactly's got the "custom Jackson URL serialiser" to do with that?
Sorry, my misspelling, i meant deserializer, of course. By the way, this is already fixed in Spring Data REST 2.4 - see jira.spring.io/browse/DATAREST-662
0

You should be able to workaround this by setting @JsonTypeInfo(use= JsonTypeInfo.Id.NONE) at the property/method level e.g.

Try with this:

@ManyToOne // same error with @OneToOne
@JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
@JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
public SupporterEntity getSupporter() {
 return supporter;
}
answered Oct 4, 2015 at 20:41

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.