【问题标题】:Why do I need @Transactional for saving an OneToOne mapped Entity为什么我需要 @Transactional 来保存 OneToOne 映射的实体
【发布时间】:2020-08-17 16:17:43
【问题描述】:

我有一个简单直接的演示应用程序,其中包含 spring-boot、spring-data-jpa 和 h2-DB。

我已经构建了两个由OneToOne 关系映射的实体。

Post.java

@Entity
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @OneToOne(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private PostDetail postDetail;
}

PostDetail.java

@Entity
public class PostDetail {

    @Id
    @GeneratedValue
    private Long id;

    private String message;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    @JoinColumn(name = "id")
    private Post post;
}

我尝试创建并保存一个新的Post。然后我尝试新建一个PostDetail,将之前生成的Post设置为它并保存。在一个控制器示例中,我没有 @Transactional 注释,而在第二个示例中,我使用 @Transactional 注释方法

@RestController
public class TestController {
    
    @Autowired
    PostRepository postRepository;

    @Autowired
    PostDetailRepository postDetailRepository;

    @GetMapping("/test1")
    public String test1() {
        Post post = new Post();
        post.setId(2L);
        post.setTitle("Post 1");
        postRepository.save(post);
        
        PostDetail detail = new PostDetail();
        detail.setMessage("Detail 1");
        detail.setPost(post);
        
        postDetailRepository.save(detail);
        
        return "";
    }
    
    @Transactional
    @GetMapping("/test2")
    public String test2() {
        Post post = new Post();
        post.setId(2L);
        post.setTitle("Post 1");
        postRepository.save(post);
        
        PostDetail detail = new PostDetail();
        detail.setMessage("Detail 1");
        detail.setPost(post);
        
        postDetailRepository.save(detail);
        
        return "";
    }
}

为什么我在第一个示例中得到 org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.demo.jpa.model.Post 异常而在另一个示例中没有?

谁能解释为什么会这样?

【问题讨论】:

    标签: spring spring-boot hibernate jpa spring-data-jpa


    【解决方案1】:

    您使用双向 @OneToOne 关联。作为休眠文档states

    每当形成双向关联时,应用程序开发人员必须确保双方始终保持同步。

    所以,你应该这样重写你的测试方法:

    @GetMapping("/test1")
    public String test1() {
       Post post = new Post();
       post.setId(2L);
       post.setTitle("Post 1");
       
       PostDetail detail = new PostDetail();
       detail.setMessage("Detail 1");
       
       // synchronization of both sides of @OneToOne association
       detail.setPost(post);
       post.setDetail(detail);
       
    
       // thanks to CascadeType.ALL on Post.postDetail
       // postDetail will be saved too
       postRepository.save(post);
    
       return "";
    }
    

    【讨论】:

    • 我遵循了this 示例,并且可以单独保存帖子。有什么解决办法吗?
    • 本文建议不要使用双向@OneToOne:This way, you don’t even need a bidirectional association since you can always fetch the PostDetails entity by using the Post entity identifier.单向使用@OneToOne就不会遇到这个问题
    • 顺便说一句,documentation 也建议这样做:Because this can lead to N+1 query issues, it’s much more efficient to use unidirectional @OneToOne associations with the @MapsId annotation in place.
    • 好的,明白了。但在文档中也提到了this。有什么建议吗?
    • 我很困惑,您需要什么建议?
    【解决方案2】:

    您不应该分别保存这两个实体——您应该在 post 对象中设置 PostDetail 并只保存 Post 对象。 Hibernate 将负责保存聚合的 PostDetail。

    这就是为什么您会收到 PersistentObjectException 的原因,您可以通过将其保留在同一个事务中来解决此问题。

    【讨论】:

    • 如果我想单独保存有什么办法吗?
    • 您需要在每次之前手动创建 HibernateSessions 并手动关闭它们,我不推荐它,但您可以使用 SessionFactory baeldung.com/hibernate-5-spring
    【解决方案3】:
    we do not always need a bidirectional mapping when we are mapping two entities
    
    you can simple have a unidirection most of the time
    
          Post post = new Post();
            post.setId(2L);
            post.setTitle("Post 1");
           
            PostDetail detail = new PostDetail();
            detail.setMessage("Detail 1");
            detail.setPost(post);
            postRepository.save(post);  
    

    因为你有cascade.all,所以hibernate先保存Post,然后保存PostDetail,现在按照Transaction行为的规则,要么完全完成,要么没有完成,所以我们不可能有Post被保存的情况但是 PostDetail 没有,因此为了避免这种歧义,在方法级别或根据您的要求可能是类级别的 @Transaction 注释很重要

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-20
      • 2020-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多