spring-data-rest
spring-data-rest copied to clipboard
How to update aggregate collections?
Here is a sample project
There are two entities:
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Child> children = new ArrayList<>();
// Skipped getters and setters
}
@Entity
@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "parent_id", "name" }) })
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@NotNull
@ManyToOne
private Parent parent;
// Skipped getters and setters
}
One repository for the aggregate root entity:
@RepositoryRestResource(path = "parents", collectionResourceRel = "parents")
public interface ParentRepository extends JpaRepository<Parent, Long> {
}
And the configuration:
@Component
public class SpringDataRestConfiguration implements RepositoryRestConfigurer {
@Autowired
private EntityManager entityManager;
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream()
.map(Type::getJavaType)
.toArray(Class[]::new));
}
}
I need to create/update the whole aggregate entity using one HTTP request. There is a number of tests. Some of them works fine, but I have the following questions/problems.
- How to create an aggregate entity with child entities in a single request. The test is the following:
@Test
void aggregateCollectionIsCreated() {
String collectionUrl = getCollectionUrlPath(Parent.class);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
String request1 = """
{
"id": 1,
"name": "parent1"
}
""";
ResponseEntity<String> response1 = testRestTemplate.postForEntity(
collectionUrl, new HttpEntity<>(request1, requestHeaders), String.class);
assertThat(response1.getStatusCode()).isEqualTo(HttpStatus.CREATED);
URI entityUrl = response1.getHeaders().getLocation();
String request2 = """
{
"id": 1,
"name": "parent1",
"children": [
{
"id": 1,
"name": "child1",
"parent": "%s"
},
{
"id": 2,
"name": "child2",
"parent": "%s"
}
]
}
""".formatted(entityUrl, entityUrl);
ResponseEntity<String> response2 = testRestTemplate.exchange(
entityUrl, HttpMethod.PUT, new HttpEntity<>(request2, requestHeaders), String.class);
System.out.println(response2.getBody());
assertThat(response2.getStatusCode()).isEqualTo(HttpStatus.OK);
ResponseEntity<String> response3 = testRestTemplate.getForEntity(entityUrl, String.class);
System.out.println(response3.getBody());
assertThat(response3.getBody()).isEqualTo(response2.getBody());
}
It works, but I have to do two requests. At first create the parent entity, get its URL. And after that I can add child entities, because I have to specify a parent URL for them. Is it possible to use one request without parent URLs?
- Child item addition doesn't work:
@Test
void itemIsAddedToAggregateCollection() {
String collectionUrl = getCollectionUrlPath(Parent.class);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
String request1 = """
{
"id": 1,
"name": "parent1"
}
""";
ResponseEntity<String> response1 = testRestTemplate.postForEntity(
collectionUrl, new HttpEntity<>(request1, requestHeaders), String.class);
assertThat(response1.getStatusCode()).isEqualTo(HttpStatus.CREATED);
URI entityUrl = response1.getHeaders().getLocation();
String request2 = """
{
"id": 1,
"name": "parent1",
"children": [
{
"id": 1,
"name": "child1",
"parent": "%s"
}
]
}
""".formatted(entityUrl);
ResponseEntity<String> response2 = testRestTemplate.exchange(
entityUrl, HttpMethod.PUT, new HttpEntity<>(request2, requestHeaders), String.class);
assertThat(response2.getStatusCode()).isEqualTo(HttpStatus.OK);
String request3 = """
{
"id": 1,
"name": "parent1",
"children": [
{
"id": 1,
"name": "child1",
"parent": "%s"
},
{
"id": 2,
"name": "child2",
"parent": "%s"
}
]
}
""".formatted(entityUrl, entityUrl);
ResponseEntity<String> response3 = testRestTemplate.exchange(
entityUrl, HttpMethod.PUT, new HttpEntity<>(request3, requestHeaders), String.class);
System.out.println(response3.getBody());
assertThat(response3.getStatusCode()).isEqualTo(HttpStatus.OK);
ResponseEntity<String> response4 = testRestTemplate.getForEntity(entityUrl, String.class);
System.out.println(response4.getBody());
assertThat(response4.getBody()).isEqualTo(response3.getBody());
}
I get the exception:
java.lang.IllegalStateException: Multiple representations of the same entity [com.example.aggregatecollection.Child#1] are being merged. Detached: [com.example.aggregatecollection.Child@727dc2d6]; Managed: [com.example.aggregatecollection.Child@25f23f02]
- And deletion of child items doesn't work:
@Test
void itemIsDeletedFromAggregateCollection() {
String collectionUrl = getCollectionUrlPath(Parent.class);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
String request1 = """
{
"id": 1,
"name": "parent1"
}
""";
ResponseEntity<String> response1 = testRestTemplate.postForEntity(
collectionUrl, new HttpEntity<>(request1, requestHeaders), String.class);
assertThat(response1.getStatusCode()).isEqualTo(HttpStatus.CREATED);
URI entityUrl = response1.getHeaders().getLocation();
String request2 = """
{
"id": 1,
"name": "parent1",
"children": [
{
"id": 1,
"name": "child1",
"parent": "%s"
},
{
"id": 2,
"name": "child2",
"parent": "%s"
}
]
}
""".formatted(entityUrl, entityUrl);
ResponseEntity<String> response2 = testRestTemplate.exchange(
entityUrl, HttpMethod.PUT, new HttpEntity<>(request2, requestHeaders), String.class);
assertThat(response2.getStatusCode()).isEqualTo(HttpStatus.OK);
String request3 = """
{
"id": 1,
"name": "parent1",
"children": [
{
"id": 1,
"name": "child1",
"parent": "%s"
}
]
}
""".formatted(entityUrl);
ResponseEntity<String> response3 = testRestTemplate.exchange(
entityUrl, HttpMethod.PUT, new HttpEntity<>(request3, requestHeaders), String.class);
System.out.println(response3.getBody());
assertThat(response3.getStatusCode()).isEqualTo(HttpStatus.OK);
ResponseEntity<String> response4 = testRestTemplate.getForEntity(entityUrl, String.class);
System.out.println(response4.getBody());
assertThat(response4.getBody()).isEqualTo(response3.getBody());
}
The is no any exceptions. But no DELETE SQL statement is executed at all.
The interesting thing is that it works differently on my full project. Child entities are added successfully. And also the last child entity in the list is deleted. The only problem on my real project is that an entity in the middle of the aggregate collection is not deleted, because instead of a DELETE SQL statement an UPDATE statement is executed. I have no idea how to repeat that behaviour on a small sample project. So i think that at first I should try to fix the sample project. Could you please help? Should it work at all?