I am adding a many-to-many relationship between two entities in my Spring Boot application using JPA. I have the following mappings: Task class:
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "task_tags_dim",
joinColumns = {@JoinColumn(name = "task_id")},
inverseJoinColumns = {@JoinColumn(name = "tag_id")})
private Set<Tag> tags;
Tag class:
@ManyToMany(mappedBy = "tags", fetch = FetchType.LAZY)
private Set<Task> tasks;
The end goal is to have a task can be associated with multiple tags, and a single tag can be associated with multiple tasks. However, it seems that no matter how many times I try to add a tag to my task set, the task set does not 'save' the tags that were previously added so currentTask.getTags()
returns no results. I've seen similar posts like this one but it hasn't exactly been helpful for my use case: Hibernate many-to-many relation returns empty collection
Here is my TagService:
@Service
@Transactional
public class TagService {
@Autowired
private TagRepository tagRepository;
@Autowired
private TaskRepository taskRepository;
//create() will create a tag and link it to a specific task
public Tag create(Tag tag, Long task_id) {
try {
Optional<Task> optionalTask = taskRepository.findById(task_id);
if (optionalTask.isPresent()) {
Task currentTask = optionalTask.get();
Tag newTag = new Tag(tag.getName(), tag.getColor());
//currentTask.getTags().add(newTag);
//taskRepository.save(currentTask);
System.out.println(currentTask.getTags().size()); //always returns 0
Set<Tag> tags = currentTask.getTags();
tags.add(tag);
currentTask.setTags(tags);
taskRepository.save(currentTask);
return newTag;
}
} catch(IllegalArgumentException e) {
throw new IllegalArgumentException("Tag '" + tag.getName() + "' already exists.");
}
throw new NoSuchElementException("Task not found");
}
}
Is there something wrong with how I am using Cascade.type in my Task class?
After obtaining the task object from taskRepository.findById() I tried to retrieve those task's tags using getTags() but did not return any results. I also attempted to change the Fetch type to EAGER but no luck.
Your many-to-many relationship is bidirectional. When you are adding a new joining entry you must add it to the both sides (both entities). Implement a helper method in the Post entity like this:
public void addTag(Tag tag) {
tags.add(tag);
tag.getTasks().add(this);
}
In the service class you will call this method like this:
currentTask.addTag(newTag);
taskRepository.save(currentTask);
Similar to this you should implement a method for removing the joining entry:
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getTasks().remove(this);
}
This way you will have the implementation in one place and the relationship will be synchronized correctly.
Based on: https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
[Edit by @Mario Barragan after solving]:
I also had to override my hashCode() and equals() within the Task class, and add a @JsonIgnore header above my @ManyToMany.