JPA 无限递归与 Kotlin 和 Glassfish 中的双向关系
JPA infinitely recursion with bidirectional relationships in Kotlin and Glassfish
我在 kotlin 中有 2 个数据 类,每个数据都相互引用。 Profile
和 Kweet
(代码在底部)。当使用 EntityManager
获取其中一个实体时,它可以成功获取单个对象。然而,它永远不会 return 这个,因为 JPA 一直在后台获取递归关系。
调用 ProfileDao.getById
或 ProfileDao.getByScreenname
时会出现此问题。
@Entity(name = "profile")
@NamedQueries(
(NamedQuery(name = "Profile.getByScreenName", query = "select p from profile p where p.screenname LIKE :screenname")),
(NamedQuery(name = "Profile.getAll", query = "select p from profile p"))
)
data class Profile(
@Id
@GeneratedValue
var id: Int? = null,
var screenname: String,
var created: Timestamp
) {
@OneToMany(mappedBy = "profile", fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
var kweets: List<Kweet> = emptyList()
@ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
@JoinTable(
name = "liked_kweets",
joinColumns = [(JoinColumn(name = "profile_id", referencedColumnName = "id"))],
inverseJoinColumns = [(JoinColumn(name = "kweet_id", referencedColumnName = "id"))]
)
var likes: List<Kweet> = emptyList()
@ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
@JoinTable(
name = "follows",
joinColumns = [(JoinColumn(name = "follower_id", referencedColumnName = "id"))],
inverseJoinColumns = [(JoinColumn(name = "followed_id", referencedColumnName = "id"))]
)
var follows: List<Profile> = emptyList()
@ManyToMany(mappedBy = "follows", fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
var followers: List<Profile> = emptyList()
}
@Entity(name = "kweet")
@NamedQuery(name = "Kweet.getAll", query = "select k from kweet k")
data class Kweet(
@Id
@GeneratedValue()
var Id: Int? = null,
var created: Timestamp,
var message: String,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id")
@JsonBackReference
var profile: Profile,
@ManyToMany(mappedBy = "likes", fetch = FetchType.LAZY)
@JsonBackReference
var likedBy: List<Profile> = emptyList()
)
@Stateless
class ProfileDao {
@PersistenceContext
lateinit var em: EntityManager
fun getById(id: Int) = em.find(Profile::class.java, id)
fun getAll(): List<Profile> = em.createNamedQuery("Profile.getAll", Profile::class.java).resultList
fun getByScreenname(name: String) = em.createNamedQuery("Profile.getByScreenName", Profile::class.java)
.setParameter("screenname", name)
.resultList
.firstOrNull()
fun create(profile: Profile) = em.persist(profile)
fun follow(follower: Profile, leader: Profile) {
follower.follows += leader
leader.followers += follower
em.persist(follower)
em.persist(leader)
}
}
更新:添加 DTO 并将其标记为打开正确解决了递归错误。示例:
@Open
class ProfileFacade(
private val profile: Profile
) : Serializable {
var screenname: String
get() = profile.screenname
set(value) {
profile.screenname = value
}
var kweets: List<SimpleKweetFacade>
get() = profile.kweets.map { SimpleKweetFacade(it) }
set(value) = Unit
var follows: List<String>
get() = profile.follows.map { it.screenname }
set(value) = Unit
var created: Timestamp
get() = profile.created
set(value) = Unit
}
@Open
注释是一个简单的 annotation class Open()
,然后由 gradle 处理以添加 open 和 noarg 构造函数
您应该使用 DTO 在前端表示数据或 @JsonIgnore
来自子对象的父引用(而不是 @JsonBackReference
)。
使用 DTO 可能是更明智的选择,因为您可以将前端表示与后端模型分离,这为您在两层提供了灵活性(即更改一层不会 break/possibly 在另一层引入错误).
我在 kotlin 中有 2 个数据 类,每个数据都相互引用。 Profile
和 Kweet
(代码在底部)。当使用 EntityManager
获取其中一个实体时,它可以成功获取单个对象。然而,它永远不会 return 这个,因为 JPA 一直在后台获取递归关系。
调用 ProfileDao.getById
或 ProfileDao.getByScreenname
时会出现此问题。
@Entity(name = "profile")
@NamedQueries(
(NamedQuery(name = "Profile.getByScreenName", query = "select p from profile p where p.screenname LIKE :screenname")),
(NamedQuery(name = "Profile.getAll", query = "select p from profile p"))
)
data class Profile(
@Id
@GeneratedValue
var id: Int? = null,
var screenname: String,
var created: Timestamp
) {
@OneToMany(mappedBy = "profile", fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
var kweets: List<Kweet> = emptyList()
@ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
@JoinTable(
name = "liked_kweets",
joinColumns = [(JoinColumn(name = "profile_id", referencedColumnName = "id"))],
inverseJoinColumns = [(JoinColumn(name = "kweet_id", referencedColumnName = "id"))]
)
var likes: List<Kweet> = emptyList()
@ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
@JoinTable(
name = "follows",
joinColumns = [(JoinColumn(name = "follower_id", referencedColumnName = "id"))],
inverseJoinColumns = [(JoinColumn(name = "followed_id", referencedColumnName = "id"))]
)
var follows: List<Profile> = emptyList()
@ManyToMany(mappedBy = "follows", fetch = FetchType.LAZY, cascade = [CascadeType.DETACH])
var followers: List<Profile> = emptyList()
}
@Entity(name = "kweet")
@NamedQuery(name = "Kweet.getAll", query = "select k from kweet k")
data class Kweet(
@Id
@GeneratedValue()
var Id: Int? = null,
var created: Timestamp,
var message: String,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id")
@JsonBackReference
var profile: Profile,
@ManyToMany(mappedBy = "likes", fetch = FetchType.LAZY)
@JsonBackReference
var likedBy: List<Profile> = emptyList()
)
@Stateless
class ProfileDao {
@PersistenceContext
lateinit var em: EntityManager
fun getById(id: Int) = em.find(Profile::class.java, id)
fun getAll(): List<Profile> = em.createNamedQuery("Profile.getAll", Profile::class.java).resultList
fun getByScreenname(name: String) = em.createNamedQuery("Profile.getByScreenName", Profile::class.java)
.setParameter("screenname", name)
.resultList
.firstOrNull()
fun create(profile: Profile) = em.persist(profile)
fun follow(follower: Profile, leader: Profile) {
follower.follows += leader
leader.followers += follower
em.persist(follower)
em.persist(leader)
}
}
更新:添加 DTO 并将其标记为打开正确解决了递归错误。示例:
@Open
class ProfileFacade(
private val profile: Profile
) : Serializable {
var screenname: String
get() = profile.screenname
set(value) {
profile.screenname = value
}
var kweets: List<SimpleKweetFacade>
get() = profile.kweets.map { SimpleKweetFacade(it) }
set(value) = Unit
var follows: List<String>
get() = profile.follows.map { it.screenname }
set(value) = Unit
var created: Timestamp
get() = profile.created
set(value) = Unit
}
@Open
注释是一个简单的 annotation class Open()
,然后由 gradle 处理以添加 open 和 noarg 构造函数
您应该使用 DTO 在前端表示数据或 @JsonIgnore
来自子对象的父引用(而不是 @JsonBackReference
)。
使用 DTO 可能是更明智的选择,因为您可以将前端表示与后端模型分离,这为您在两层提供了灵活性(即更改一层不会 break/possibly 在另一层引入错误).