spring-projects / spring-data-rest

Simplifies building hypermedia-driven REST web services on top of Spring Data repositories
https://spring.io/projects/spring-data-rest
Apache License 2.0
914 stars 560 forks source link

Circular JPA entity references cause infinite recursion in LinkCollectingAssociationHandler/BasicPersistentEntity [DATAREST-396] #773

Open spring-projects-issues opened 9 years ago

spring-projects-issues commented 9 years ago

Balázs E. Pataki opened DATAREST-396 and commented

I have an entity that has a property of the same type as the entity itself.

Something like:

@Entity
public static class Location {  

  // NOTE: circular type reference
  @ManyToOne
  @JoinColumn(name="partOf")
  private Location partOf;
  …
}

When I create a Repository for it and try to use the exported REST service to access an instance of this entity, or any entity that references this entity will cause an infinite recursion like this:

at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:100)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)

It also happens when the partOf is not even set. I attached a simple test case, which shows this happening. Since this is not a JSON (Jackson) related bug @JsonIgnore cannot be used here. This seems to be some weird operation of the hypermedia link (association) generation functions.


Affects: 2.1.4 (Dijkstra SR4)

Attachments:

10 votes, 11 watchers

spring-projects-issues commented 9 years ago

Panos K. commented

We experience the very same problem (but we use Java 1.8)

We have a Company entity that has a @OneToMany mapping to a Location entity, which in turn, the Location entity, has a @OneToMany mapping to a Service entity and also a @OneToOne mapping to an Address entity. This goes into an infinite recursion in spring data rest:

at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)
at org.springframework.data.rest.webmvc.mapping.LinkCollectingAssociationHandler.doWithAssociation(LinkCollectingAssociationHandler.java:101)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:337)

If I create a standalone hibernate application and copy/paste the entities there everything works fine. I too believe there must be a problem with how hateoas tries to resolve the linked entities

spring-projects-issues commented 9 years ago

Pablo Lozano commented

Hi,

I did some tests on the example in the attachment, for what i see in the example the entity with the circular OneToMany reference has no repository of itself. Spring Rest depends that a repository exists for it to be able to expose an entity in a proper way, or at least process it in a hateoas manner.

As no repository exists, it will try to serialize it but first it must look if that entity has any relationship to any other that can be linked. As that entity is linked to itself it will try find more relationships to itself creating the loop.

A strange effect I noticed too was that if I removed the OneToMany relationship and added the bidirectional relationship of OneToOne on DummyLocation it created the relationship link to itself on the _links section of the response. I Haven't tested if the link works but It probably doesn't.

{
  "name" : "DummyPlace1",
  "location" : {
    "@class" : "com.model.DummyLocation",
    "name" : "DummyLoc1"
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost/dummyPlaces/1"
    },
    "dummyPlace" : {
      "href" : "http://localhost/dummyPlaces/1/dummyPlace"
    }
  }
}
{
  "name" : "DummyPlace1",
  "_links" : {
    "self" : {
      "href" : "http://localhost/dummyPlaces/1"
    },
    "location" : {
      "href" : "http://localhost/dummyPlaces/1/location"
    }
  }
}

The issue can be easily be worked around if you expose a repository for the related class. As showed above in the OneToOne example different kinds of issues can arise if Data Rest tries to access an Entity without a repository so maybe for the time being it should be avoided.

What should be done in my understanding could be to avoid the search for associations properties that are entities(maybe) and are not tied to a repository in class

LinkCollectingAssociationHandler This would leave the relationship handling in the hands of the developer.

If necessary I can work in fixing this issue

spring-projects-issues commented 9 years ago

Pablo Lozano commented

After a little bit more of inspection this would break embeddables, what should be avoided is the do associations in the case where: the relation cannot generate a link and the property owner is the same as the property.

If its okay I can generate a patpatchcg with some tests, and if is the correct aproach I can send a PR

spring-projects-issues commented 9 years ago

Dofs Serge commented

Proposed fix for version 2.2.2

spring-projects-issues commented 7 years ago

Oliver Drotbohm commented

Can anyone reproduce that on a current version? We've changed the way links get collected significantly which hopefully has solved this, too

spring-projects-issues commented 4 years ago

Spring commented

It worked till 3.1.11 (Spring Boot 2.1.9).

Now in 3.2.0 the bug is back (Boot 2.2.1)

spring-projects-issues commented 4 years ago

Spring commented

Can I do anything to help fixing the bug? At the moment this is a showstopper to switch to Boot 2.2 which is required for Java 13. :(

Maybe a new test project? Oliver Drotbohm

spring-projects-issues commented 4 years ago

Spring commented

Works again in 3.2.1

spring-projects-issues commented 3 years ago

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.