【问题标题】:Handling relations in POST requests with Spring Data JPA使用 Spring Data JPA 处理 POST 请求中的关系
【发布时间】:2019-03-02 08:11:36
【问题描述】:

我有以下实体,每个实体都有一个 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 加载导演,然后创建新电影并最终保存它。这会导致两次访问数据库:第一次检索导演,第二次保存电影。

在这种情况下,这不是一个大问题,但可能存在具有大量关系的实体,这意味着可能会执行大量查询来实现单个插入。

问题:我想将新电影保存在单个数据库操作中。有没有办法避免最初的人员查询?有没有更好的方法来处理这种情况?

【问题讨论】:

  • 你熟悉 findById 和 getOne 的区别吗?由于您只需要对导演的引用而不是整个对象,请看一下这个问题stackoverflow.com/q/5482141/7634201
  • 感谢@C.Weber,这几乎就是我想要的。正如您提到的 getOne 方法在这种情况下比 findById 更好,因为它获取引用而不是整个实体,但它仍然会去数据库检查 id 是否存在,如果不存在则抛出 EntityNotFoundException不。我真的很想把它作为一个单一的数据库操作来做,这看起来可能吗?
  • 您正在使用 Hibernate 作为 JPA 提供程序?只要您不访问标识符以外的任何属性,AFAIK Hibernate 就不会初始化代理。可以通过以下设置 hibernate.jpa.compliance.proxy 更改此行为(默认应为 false)。如果设置为 true,它还将在访问标识符时初始化代理。有关更多信息,请参阅Hib User Guide。我的猜测是你正在访问代理,标志设置为 true 或者我的信息是旧的 :)
  • @ESala MOVIE 表中有哪些列??
  • @C.Weber 是的,我正在使用 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=?

标签: java spring spring-mvc spring-data-jpa spring-data


【解决方案1】:

我认为您的 MOVIE 表不包含“DIRECTOR_NAME”列(假设您遵循规范化的第二条规则)。它应该是only DIRECTOR_ID

因此,您可以完全跳过在您的场景中加载导演名称(假设 directId 与请求的查询参数一起发送)。

由于您在 Movie.DIRECTOR_IDDIRECTOR.ID 之间有(应该有)外键约束,因此如果您尝试插入任何不符合条件的 DIRECTOR_ID,它将处理任何违反约束的情况不存在。所以你不必担心。

【讨论】:

  • 是的,这就是我所拥有的,director 被引用为外键director_id。我正在寻找的是如何解决问题末尾描述的问题。
  • @ESala 您可以简单地跳过该 findById 查询。不需要将电影保存到数据库吗?是吗?
  • 在保存新电影时,您还会如何设置导演字段?
  • @ESala 请告诉我你为什么需要它?您的 MOVIE 表没有 DIRECTOR_NAME 的列,对吗?所以没有必要为模型设置名称。
【解决方案2】:

会很丑但是 你的请求中有 personId,所以你可以用你的长 personId 映射你的电影

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

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

在你的控制器中

movie.setPersonId(request.directorId);

【讨论】:

  • 您能详细说明一下吗?用personId 映射电影是什么意思?
  • 感谢您的回答,但导演字段将不会更新。此外,在执行查询以获取电影的详细信息时,每个关系都需要额外的查询。
  • 由于director字段将与您表中的personId相同,因此它也会更新。有关详细信息,您将使用@ManyToOne Person 导演
【解决方案3】:

没有办法告诉Person 与您的新Movie 相关的代码。因此,您确实需要执行查询并手动进行关联。

只有当您的端点在创建Person 的同时创建Movie 时,才有可能使用另一种方法。然后您可以简单地执行 2 个保存操作或使用 CascadeType=ALL 进行单个保存操作。


如果您能够更改请求参数,接收完整的Person 对象而不是接受directorId 可能是一个不错的选择。这样你就可以建立关联movie.director = director;

小心这种方法:如果接收到的Person 对象未存储在您的数据库中,您将收到异常。


也许您可以为您的Directors 创建一个缓存。例如,如果您将所有 Director 都保存在 Redis 中,则可以搜索与收到的 directorId 对应的 Director 并执行关联。

当然,您仍然需要进行第二次操作,但它可能比查询数据库便宜得多。

【讨论】:

  • 感谢您的回答。假设相关实体已经存在。如果我有一个具有 10 个关系的实体会发生什么?我需要进行 10 次查询以及保存吗?
  • 遗憾的是:是的。主要原因是因为与您的电影相关的Person 取决于用户请求参数。我更新了我的答案,在您的特定情况下可能会感兴趣以提高性能。
  • 感谢使用缓存的建议,但我仍在等待 HTTP 请求,以获取应该是简单插入的内容。在我的情况下,将完整的Person 放入请求中也是不可接受的,因为相关实体可能很大并且也有关系。我真正在寻找的是一种在spring-data-jpa 内处理此问题的方法。
  • 好的@ESala,在答案中我试图包含我想到的所有想法。抱歉,我帮不上忙。
  • 谢谢@andreybleme,我真的很感激!我暂时将其保持打开状态,看看其他人是否提出了其他解决方案。
猜你喜欢
  • 2021-01-08
  • 2019-01-29
  • 1970-01-01
  • 2017-11-09
  • 1970-01-01
  • 2018-03-13
  • 2013-08-26
  • 2018-04-14
  • 2018-11-06
相关资源
最近更新 更多