如何在多对多关系 [Spring Boot 2、JPA、Hibernate、PostgreSQL] 中相关的一个操作中保存多个实体
How to save multiple entity in one action related in Many to Many Relationship [Spring Boot 2, JPA, Hibernate, PostgreSQL]
我在这里写信是为了对 make 的解决方案有所了解,简而言之,我遇到的问题是:我有两个双向多对多关系的实体,例如我有以下 Post 和标记实体:
@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 的数据和与之相关的标签集合,我必须保存所有条件以不重复如果 "text" 已存在于数据库中,则标记实体。假设我们有一个包含给定数据的地图,代码如下:
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 class,如下:
@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;
}
}
我的问题是:我可以更好地实施它吗?也许在一个 query/transaction 中?你能给我一些提示吗?
几点:
您 link 两个实体,然后 clear
存储库中的关系。因为你首先没有它们之间的不变量 linkage 是无用的。
maybe in a single query/transaction?
单一查询是不可能的,但单一事务确实是你需要实现的,以避免不一致问题。
- 不幸的是,级联合并不适用于 naturalid,因此您必须自己产生这种行为。
所以对于每个标签验证它是否存在:
Session session = entityManager.unwrap(Session.class);
Tag t= session.bySimpleNaturalId(Tag.class).load(“some txt”);
根据结果,您必须将现有标记之一(已经在数据库上并通过 bySimpleNaturalId
恢复)或新标记加载到 Post
对象中。然后 Post
上的级联合并将完成剩下的工作。
- 您总是在每次调用您的存储库时创建新的实体管理器。您应该通过直接注入共享实体管理器来克服这个问题。
@Autowired
Private EntityManager em;
它是线程安全的。
我在这里写信是为了对 make 的解决方案有所了解,简而言之,我遇到的问题是:我有两个双向多对多关系的实体,例如我有以下 Post 和标记实体:
@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 的数据和与之相关的标签集合,我必须保存所有条件以不重复如果 "text" 已存在于数据库中,则标记实体。假设我们有一个包含给定数据的地图,代码如下:
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 class,如下:
@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;
}
}
我的问题是:我可以更好地实施它吗?也许在一个 query/transaction 中?你能给我一些提示吗?
几点:
您 link 两个实体,然后
clear
存储库中的关系。因为你首先没有它们之间的不变量 linkage 是无用的。maybe in a single query/transaction?
单一查询是不可能的,但单一事务确实是你需要实现的,以避免不一致问题。
- 不幸的是,级联合并不适用于 naturalid,因此您必须自己产生这种行为。 所以对于每个标签验证它是否存在:
Session session = entityManager.unwrap(Session.class);
Tag t= session.bySimpleNaturalId(Tag.class).load(“some txt”);
根据结果,您必须将现有标记之一(已经在数据库上并通过 bySimpleNaturalId
恢复)或新标记加载到 Post
对象中。然后 Post
上的级联合并将完成剩下的工作。
- 您总是在每次调用您的存储库时创建新的实体管理器。您应该通过直接注入共享实体管理器来克服这个问题。
@Autowired
Private EntityManager em;
它是线程安全的。