代码之家  ›  专栏  ›  技术社区  ›  requinard

Kotlin和Glassfish中具有双向关系的JPA无限递归

  •  0
  • requinard  · 技术社区  · 7 年前

    我在kotlin中有2个数据类,每个类都有一个相互引用。 Profile Kweet (代码将位于底部)。获取其中一个实体时 EntityManager 它可以成功地获取单个对象。然而,它永远不会返回这个结果,因为JPA会在后台不断获取递归关系。

    问题发生在 ProfileDao.getById ProfileDao.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构造函数

    1 回复  |  直到 7 年前
        1
  •  1
  •   Strelok    7 年前

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

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