Issue
How can I model many to many relationship with same entity with spring data jdbc. I have a scenario where a task can depend on several other task and at the same time the task has dependants but it is same object. I have done this previously with jpa
@JoinTable(
name = "task_dependencies",
joinColumns = @JoinColumn(name = "dependent_process"),
inverseJoinColumns = @JoinColumn(name = "depends_on_process")
)
private final Set<AsyncTask> dependencies = new HashSet<>();
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "dependencies")
private final Set<AsyncTask> dependents = new HashSet<>();
Solution
In your example AsyncTask
is an aggregate and its own aggregate root.
So references from one AsyncTask
to another are considered references between aggregates.
In Spring Data JDBC (and also recommended in Domain Driven Design) references to other aggregates are to be implemented as ids, not actual Java references.
For the mapping table you need a separate small entity which becomes part of the aggregate on one side of the relationship.
Instead of just an Id
you can use an AggregateReference
in order to avoid having Long
values all over the place.
The complete model could look like this:
@Table
class AsyncTask {
@Id
Long id;
String name;
@MappedCollection(idColumn = "FROM_ID")
Set<DependentTask> dependents = new HashSet<>();
AsyncTask(String name) {
this.name = name;
}
public void addDependent(AsyncTask task) {
dependents.add(new DependentTask(task.id));
}
}
@Table
class DependentTask {
@Column("TO_ID")
AggregateReference<AsyncTask, Long> id; // this is the Id of the dependent task, note that it is not marked with `@Id`
DependentTask(Long id) {
this.id = AggregateReference.to(id);
}
}
And be used like this:
AsyncTask one = repo.save(new AsyncTask("one"));
AsyncTask two = repo.save(new AsyncTask("two"));
AsyncTask three = new AsyncTask("three");
three.addDependent(one);
three.addDependent(two);
repo.save(three);
one.addDependent(two);
repo.save(one);
two.addDependent(two);
repo.save(two);
The underlying database schema looks like this:
CREATE TABLE ASYNC_TASK (
ID INTEGER IDENTITY PRIMARY KEY,
NAME VARCHAR (200) NOT NULL
);
CREATE TABLE DEPENDENT_TASK (
FROM_ID INTEGER NOT NULL REFERENCES ASYNC_TASK(ID),
TO_ID INTEGER NOT NULL REFERENCES ASYNC_TASK(ID)
);
The complete source code is on GitHub.
For more information on the topic read Spring Data JDBC, References, and Aggregates and Spring Data JDBC - How do I make Bidirectional Relationships?
Answered By - Jens Schauder
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)