Working with the Many in a JPA @ManyToMany

We previously worked with the Many in a @OneToMany relationship using Contact Entities and multiple Contact Phones. Today we’ll look at @ManyToMany JPA processing by adding Hobbies into the mix.

If you recall, here’s the data schema of my GitHub Spring-Data app.

Here’s a sample record output.

Our Contact and Hobbies have a @ManyToMany relationship. Here’s the relationship defined in the Contact Model using the contact_hobby_ids lookup table.

 @ManyToMany
    @JoinTable(name = "contact_hobby_ids",
            joinColumns = @JoinColumn(name = "contact_id",
                    referencedColumnName = "contact_id",
                    nullable = false),
            inverseJoinColumns = @JoinColumn(name = "hobby_id",
                    referencedColumnName = "hobby_id",
                    nullable = false))
    public Set<Hobby> getHobbies() {
        return hobbies;
    }

Dynamically Adding a @ManyToMany Hobby to a New Contact

We’re going to add a new contact and in the process enter a hobby that doesn’t yet exist. We have a service to enter a new hobby separately, but we won’t cover that here as it is more interesting to create a hobby dynamically through a ContactDTO object. ContactDTO contains a Set<ContactPhone> and Set<Hobby> and we’ll be using it with a future MVC Controller. For now we’re using it in a ContactTestUtils class for testing ContactService.

Below is the essential logic of our ContactService.add(ContactDTO) service where we add a new Contact as well as a new Hobby.

First we save any new hobbies to the database, then we refresh our Contact object so that when we add the hobby in the final step we won’t encounter a NullPointerException on our newly created hobby.

if (contactDto.getHobbies() != null) {
            saveNewHobbiesToDatabase(contactDto);
}

em.refresh(saved);

if (contactDto.getHobbies() != null) {
    for (HobbyDTO hobbyDTO : contactDto.getHobbies()) {
        Hobby hobby = hobbyRepository.findByHobbyTitleIgnoreCase(hobbyDTO.getHobbyTitle());
        saved.getHobbies().add(hobby);
    }
}

Dynamically Adding a @ManyToMany Hobby on Contact Update

Adding a @ManyToMany Hobby when updating a Contact is very similar to adding it to a new Contact. The only change is the logic for determining if it should be added.

if (contactDto.getHobbies() != null) {
    for (HobbyDTO hobbyDTO : contactDto.getHobbies()) {
        Hobby hobby = hobbyRepository.findByHobbyTitleIgnoreCase(hobbyDTO.getHobbyTitle());

        if (!found.getHobbies().contains(hobby))
            found.getHobbies().add(hobby);
    }
}

Removing a @ManyToMany Hobby from a Contact

Below is the Service method to remove a Hobby from a Contact. We’ll pass the ContactDTO and the Long HobbyId of the hobby to be deleted. Using the JPA hobbyRepository findOne() call we’ll determine if the Contact contains the hobby and if so, remove it.

@Transactional(rollbackFor = NotFoundException.class)
@Override
public Contact removeHobby(ContactDTO contactDto, Long hobbyId) throws NotFoundException {
	LOGGER.info("Removing contact hobby with information: {}", contactDto);

	Contact found = findContactById(contactDto.getContactId());
	Hobby hobby = hobbyRepository.findOne(hobbyId);

	if (found.getHobbies().contains(hobby))
	    found.getHobbies().remove(hobby);

	return found;
}

Testing Adding a New Hobby to a Contact

Here is one of several tests covering the @ManyToMany processes described above. We’re retrieving a Contact, converting it to a ContactDTO object and confirming it contains 2 hobbies. We’ll then send that contactDTO object to the Contact update() service and afterward confirm it now contains a Jousting hobby.

The complete source for handling @ManyToMany items described in this article can be found in the v0.1.1 branch of my Spring-Data app on GitHub.