如何解决 JPA 关联中的循环引用?
How to solve circular reference in JPA associations?
我知道这类问题已被多次回答并且有解决方案,但是,none 其中对我有用。我尝试了 @JsonIgnore
、@JsonIgnoreProperties
@JsonManagedReference/@JsonBackedReference
,但调试器仍然显示 user
引用了 authority
,后者引用了 user
,后者引用了 authority
,它引用了 user
...最重要的是 它不会抛出任何异常。但是,我还是想知道为什么会这样,为什么不抛异常,会不会影响生产力
我的实体很简单:有一个User
@Entity
@Table(name = "users_tb")
@NoArgsConstructor
@Getter
@Setter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
和Authority
@Entity
@Table(name = "authorities_tb")
@NoArgsConstructor
@Getter
@Setter
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
使用JpaRepository<T, V>
检索用户的代码
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userRepository.findUserByUsername(username).orElseThrow(
() -> new UsernameNotFoundException(ERR_USERNAME_NOT_FOUND));
return new CustomUserDetails(user);
}
loadUserByUsername
return之前的调试器输出状态:
user = {User@10781}
> id = {Long@10784}
> username = "John"
> password = "a$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag@10788} size = 2
> 0 = {Authority@10818}
> id = {Long@10784}
> name = "READ"
> user = {User@10784}
> id = {User@10784}
> username = "John"
> password = "a$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag@10788} size = 2
> 0 = {Authority@10818}
...
尝试而不使用 Lombok 注释@Getter 和@Setter。然后手动生成 getters 和 setters 并在 class 成员字段和 getter 上使用 @JsonIgnore,并且 [= setter.
上的 13=]@JsonProperty
@JsonIgnore
private List<Authority> authorities;
@JsonIgnore
// Getter for authorities
@JsonProperty
// Setter for authorities
您可以简单地用 @ToString.Exclude
注释重复的字段
你的情况:
@Data // this includes getter/setter and toString
@Entity
@Table(name = "users_tb")
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ToString.Exclude
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
@Data
@Entity
@Table(name = "authorities_tb")
@NoArgsConstructor
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ToString.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
更多信息:龙目岛@Data and @ToString.Exclude
对于 JPA,循环依赖本身不是问题。
它们有两个潜在的问题:
从软件设计的角度来看,循环依赖会创建一个 类 集群,您无法轻易将其分解。
如果确实需要,您可以通过使关系成为单向关系并通过查询替换另一个方向来轻松摆脱它们。
在你的情况下值得吗?
这取决于您的两个实体的真正相关程度。
我会尽量避免双向关系,因为很容易犯错误,比如没有保持关系双方的同步。
但在大多数情况下,我不会出汗。
大多数软件都是更严重的设计问题。
另一个问题发生在当某些东西试图在这个循环中导航直到它结束时,这显然是行不通的。典型的场景是:
- 将其渲染为 JSON(或 XML)。这就是
@JsonIgnore
& Co 通过不在 JSON. 中包含属性来解决的问题
equals
、hashCode
、toString
常被实现为调用所有被引用对象各自的方法。
正如 JSON 渲染这将导致堆栈溢出。
所以一定要打破这些方法中的循环。
JPA 本身没有循环问题,因为它会在一级缓存中查找实体。
假设您加载了一个 Authority
并且所有内容都已预先加载,JPA 将在检查引用的用户 ID 之前将其放入一级缓存中。如果它存在于缓存中,它将使用该实例。
如果不是,它将从数据库中加载它,将其放入缓存中,然后检查缓存中的权限 ID。它将使用找到的那些并加载其余的。
对于那些它会再次检查用户 ID,但那些是我们刚刚加载的用户,所以它肯定在缓存中。
于是JPA就搞定了,不会在一个循环里丢了。
它只会跳过带注释的
我知道这类问题已被多次回答并且有解决方案,但是,none 其中对我有用。我尝试了 @JsonIgnore
、@JsonIgnoreProperties
@JsonManagedReference/@JsonBackedReference
,但调试器仍然显示 user
引用了 authority
,后者引用了 user
,后者引用了 authority
,它引用了 user
...最重要的是 它不会抛出任何异常。但是,我还是想知道为什么会这样,为什么不抛异常,会不会影响生产力
我的实体很简单:有一个User
@Entity
@Table(name = "users_tb")
@NoArgsConstructor
@Getter
@Setter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
和Authority
@Entity
@Table(name = "authorities_tb")
@NoArgsConstructor
@Getter
@Setter
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
使用JpaRepository<T, V>
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userRepository.findUserByUsername(username).orElseThrow(
() -> new UsernameNotFoundException(ERR_USERNAME_NOT_FOUND));
return new CustomUserDetails(user);
}
loadUserByUsername
return之前的调试器输出状态:
user = {User@10781}
> id = {Long@10784}
> username = "John"
> password = "a$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag@10788} size = 2
> 0 = {Authority@10818}
> id = {Long@10784}
> name = "READ"
> user = {User@10784}
> id = {User@10784}
> username = "John"
> password = "a$xn3LI/AjqicFYZFruSwve.681477XaVNaUQbr1gioaWPn4t1KsnmG"
> authorities = {PersistentBag@10788} size = 2
> 0 = {Authority@10818}
...
尝试而不使用 Lombok 注释@Getter 和@Setter。然后手动生成 getters 和 setters 并在 class 成员字段和 getter 上使用 @JsonIgnore,并且 [= setter.
上的 13=]@JsonProperty@JsonIgnore
private List<Authority> authorities;
@JsonIgnore
// Getter for authorities
@JsonProperty
// Setter for authorities
您可以简单地用 @ToString.Exclude
你的情况:
@Data // this includes getter/setter and toString
@Entity
@Table(name = "users_tb")
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ToString.Exclude
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Authority> authorities;
}
@Data
@Entity
@Table(name = "authorities_tb")
@NoArgsConstructor
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ToString.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
更多信息:龙目岛@Data and @ToString.Exclude
对于 JPA,循环依赖本身不是问题。
它们有两个潜在的问题:
从软件设计的角度来看,循环依赖会创建一个 类 集群,您无法轻易将其分解。 如果确实需要,您可以通过使关系成为单向关系并通过查询替换另一个方向来轻松摆脱它们。 在你的情况下值得吗? 这取决于您的两个实体的真正相关程度。 我会尽量避免双向关系,因为很容易犯错误,比如没有保持关系双方的同步。 但在大多数情况下,我不会出汗。 大多数软件都是更严重的设计问题。
另一个问题发生在当某些东西试图在这个循环中导航直到它结束时,这显然是行不通的。典型的场景是:
- 将其渲染为 JSON(或 XML)。这就是
@JsonIgnore
& Co 通过不在 JSON. 中包含属性来解决的问题
equals
、hashCode
、toString
常被实现为调用所有被引用对象各自的方法。 正如 JSON 渲染这将导致堆栈溢出。 所以一定要打破这些方法中的循环。
JPA 本身没有循环问题,因为它会在一级缓存中查找实体。
假设您加载了一个 Authority
并且所有内容都已预先加载,JPA 将在检查引用的用户 ID 之前将其放入一级缓存中。如果它存在于缓存中,它将使用该实例。
如果不是,它将从数据库中加载它,将其放入缓存中,然后检查缓存中的权限 ID。它将使用找到的那些并加载其余的。
对于那些它会再次检查用户 ID,但那些是我们刚刚加载的用户,所以它肯定在缓存中。
于是JPA就搞定了,不会在一个循环里丢了。
它只会跳过带注释的