Spring Boot Jpa one-to-many

0

In this article, we will build a one-to-many relationship. The example we have here is the relation between a movie and its cast. In the database, we will have two tables “movie” and another table “movie_character” representing the cast.

The one-to-many relationship is built by introducing a join column in the that refers to a key in another table. In this case the “movie_character” will have “movie_id” referring to an “id” in “movie” table.

The entity having the join column is called the owner of the relationship defined using @ManyToOne annotation.

public class MovieCharacter extends AbstractPersistable<Long> {
    private String characterName;

    @NotNull
    @ManyToOne
    @JoinColumn(name="movie_id")
    private Movie movie;

    @NotNull
    @ManyToOne
    @JoinColumn(name="actor_id")
    private Actor actor;
....
}

A one-to-many relationship is defined using @OneToMany annotation. It comes with a mappedBy element which indicates that the owning side resides on the other side of the relationship defined by the property that represents the join column.

@Entity
public class Movie extends AbstractPersistable<Long> {
    private String name;
    
    private Integer year;
...
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "movie")
    private Set<MovieCharacter> actors;
...
}

–The mappedBy element indicates that the owning side resides on the other side of the relationship

Movie:

package com.javarticles.springboot.movies.domain;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

import org.springframework.data.jpa.domain.AbstractPersistable;

@Entity
public class Movie extends AbstractPersistable<Long> {
    private String name;
    
    private Integer year;

    @ElementCollection(targetClass=Genre.class)
    @Column(name="genre", nullable=false)
    @CollectionTable(name="movie_genres", joinColumns= {@JoinColumn(name="movie_id")})
    private Set<Genre> genres;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "movie")
    private Set<MovieCharacter> actors;

    public Movie(){}

    public Movie(String name) {
        this();
        setName(name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    public Set<Genre> getGenres() {
        return genres;
    }

    public void setGenres(Set<Genre> genres) {
        this.genres = genres;
    }

    public MovieCharacter addCast(Actor actor, String characterName) {
        if (actors == null) {
            actors = new HashSet<>();
        }
        MovieCharacter movieActor = new MovieCharacter(this, actor, characterName);
        actors.add(movieActor);
        return movieActor;
    }

    public void setActors(Set<MovieCharacter> actors) {
        this.actors = actors;
    }

    public int getCastCount() {
        return actors == null ? 0 : actors.size();
    }

    public Set<MovieCharacter> getActors() {
        return actors;
    }
    
    public String toString() {
        return name;
    }
}

MovieCharacter:

package com.javarticles.springboot.movies.domain;

import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.springframework.data.jpa.domain.AbstractPersistable;

@Entity
@Table(name="movie_character")
public class MovieCharacter extends AbstractPersistable<Long> {
    private String characterName;

    @NotNull
    @ManyToOne
    @JoinColumn(name="movie_id")
    private Movie movie;

    @NotNull
    @ManyToOne
    @JoinColumn(name="actor_id")
    private Actor actor;

    MovieCharacter(){}

    MovieCharacter(Movie movie, Actor actor, String characterName) {
        this.movie = movie;
        this.actor = actor;
        this.characterName = characterName;
    }

    public Actor getActor() {
        return actor;
    }

    public String getCharacterName() {
        return characterName;
    }

    public void setCharacterName(String characterName) {
        this.characterName = characterName;
    }

    public Movie getMovie() {
        return movie;
    }

    public void setMovie(Movie movie) {
        this.movie = movie;
    }

    public void setActor(Actor actor) {
        this.actor = actor;
    }
    
    public String toString() {
        return characterName + "(" + actor.getName() + ")";
    }
}

Actor:

package com.javarticles.springboot.movies.domain;

import javax.persistence.Entity;
import javax.persistence.OneToMany;

import org.springframework.data.jpa.domain.AbstractPersistable;

import java.util.Set;
import java.util.stream.Collectors;

@Entity
public class Actor extends AbstractPersistable<Long> {
    @OneToMany(mappedBy = "actor")
    private Set<MovieCharacter> movies;

    private String name;

    public Actor(){}

    public Actor(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMovieCount() {
        return movies == null ? 0 : movies.size();
    }

    public Set<Movie> getMovies() {
        return movies.stream().map(MovieCharacter::getMovie).collect(Collectors.toSet());
    }
}

Let’s create some data now.

SpringbootMoviesJpaApplicationTests:

package com.javarticles.springboot.movies.springbootMoviesJpa;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.HashSet;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import com.javarticles.springboot.movies.domain.Actor;
import com.javarticles.springboot.movies.domain.Genre;
import com.javarticles.springboot.movies.domain.Movie;
import com.javarticles.springboot.movies.domain.MovieCharacter;
import com.javarticles.springboot.movies.repositories.ActorRepository;
import com.javarticles.springboot.movies.repositories.MovieCharacterRepository;
import com.javarticles.springboot.movies.repositories.MovieRepository;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class SpringbootMoviesJpaApplicationTests {
    @Autowired
    private MovieRepository movieRepository;

    @Autowired
    private ActorRepository actorRepository;

    @Autowired
    private MovieCharacterRepository movieActorRepository;

    @Test
    public void addActors() {

        String movieName = "Murder on the Orient Express";
        Movie murderOnOrient = new Movie();
        murderOnOrient.setName(movieName);
        murderOnOrient.setYear(2001);
        murderOnOrient.setGenres(new HashSet<>(Arrays.asList(Genre.DRAMA, Genre.MYSTERY)));
        movieRepository.save(murderOnOrient);

        String actorName = "Kenneth Branagh";
        String characterName = "Hercule Poirot";

        Actor kennethBranagh = new Actor(actorName);
        actorRepository.save(kennethBranagh);
        assertThat(actorRepository.findByName(actorName), equalTo(kennethBranagh));

        MovieCharacter herculeP = murderOnOrient.addCast(kennethBranagh, characterName);
        assertThat(herculeP.getActor().getName(), equalTo(actorName));
        assertThat(herculeP.getCharacterName(), equalTo(characterName));

        Actor johnnyDepp = new Actor("Johnny Depp");
        actorRepository.save(johnnyDepp);
        murderOnOrient.addCast(johnnyDepp, "Edward Ratchett");
        assertEquals(2, murderOnOrient.getCastCount());
        assertTrue(murderOnOrient.getActors().contains(herculeP));

        Movie pirates = new Movie("Pirates of the Caribbean: Dead Men Tell No Tales");
        pirates.setYear(2017);
        pirates.addCast(johnnyDepp, "Captain Jack Sparrow");
        movieRepository.save(pirates);

        System.out.println("cast: " + movieActorRepository.findByMovieName(movieName));
        System.out.println("Johnny Depp's movies: " + movieRepository.findByActorsActorName("Johnny Depp"));
    }
}

You can see the SQL that retrieves the movie cast given a movie name.

Output:

Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
2018-06-28 22:44:32.456  INFO 17536 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: insert into movie (name, year, id) values (?, ?, ?)
Hibernate: insert into actor (name, id) values (?, ?)
Hibernate: insert into movie_genres (movie_id, genre) values (?, ?)
Hibernate: insert into movie_genres (movie_id, genre) values (?, ?)
Hibernate: select actor0_.id as id1_0_, actor0_.name as name2_0_ from actor actor0_ where actor0_.name=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: insert into actor (name, id) values (?, ?)
Hibernate: insert into movie (name, year, id) values (?, ?, ?)
Hibernate: insert into movie_character (actor_id, character_name, movie_id, id) values (?, ?, ?, ?)
Hibernate: insert into movie_character (actor_id, character_name, movie_id, id) values (?, ?, ?, ?)
Hibernate: insert into movie_character (actor_id, character_name, movie_id, id) values (?, ?, ?, ?)
Hibernate: select moviechara0_.id as id1_2_, moviechara0_.actor_id as actor_id3_2_, moviechara0_.character_name as characte2_2_, moviechara0_.movie_id as movie_id4_2_ from movie_character moviechara0_ left outer join movie movie1_ on moviechara0_.movie_id=movie1_.id where movie1_.name=?
cast: [Hercule Poirot(Kenneth Branagh), Edward Ratchett(Johnny Depp)]
Hibernate: select movie0_.id as id1_1_, movie0_.name as name2_1_, movie0_.year as year3_1_ from movie movie0_ left outer join movie_character actors1_ on movie0_.id=actors1_.movie_id left outer join actor actor2_ on actors1_.actor_id=actor2_.id where actor2_.name=?
Johnny Depp's movies: [Pirates of the Caribbean: Dead Men Tell No Tales, Murder on the Orient Express]

Let’s run few SQLs to query the data.

mysql> select * from movie;
+----+--------------------------------------------------+------+
| id | name                                             | year |
+----+--------------------------------------------------+------+
|  1 | Murder on the Orient Express                     | 2001 |
|  4 | Pirates of the Caribbean: Dead Men Tell No Tales | NULL |
+----+--------------------------------------------------+------+
2 rows in set (0.00 sec)

mysql> select * from movie_character;
+----+----------------------+----------+----------+
| id | character_name       | actor_id | movie_id |
+----+----------------------+----------+----------+
|  5 | Captain Jack Sparrow |        3 |        4 |
|  6 | Hercule Poirot       |        2 |        1 |
|  7 | Edward Ratchett      |        3 |        1 |
+----+----------------------+----------+----------+
3 rows in set (0.00 sec)

mysql> select * from movie_genres;
+----------+-------+
| movie_id | genre |
+----------+-------+
|        1 |     6 |
|        1 |    11 |
+----------+-------+
2 rows in set (0.00 sec)

mysql> select * from actor;
+----+-----------------+
| id | name            |
+----+-----------------+
|  2 | Kenneth Branagh |
|  3 | Johnny Depp     |
+----+-----------------+
2 rows in set (0.00 sec)

Download the source code

This was an example about JPA one-to-many relation using spring boot.

You can download the source code here: springbootJpaOneToMany.zip
Share.

Comments are closed.