我有以下实体,每个实体都有一个CrudRepository
:
@Entity
class Movie {
@Id Long id;
@Column String name;
@ManyToOne Person director;
}
@Entity
class Person {
@Id Long id;
@Column String name;
}
我的控制器如下:
@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
}
如您所见,create
方法首先通过其ID加载导演,然后创建新电影并最后保存它。这导致两次访问数据库:第一次是检索导演,第二次是保存电影。
在这种情况下,这不是什么大问题,但是可能存在一个具有很多关系的实体,这意味着可能要进行大量查询才能实现单个插入。
问题:我想将新电影保存在单一数据库操作中。有没有办法避免最初的人查询?有没有更好的方法来处理这种情况?
答案 0 :(得分:1)
没有办法告诉Person
所需要的代码与您的新Movie
相关。因此,您确实需要执行查询并手动进行关联。
只有在端点创建Person
的同时创建Movie
的情况下,才有其他选择。然后,您可以简单地执行2个保存操作,或使用CascadeType=ALL
进行单个保存操作。
如果您能够更改请求参数,则可以选择接收完整的Person
对象而不是使用directorId
。这样,您就可以建立关联movie.director = director;
。
请谨慎使用这种方法:如果收到的Person
对象未存储在数据库中,则会出现异常。
也许您可以为Directors
创建一个缓存。例如,如果您将所有Director保存在Redis中,则可以搜索与收到的Director
相对应的directorId
,然后执行关联。
当然,您仍然需要进行第二次操作,但这可能比查询数据库便宜。
答案 1 :(得分:0)
这将是丑陋的,但是 您的请求中包含personId,因此可以将您的Movie与长的personId映射起来
class Movie {
@Id Long id;
@Column String name;
@ManyToOne Person director;
@Column(name="PERSON_ID")
long personId;
}
在您的控制器中
movie.setPersonId(request.directorId);
答案 2 :(得分:0)
我认为您的MOVIE
表中没有'DIRECTOR_NAME
'列(假设您遵循第二条规范化规则)。它应该仅 DIRECTOR_ID
。
因此,您可以完全跳过将脚本名称加载到您的方案中的情况(前提是DirectId是随请求的查询参数一起发送的)。
由于(应该)在Movie.DIRECTOR_ID
和DIRECTOR.ID
之间有一个外键约束,因此如果您尝试插入任何{{ 1}}不存在。因此,您不必担心。