Handling relations in POST requests with Spring Data JPA
我有以下实体,每个实体都有一个CrudRepository:
我的控制器如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @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加载导演,然后创建新电影并最后保存它。这将导致两次旅行到数据库:第一次旅行是检索导演,第二次是保存电影。
在这种情况下,这不是什么大问题,但是可能存在一个具有很多关系的实体,这意味着可能要进行大量查询才能实现单个插入。
问题:我想将新电影保存在单数据库操作中。有没有办法避免最初的人查询?有没有更好的方法来处理这样的情况?
- 您是否熟悉findById和getOne的区别?由于您只需要引用导演,而不需要整个对象,因此请查看此问题stackoverflow.com/q/5482141/7634201
-
谢谢@ C.Weber,这几乎是我想要的。正如您提到的,在这种情况下,getOne方法比findById更好,因为它获取了一个引用而不是整个实体,但是它仍然要去数据库检查id是否存在,如果不存在,则抛出EntityNotFoundException没错我真的很想将其作为单个数据库操作来完成,这看起来可能吗?
-
您正在使用Hibernate作为JPA Provider?只要您不访问标识符以外的任何属性,AFAIK Hibernate都不会初始化代理。可以通过以下设置hibernate.jpa.compliance.proxy更改此行为(默认应为false)。如果设置为true,则在访问标识符时还将初始化代理。有关更多信息,请参见Hib用户指南。我的猜测是您正在访问代理,标志设置为true或我的信息很旧:)
-
@ESala MOVIE表中的列是什么?
-
@ C.Weber是的,我正在使用Hibernate,但不是直接使用Hibernate,而是在顶部使用spring-data-jpa。我只是用PersonRepository扩展JpaRepository并调用getOne(id)对其进行了测试,但它仍可获取实体。这是记录的查询:select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from person person0_ where person0_.id=?。
没有办法告诉它与您的新Movie相关的代码Person。因此,您确实需要执行查询并手动进行关联。
只有在端点创建Movie的同时创建Person的情况下,才有其他选择。然后,您可以简单地执行2个保存操作或使用CascadeType=ALL进行单个保存操作。
如果您能够更改请求参数,则可能是接收完整的Person对象而不是使用directorId的一个不错的选择。这样,您可以将关联设置为movie.director = director;。
请谨慎使用这种方法:如果收到的Person对象未存储在数据库中,则会出现异常。
也许您可以为Directors创建一个缓存。例如,如果您将所有Director都保存在Redis中,则可以搜索与收到的directorId相对应的Director,然后执行关联。
当然,您仍然需要进行第二次操作,但是它可能比查询数据库便宜得多。
- 谢谢您的回答。假设相关实体已经存在。如果我有一个有10个关系的实体,该怎么办?我需要进行10个查询以及保存吗?
-
可悲的是:是的。主要原因是因为与影片相关的Person取决于用户请求参数。我用一种替代方法更新了我的答案,该替代方法在您的特定情况下可能会很有趣,以提高性能。
-
感谢您提出使用缓存的建议,但随后,我仍在等待HTTP请求,以便进行简单的插入。在我的情况下,将完整的Person放入请求中也是不可接受的,因为相关实体可能很大并且也有关系。我真正想要的是在spring-data-jpa中处理此问题的方法。
-
好的@ESala,在答案中,我尝试包括所有想到的想法。对不起,我帮不上忙。
-
谢谢@andreybleme,我真的很感激!我暂时将其保持打开状态,以查看是否有人提出了另一种解决方案。
我不认为您的Movie表包含'DIRECTOR_NAME'列(假设您遵循规范化的第二条规则)。它只能是DIRECTOR_ID。
因此,您可以完全跳过将脚本名称加载到您的方案中的情况(前提是DirectId是随请求的查询参数一起发送的)。
由于您具有(应具有)Movie.DIRECTOR_ID和DIRECTOR.ID之间的外键约束,因此如果您尝试插入任何不存在的DIRECTOR_ID,它将照看任何约束冲突。因此,您不必担心。
-
是的,这就是我所拥有的,该导演被称为外键director_id。我正在寻找的是如何解决问题末尾描述的问题。
-
@ESala,您只需跳过该findById查询。是否需要将电影保存到数据库?是吗?
-
保存新电影时,您将如何设置导演字段?
-
@ESala请告诉我您为什么需要它?您的MOVIE表中没有DIRECTOR_NAME的列,对吗?因此,不必为模型设置名称。
这将是丑陋的,但是
您的请求中有personId,因此可以将您的Movie与您的long personId
映射在一起
1 2 3 4 5 6 7 8
| class Movie {
@Id Long id ;
@Column String name ;
@ManyToOne Person director ;
@Column (name ="PERSON_ID")
long personId ;
} |
在您的控制器中
1
| movie.setPersonId(request.directorId); |
-
您能详细说明一下吗?用personId映射电影是什么意思?
-
谢谢您的回答,但是Director字段将不会更新。另外,在进行查询以获取电影的详细信息时,每个关系都需要附加查询。
-
由于Director字段与表中的personId相同,因此也会更新。有关详细信息,您将使用@ManyToOne Person Director