So I have 3 entities, Employee
, Shop
and EmployeeShop
which is the link entity for Employee
and Shop
.
@Entity
@Table(name = "employee_shops")
class EmployeeShop(
@Id
@ManyToOne
var employee: Employee? = null,
@Id
@ManyToOne
var shop: Shop? = null
) : Serializable {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
other as EmployeeShop
return Objects.equals(employee, other.employee) &&
Objects.equals(shop, other.shop)
}
override fun hashCode(): Int {
return Objects.hash(employee, shop)
}
}
The Employee
@Entity
@Table(name = "employees")
class Employee(
@Id
var id: Int? = null,
var userName: String? = null,
var email: String? = null
@OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], orphanRemoval = true)
var shops: MutableList<EmployeeShop> = mutableListOf()
) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
other as Employee
return Objects.equals(userName, other.userName) &&
Objects.equals(email, other.email)
}
override fun hashCode(): Int {
return Objects.hash(userName, email)
}
}
And the Shop
@Entity
@Table(name = "shops")
class Shop(
@Id
var id: Int? = null,
@OneToMany(mappedBy = "shop", cascade = [CascadeType.ALL], orphanRemoval = true)
var users: MutableList<EmployeeShop> = mutableListOf(),
) {
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
other as Shop
return Objects.equals(id, other.id)
}
override fun hashCode(): Int {
return Objects.hash(id)
}
}
The EmployeeRepository
is something like this
interface EmployeeRepository: CrudRepository<Employee, String> {
@Query(value = "SELECT e FROM Employee e LEFT JOIN FETCH e.shops WHERE e.id = :id")
override fun findById(id: String): Optional<Employee>
}
The reason I override this is to add LEFT JOIN FETCH
in order to avoid LazyInitializationException
To return from the API, I transform the Employee
to EmployeeDto
fun getDto(entity: Employee): EmployeeDto {
return EmployeeDto(
userName = entity.userName,
email = entity.email
shops = entity.shops.map { IdAndNameDto(it.shop?.id?.toString().orEmpty(), it.shop?.name.orEmpty()) }
)
}
class IdAndNameDto(val id: String = "", val name: String = "")
class EmployeeDto(
val userName: String,
val email: String,
val shops: List<IdAndNameDto>?
)
When I use the repository to update the Employee
employeeRepo.save(employee)
The save
is from CrudRepository
(doc). In here the save
should return an instance of Employee
.
I get the following stacktrace
java.lang.StackOverflowError: null
at ch.qos.logback.classic.spi.TurboFilterList.getTurboFilterChainDecision(TurboFilterList.java:49) ~[logback-classic-1.2.3.jar:na]
at ch.qos.logback.classic.LoggerContext.getTurboFilterChainDecision_0_3OrMore(LoggerContext.java:269) ~[logback-classic-1.2.3.jar:na]
at ch.qos.logback.classic.Logger.callTurboFilters(Logger.java:751) ~[logback-classic-1.2.3.jar:na]
at ch.qos.logback.classic.Logger.isTraceEnabled(Logger.java:623) ~[logback-classic-1.2.3.jar:na]
at org.apache.logging.slf4j.SLF4JLogger.isEnabledFor(SLF4JLogger.java:213) ~[log4j-to-slf4j-2.12.1.jar:2.12.1]
at org.apache.logging.slf4j.SLF4JLogger.isEnabled(SLF4JLogger.java:121) ~[log4j-to-slf4j-2.12.1.jar:2.12.1]
at org.apache.logging.log4j.spi.AbstractLogger.isEnabled(AbstractLogger.java:1505) ~[log4j-api-2.12.1.jar:2.12.1]
at org.jboss.logging.Log4j2Logger.isEnabled(Log4j2Logger.java:46) ~[jboss-logging-3.4.1.Final.jar:3.4.1.Final]
at org.jboss.logging.Logger.isTraceEnabled(Logger.java:98) ~[jboss-logging-3.4.1.Final.jar:3.4.1.Final]
at org.jboss.logging.DelegatingBasicLogger.isTraceEnabled(DelegatingBasicLogger.java:54) ~[jboss-logging-3.4.1.Final.jar:3.4.1.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:502) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:208) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:332) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:108) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:74) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:113) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1176) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1041) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:687) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:240) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:457) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.ComponentType.resolve(ComponentType.java:695) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:878) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:732) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.processResultSet(Loader.java:1008) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQuery(Loader.java:964) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:324) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.loadEntity(Loader.java:2410) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:74) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:63) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:4396) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4386) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:569) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:537) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:208) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:332) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:108) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:74) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:113) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1176) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1041) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:687) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:240) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:457) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.ComponentType.resolve(ComponentType.java:695) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:878) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:732) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.processResultSet(Loader.java:1008) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQuery(Loader.java:964) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:324) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.loadEntity(Loader.java:2410) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:74) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:63) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:4396) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4386) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
...
The rest of the stack trace is just repeating of these few lines which you can also see from the above stacktrace
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:502) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:208) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:332) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:108) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:74) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:113) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.internal.SessionImpl.fireLoadNoChecks(SessionImpl.java:1176) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1041) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:687) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:240) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:457) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.type.ComponentType.resolve(ComponentType.java:695) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:878) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:732) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.processResultSet(Loader.java:1008) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQuery(Loader.java:964) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:324) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.Loader.loadEntity(Loader.java:2410) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:74) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:63) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.doLoad(AbstractEntityPersister.java:4396) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4386) ~[hibernate-core-5.4.12.Final.jar:5.4.12.Final]
But I don't have any error when I try to create an Employee with the same save
method.
Also, this only happens if the employee entity contains an non-empty shops
object. If the shop
is empty, which means the Employee
I got using findById
doesn't have any record in the EmployeeShop
link table, then the update also don't any error.
I'm still new to JPA, so my suspect is that the way I declared those Entities, like how I use annotations, is not correct. But I'm not sure where is the problem.
Does anyone know why I got the StackOverflow error?
I accidentally solved this by using the "trying-everything-I-can" approach.
EmployeeShop
entity class. In the Employee
class, I made this change.
// old
@OneToMany(mappedBy = "employee", cascade = [CascadeType.ALL], orphanRemoval = true)
var shops: MutableList<EmployeeShop> = mutableListOf()
// new
@ManyToMany
@JoinTable(
name = "employee_shops",
joinColumns = [JoinColumn(name = "employee_id")],
inverseJoinColumns = [JoinColumn(name = "shop_id")]
)
var shops: MutableSet<Shop> = mutableSetOf()
And in the Shop
class
// old
@OneToMany(mappedBy = "shop", cascade = [CascadeType.ALL], orphanRemoval = true)
var users: MutableList<EmployeeShop> = mutableListOf()
// new
@ManyToMany(mappedBy = "shops")
var users: MutableSet<Employee> = mutableSetOf()
Then the exception was gone and the app was back to work again. My Hibernate/JPA knowledge is not adequate to explain why this works. But if anyone has the same issue, this may be the solution for you.