Search code examples
javaspringspring-data-jdbc

Spring boot data jdbc implement Many to Many relationship with same entity


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?