如何在Many to Many Relationship中的一个操作中保存多个实体[Spring Boot 2, JPA, Hibernate, PostgreSQL]。

问题描述 投票:0回答:1

我写在这里是想了解一些关于解决方案的信息,简而言之,我所面临的问题是:我有两个实体在双向的多对多关系中,例如我有以下的Post和Tag实体。

@Data
@Entity
@Table(name = "posts")
public class Post {


    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    /*...*/

    @ManyToMany( cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH} )
    @JoinTable(name = "post_tag", 
            joinColumns = @JoinColumn(name = "post_id", referencedColumnName = "id"), 
            inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id"))
    @JsonIgnoreProperties("posts")
    private Set<Tag> tags = new HashSet<>();

}

@Data
@Entity
@Table(name = "tags")
public class Tag {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;    

    @NaturalId
    private String text;

    @ManyToMany(mappedBy = "tags")//, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
    @JsonIgnoreProperties("tags")
    private Set<Post> posts = new HashSet<>();

}

我的问题是,在一个HTTP POST操作中,我得到了帖子的数据和与之相关的标签集合,我必须保存所有的数据,如果 "文本 "已经存在于数据库中,则不能重复标签实体的条件。假设我们有一个包含给定数据的地图,代码如下。

Post post = new Post();
String heading = (String) payload.get("heading");
String content = (String) payload.get("content");
post.setHeading(heading);
post.setContent(content);
Set<Tag> toSaveTags = new HashSet<Tag>();
List list = (List) payload.get("tags");
for (Object o : list) {
    Map map = (Map) o;
    String text = (String) map.get("text");
    Tag tag = new Tag();
    tag.setText(text);
    post.getTags().add(tag);
    tag.getPosts().add(post);
    log.info("post has {}# tag", post.getTags().size());
    toSaveTags.add(tag);
};
//method to save it all
postRepository.saveWithTags(post, toSaveTags);

我的解决方案是设计一个Repository类,方法如下图所示。

@Repository
public class PostTagsRepositoryImpl implements PostTagsRepository {

    @Autowired
    private EntityManagerFactory emf;

    @Override
    public Post saveWithTags(Post post, Collection<Tag> tags) {
        EntityManager entityManager = emf.createEntityManager();
        post.getTags().clear();
        for (Tag tag : tags) {

            tag.getPosts().clear();
            Tag searchedTag = null;
            try {
                searchedTag = entityManager.createQuery(
                        "select t "
                        + "from Tag t "
                        + "join fetch t.posts "
                        + "where t.text = :text", Tag.class)
                        .setParameter("text", tag.getText())
                        .getSingleResult();
            } catch (NoResultException e) {/* DO NOTHING */}
            if (searchedTag == null) {
                post.getTags().add(tag);
                tag.getPosts().add(post);
            } else {
                entityManager.getTransaction().begin();
                entityManager.detach(searchedTag);

                post.getTags().add(searchedTag);
                searchedTag.getPosts().add(post);
                entityManager.merge(searchedTag);
                entityManager.getTransaction().commit();
            }
        }

        entityManager.getTransaction().begin();
        entityManager.merge(post);
        entityManager.getTransaction().commit();
        return post;
    }

}

我的问题是: 我是否可以更好的实现它,也许可以用一个查询transaction来实现? 你能给我一些提示吗?

java spring jpa many-to-many hibernate-mapping
1个回答
1
投票

一些要点。

  • 你把两个实体连接起来,然后 clear 仓库中的关系。因为你在它们之间没有不变性,第一联结是没有用的。

  • 也许在单次查询transaction中?

单一查询是不可能的,但单一事务确实是你需要实现的,以避免不一致的问题。

  • 不幸的是,级联合并不能与naturalid一起工作,所以你必须自己产生这种行为。因此,对于每个标签,验证它是否存在。
Session session = entityManager.unwrap(Session.class);

Tag t= session.bySimpleNaturalId(Tag.class).load(“some txt”);

根据结果,你必须把它载入到 Post 对象中现有的标签之一(已在数据库中,并可通过以下方式恢复)。bySimpleNaturalId)或新的。然后逐级合并在 Post 会做剩下的事情。

  • 你总是在每次调用你的存储库时创建新的实体管理器,你应该通过直接注入共享的entitymanager来克服这个问题。你应该通过直接注入共享的entitymanager来克服这个问题。
@Autowired
Private EntityManager em;

它是线程安全的。

© www.soinside.com 2019 - 2024. All rights reserved.