Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reference an entity with inheritance in Spring Data REST when POSTing new entity?

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.

like image 948
tewe Avatar asked Sep 29 '22 13:09

tewe


1 Answers

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:

  • https://jira.spring.io/browse/DATAREST-344
  • http://docs.spring.io/spring-hateoas/docs/current/reference/html/#configuration.at-enable
like image 172
aux Avatar answered Oct 07 '22 20:10

aux