ESala
ESala

Reputation: 7058

Handling relations in POST requests with Spring Data JPA

I have the following entities, and a CrudRepository for each:

@Entity
class Movie {
    @Id Long id;
    @Column String name;
    @ManyToOne Person director;
}

@Entity
class Person {
    @Id Long id;
    @Column String name;
}

My controller looks like this:

@RestController
@RequestMapping("/movies")
class MovieController {

    private MovieRepository movies = ...;
    private PersonRepository people = ...;

    @PostMapping
    public Movie create(@RequestBody MovieRequest request) {
        // Get the director
        Person director = people.findById(request.directorId);

        // Create the new movie
        Movie movie = new Movie();
        movie.name = request.name;
        movie.director = director;

       // Save the new movie
       return movies.save(movie);
    }
}

class MovieRequest {
    String name;
    Long directorId
}

As you can see, the create method first loads the director by its id, then creates the new movie and finally saves it. This causes two trips to the database: the first one to retrieve the director and the second one to save the movie.

In this case it's not a big problem, but there could be an entity with lots of relations, which means potentially doing lots of queries to achieve a single insert.

Question: I would like to save the new movie in a singe database operation. Is there a way to avoid the initial person query? Is there a better way to handle cases like this?

Upvotes: 3

Views: 1528

Answers (3)

Supun Wijerathne
Supun Wijerathne

Reputation: 12938

I don't think your MOVIE table contains a 'DIRECTOR_NAME' column (Assuming you follow 2nd rule of normalization). It should be only DIRECTOR_ID.

So you can totally skip loading the director name in your scenario (provided that directId is being sent with the query parameters of the request).

Since you have(should have) a foreign key constraining between Movie.DIRECTOR_ID and DIRECTOR.ID , it will look after any constraint violation if you are trying to insert any DIRECTOR_ID that doesn't exist. So you don't need to worry.

Upvotes: 0

Gnk
Gnk

Reputation: 720

It will be ugly but you have personId in your request, so you can map you Movie with you long personId

class Movie {
    @Id Long id;
    @Column String name;
    @ManyToOne Person director;

    @Column(name="PERSON_ID")
    long personId;
}

in your controller

movie.setPersonId(request.directorId);

Upvotes: 0

andreybleme
andreybleme

Reputation: 689

There's no way to tell the code wich Person it needs to relates to your new Movie. So you really need to perform a query and manually make the association.

There is an alternative that would only be possible if your endpoint create the Person at the same time it creates the Movie. Then you could simply perform the 2 save actions or use a CascadeType=ALL to make a single save action.


If you're able to change your request parameters, might be a good choice to receive a complete Person object instead of accpeting a directorId. This way you could just make the association movie.director = director;.

Be careful with this kinf of approach: if the received Person object is not stored in your database, you'll get an Exception.


Maybe you could create a cache for your Directors. If you have all your Directors saved in a Redis for example, you could search the Director corresponding to the received directorId an then perform the association.

Of course you would still need to make a second operation, but it might be way cheaper than query the database.

Upvotes: 1

Related Questions