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.spring boot one to many
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 “spring boot one to many”
This was an example about JPA one-to-many relation using spring boot.