I have 2 data classes in kotlin, each having a reference to eachother. Profile
and Kweet
(Code will be at the bottom). When fetching one of these entities with an EntityManager
it can succesfully fetch a single object. It will never return this however, as the JPA keeps fetching the recursive relationship in the background.
The issue occurs when either ProfileDao.getById
or ProfileDao.getByScreenname
is being called.
@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)
}
}
Update: Adding a DTO and marking this as open correctly solves the recursion error. Example:
@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
}
The @Open
annotation is a simple annotation class Open()
which is then processed by gradle to add open and noarg constructors
You should either use DTOs to represent data in the front end or @JsonIgnore
parent references from child objects (instead of @JsonBackReference
).
Using DTOs is probably a smarter choice as you can decouple your front end presentation from the backend model, which gives you flexibility in both layers (ie. changing one layer does not break/possibly introduce bugs in the other).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.