JPA Spring Boot 微服务 - 使用两个多对一映射持久化实体时的无限循环
Posted
技术标签:
【中文标题】JPA Spring Boot 微服务 - 使用两个多对一映射持久化实体时的无限循环【英文标题】:JPA Spring Boot micro service - Infinite loop when persisting an Entity with two ManyToOne mappings 【发布时间】:2019-10-24 18:07:34 【问题描述】:遵循这篇文章中给出的优秀建议...
How to model a three-way relationship in a JPA Spring Boot micro service with a mysql back end
..我在 Spring-boot Java 应用程序中编写了三个实体类(如下所示)来表示我所在组织的技能库,其中每个用户可能有很多技能。 'Level' 枚举属性用于表示用户对每个特定技能的能力。
我添加了一些控制器映射,启动了我的应用程序并进行了一些基本的 API 测试,其中我能够成功地为用户添加技能并指定附加级别属性。
这里是实体类:
@Entity
@Table(name = "users")
public class User
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name = "id", updatable = false, nullable = false)
private Integer id;
private String name;
private String email;
@OneToMany(mappedBy = "user",
cascade = CascadeType.MERGE,
orphanRemoval = true
)
private Set<UserSkill> skills = new HashSet<>();
(getters and setters)
@Entity
@Table(name = "skills")
public class Skill
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name = "id", updatable = false, nullable = false)
private Integer id;
private String name;
@OneToMany(mappedBy = "skill",
cascade = CascadeType.MERGE,
orphanRemoval = true
)
private Set<UserSkill> users = new HashSet<>();
(getters and setters)
@Entity
@Table(name = "users_skills")
public class UserSkill
@EmbeddedId
private UserSkillId id;
@ManyToOne
@JoinColumn(name = "fk_user", insertable = false, updatable = false)
private User user;
@ManyToOne
@JoinColumn(name = "fk_skill", insertable = false, updatable = false)
private Skill skill;
@Column
@Enumerated(EnumType.STRING)
private Level level;
public UserSkill()
public UserSkill(User u, Skill s, Level l)
// create primary key
this.id = new UserSkillId(u.getId(), s.getId());
// initialize attributes
this.user = u;
this.skill = s;
this.level = l;
// update relationships to assure referential integrity
u.getSkills().add(this);
s.getUsers().add(this);
我正在使用代表三个实体的存储库类为用户分配技能:
User user = userRepository.findById(userid).get();
Skill skill= skillRepository.findById(skillid).get();
Level level = Level.valueOf(levelid);
UserSkill userSkill = new UserSkill(user, skill, level);
userSkillRepository.save(userSkill);
In my Controller, I have a mapping to retrieve the user and associated skills and add this to my Model:
@GetMapping("/user/userid/skills/get")
public String getUserSkills(@PathVariable("userid") Integer userid, Model model)
User user = userRepository.findById(userid).get();
model.addAttribute("user", user);
Set<UserSkill> userSkills = userSkillRepository.findAllByUser(user);
model.addAttribute("userskills", userSkills);
return "update-user";
在我看来(html 和 ThymeLeaf),我正在尝试显示此信息。 当用户没有技能时,我可以成功地将用户显示在我的视图中。但是当用户有技能时,我尝试检索技能,就像这样......
<tr th:each="userskill : $userskills">
<td th:text="$userskill.skill.name"></td>
...我收到以下错误:
org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'skill' cannot be found on object of type 'uk.gov.hmrc.skills.model.UserSkill' - maybe not public or not valid?
我认为这是因为我的 UserSkill 实体类中没有 getter 和 setter,所以我添加了这些。但这让我后退了一步。现在,当我尝试向这样的用户添加技能时......
userSkillRepository.save(userSkill);
...我进入了一个无限循环。
在原始输出中,我看到这似乎无限重复:
"id":1,"name":"Dave","email":"dave@test.com","skills":["user":
"id":1,"name":"Dave","email":"dave@test.com","skills":["user":
"id":1,"name":"Dave","email":"dave@test.com","skills":["user":
...
当 getter 和 setter 不在我的 UserSkill 实体类中时,不会发生这种情况。
作为 JPA 的新手,我在这一点上感到困惑和迷茫,非常感谢一些帮助和指导!
【问题讨论】:
我不确定 JSON 是如何出现的,你能详细说明一下 json 位吗?您的 UI 是 thymeleaf,因此该流程中不需要 JSON。 感谢您的评论 Himanshu - Mounir 在下面提供了解决方案。 Json 用作我的 Controller 方法的请求和响应主体的内容。 JSON.stringify, avoid TypeError: Converting circular structure to JSON的可能重复 【参考方案1】:在使用 spring json rest 时,这个问题被称为循环引用。
要修复它,请在 UserSkill 类的 user 字段中添加 @JsonIgnoreProperties("skills") 注释(或 @JsonIgnore 以在返回 json 结果时忽略该字段)。
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity
@Table(name = "users_skills")
public class UserSkill
@EmbeddedId
private UserSkillId id;
@ManyToOne
@JoinColumn(name = "fk_user", insertable = false, updatable = false)
@JsonIgnoreProperties("skills")
private User user;
@ManyToOne
@JoinColumn(name = "fk_skill", insertable = false, updatable = false)
@JsonIgnoreProperties("users")
private Skill skill;
@Column
@Enumerated(EnumType.STRING)
private Level level;
public UserSkill()
...
https://hellokoding.com/handling-circular-reference-of-jpa-hibernate-bidirectional-entity-relationships-with-jackson-jsonignoreproperties/
使用 Jackons JSON 视图的第二个解决方案
https://www.baeldung.com/jackson-json-view-annotation
使用 DTO 模式的第三种解决方案
https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
【讨论】:
OP提到的错误是org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'skill' cannot be found on object of type 'uk.gov.hmrc.skills.model.UserSkill' - maybe not public or not valid?
出色的回复 Mounir - 这为我解决了问题!我很感激,因为我完全迷路了! :) 我会将您的答案标记为已接受的答案,但我可以请您先做一个小编辑吗?我们还需要 dd 技能属性的 @JsonIgnoreProperties 注释 - 你可以编辑你的代码 sn-p 来反映这一点吗?我会在接受之前等你做,因为我怀疑它之后是否可以编辑。再次感谢。
您可以从 Skill 类中删除 users 字段,因为他有任何影响。如果您想保留用户字段,我将 @JsonIgnoreProperties("users") 添加到 UserSkill 的技能字段中。【参考方案2】:
@JsonBackReference
annotations 正在解决这个无限的 json 循环问题
【讨论】:
以上是关于JPA Spring Boot 微服务 - 使用两个多对一映射持久化实体时的无限循环的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Spring Security 实现具有两级令牌认证的 Spring Boot 微服务?
如何为 Spring Boot JPA 时间戳指定 UTC 时区
Spring Boot - JPA 或 SQL 查询在连接表中查找最常见的项目?
Spring Boot JPA 元模型不能为空!尝试运行 JUnit / 集成测试时