Skip to main content

Spring Data JPA Many-To-Many Explained Simply

 At a certain point in your Java career, it is inevitable to arrive at a point where you have models that are related to each other but cannot be solved with foreign keys.

A good example of a many-to-many relationships is the relationship between rice and sauces. I love ALL types of different rice with different types of sauces. Sometimes I like Jasmine rice with spicy curry, while other times I like sticky rice with soy sauce. In a pinch, I’ve even ate Jasmine rice with soy sauce! *crowd gasps*

Overall, many-to-many is a relationship that has infinite amounts of different combinations. Try to apply the rice analogy to a one-to-many relationship and it will not work.

Many-To-Many Relationships

To maintain a many-to-many relationship between two tables in a database, the only way is to have a third table which has a reference to both sides of the table. This table is called a “join” table and each entry in this table will connect the source table and target table.

Many-To-Many in Spring Data JPA

Modeling a many-to-many relationship in Spring Data is easy and the best way to remember them conceptually is we should include a collection in both models.

@Entity
public class Rice {
     
    @Id
    public Long id

    @ManyToMany
    public Set<Sauce> sauces;
}

@Entity
public class Sauce {
    @Id
    public Long id

    @ManyToMany
    public Set<Rice> rice;
}

This is a good start, but we must configure the model with the correct annotations.

We can do this with the @JoinTable annotation in the Rice class. You must also provide the name of the join table, foreign keys with the @JoinColumn and @InverseJoinColumn.

@ManyToMany
@JoinTable(
    name = "rice_sauces",
    joinColunms = @JoinColumn(name = "rice_id"),
    inverseJoinColumns = @JoinColumn(name = "sauce_id"))
Set<Sauces> sauces;

@JoinTable and @JoinColumn are not required, but it is best practice to name them explicitly since database table naming is a big deal.

On the otherside, we only have to set the mappedBy attribute of the @ManyToMany annotation in the course class:

@ManyToMany(mappedBy = "rice")
Set<Rice> rice;

Many-to-many Using a Composite Key

Our current rice and sauce model is a bit…. bland. Let’s sauce things up a bit more with the ability to put which culture our sauce combination is from!

Because every JPA entity needs a primary key. In this case a primary key is actually a composite key.

Composite keys require us to create a new class that holds different parts of the key.

@Embeddable
public class CultureTypeKey implements Serializable {

    @Columns(name = "rice_id")
    public Long riceId;

    @Column(name = "sauce_id")
    public Long sauceId;
}

But what is @Embeddable? As our program grows, you could get to a point where you have database tables that are too big. @Embeddable allows us to “split up” entities into seperate objects.

@Entity
public class Culture {
    @EmbeddedId
    CultureKey id;

    @ManyToOne
    @MapsId("riceId")
    @JoinColumn(name = "rice_id")
    Rice rice;

    @ManyToOne
    @MapsId("sauceId")
    @JoinColumn(name = "sauce_id")
    Sauce sauce;

    string culture;
}

Comments

Popular posts from this blog

Higher Order Functions in JavaScript Explained Simply

  What is a higher order function? In JavaScript, a higher-order function is a function that  takes one or more functions as arguments,  or returns a function as output. These functions are a powerful feature of the language that allow you to write concise and expressive code. The best way to learn higher order functions (and likely the most “real-world”) is to look at “map()” and “filter()”. Both take functions as arguments where the function is applied to each element in an array. Map For example, consider the  map  function, which is used to apply a function to each element of an array and return a new array containing the results. The  map   function is a higher-order function because it takes a function as its argumen t, in this case the function to apply to each element of the array. const numbers = [1, 2, 3, 4, 5]; const squares = numbers.map(x => x * x); Filter Another common higher-order function is  filter , which is used to select el...

Basic CRUD Array Operations in JavaScript

  Sometimes it pays just to stick to the basics. Here are CRUD operations for a basic array: 1. Create an element to the end of an array let clubs = ['baseball','running','computer club']; clubs.push('tennis'); console.log(clubs); 2. Delete an element from the end of an array let clubs = ['baseball','running','computer club']; const lastElement = seas.pop(); console.log(lastElement); 3. Reading an index from an element in an array let clubs = ['baseball','running','computer club']; let index = clubs.indexOf("running"); console.log(index); 4. Update an index from an element in an array let clubs = ['baseball','running','computer club']; clubs.splice(1, 0, 'tennis'); console.log(clubs);