【问题标题】:JPA infinitely recursion with bidirectional relationships in Kotlin and Glassfish在 Kotlin 和 Glassfish 中具有双向关系的 JPA 无限递归
【发布时间】:2018-03-04 13:47:14
【问题描述】:

我在 kotlin 中有 2 个数据类,每个类都有相互引用。 ProfileKweet(代码将在底部)。当使用EntityManager 获取其中一个实体时,它可以成功获取单个对象。但是它永远不会返回这个,因为 JPA 一直在后台获取递归关系。

在调用ProfileDao.getByIdProfileDao.getByScreenname 时会出现此问题。

Profile.kt

@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()
}

Kweet.kt

@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()
)

ProfileDao.kt

@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 构造函数

【问题讨论】:

  • 异常堆栈跟踪是...?
  • 这很有趣,没有。它被 Glassfish 吞下,错误页面只给出错误 500
  • 我的猜测是 JPA 与问题无关。 JSON序列化是问题所在。我建议不要使用实体(持久性模型)作为 REST 控制器(通信层)的返回类型。它们包含循环,因此 JSON 序列化通过实体的循环图递归导航。每次修改实体时,您也会更改 API,并可能会暴露不应该的内容。
  • 好的,我明天先试试!
  • 嘿,我为配置文件添加了一个 DTO,它只显示屏幕名称并保持实际配置文件的私密性。尽管如此,Glassfish 和 JPA 似乎仍在自行递归。

标签: jpa kotlin glassfish eclipselink


【解决方案1】:

您应该使用 DTO 来表示前端中的数据,或者使用 @JsonIgnore 来自子对象的父引用(而不是 @JsonBackReference)。

使用 DTO 可能是一个更明智的选择,因为您可以将前端演示与后端模型分离,从而为您提供两层的灵活性(即,更改一层不会破坏/可能会在另一层中引入错误)。

【讨论】:

  • 添加到此:DTO 需要标记为 open 并且需要一个无参数构造函数。请参阅 OP 中的编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-23
  • 2020-07-25
  • 2019-09-12
相关资源
最近更新 更多