Issue
Entities
I have three entities User
, Permission
and UserPermission
.
[User] --(OneToMany)--> [UserPermission]
[UserPermission] --(ManyToOne)--> [User]
[UserPermission] --(ManyToOne)--> [Permission]
These are the simplified implementations:
@Entity
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@NoArgsConstructor @RequiredArgsConstructor @AllArgsConstructor
public class User {
@Id
@NonNull @NotNull
private String id;
// ... Other non pk fields
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "user", referencedColumnName = "id")
private Collection<UserPermission> permissions;
}
@Entity @IdClass(UserPermission.PK.class)
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor
public class UserPermission {
@Id @NonNull @NotNull
@ManyToOne
@JoinColumn(name = "user", updatable = false, insertable = false)
private User user;
@Id @NonNull @NotNull
@ManyToOne
@JoinColumn(name = "permission", updatable = false, insertable = false)
private Permission permission;
// ... Other non pk fields
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
public static class PK implements Serializable {
private User user;
private Permission permission;
}
}
@Entity
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor
public class Permission {
@Id
@NonNull @NotNull
private String id;
// ... Other non pk fields
}
The problem
When I try to update an UserPermission
using the method org.springframework.data.repository.CrudRepository.save(S)
:
@Override
public T upd(ID id, T entity) {
entity.setPk(id);
return repository.save(entity);
}
I get the following exception:
java.lang.StackOverflowError: null
at java.lang.Character.isWhitespace(Character.java:6789) ~[na:1.8.0_151]
at java.lang.Character.isWhitespace(Character.java:6757) ~[na:1.8.0_151]
at org.postgresql.core.Parser.parseJdbcSql(Parser.java:82) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.core.CachedQueryCreateAction.create(CachedQueryCreateAction.java:63) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.core.CachedQueryCreateAction.create(CachedQueryCreateAction.java:17) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.util.LruCache.borrow(LruCache.java:115) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.core.QueryExecutorBase.borrowQuery(QueryExecutorBase.java:271) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.jdbc.PgConnection.borrowQuery(PgConnection.java:146) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.jdbc.PgPreparedStatement.<init>(PgPreparedStatement.java:78) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:1223) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:1681) ~[postgresql-42.2.5.jar:42.2.5]
at org.postgresql.jdbc.PgConnection.prepareStatement(PgConnection.java:371) ~[postgresql-42.2.5.jar:42.2.5]
at com.zaxxer.hikari.pool.ProxyConnection.prepareStatement(ProxyConnection.java:311) ~[HikariCP-3.2.0.jar:na]
at com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement(HikariProxyConnection.java) ~[HikariCP-3.2.0.jar:na]
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$5.doPrepare(StatementPreparerImpl.java:146) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:172) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:148) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1984) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1914) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1892) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQuery(Loader.java:937) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:340) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:310) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.loadEntity(Loader.java:2281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:64) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:54) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4269) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:481) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:222) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:124) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:92) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1257) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1140) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:682) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:239) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:457) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.ComponentType.resolve(ComponentType.java:688) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:862) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:718) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.processResultSet(Loader.java:990) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQuery(Loader.java:948) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:340) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:310) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.loadEntity(Loader.java:2281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:64) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:54) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4269) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:481) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:222) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:124) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:92) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1257) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1140) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
// Repeated over and over
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:682) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:239) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:457) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.ComponentType.resolve(ComponentType.java:688) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:862) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:718) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.processResultSet(Loader.java:990) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQuery(Loader.java:948) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:340) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:310) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.loadEntity(Loader.java:2281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:64) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:54) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4269) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:481) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:222) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:124) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:92) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1257) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1140) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
What I do know
I know for sure the problem lies in the "recursive" relation between User
and UserPermission
.
If I remove the mapping to User
in UserPermission
the exception goes away:
public class UserPermission {
@Id @NonNull @NotNull
private String user;
What I don't know
How can I map these two entities without getting this exception, it doesn't matter if the entities are not mapped perse I just want to enforce the relationship
Update 1
Adding the mappedBy attribute to the OneToMany annotation in the User entity and removing the JoinColumn annotation:
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private Collection<UserPermission> permissions;
Does not fix the problem but the stacktrace changed:
java.lang.StackOverflowError: null
at java.lang.ThreadLocal.get(ThreadLocal.java:161) ~[na:1.8.0_151]
at io.micrometer.core.instrument.binder.logging.MetricsTurboFilter.decide(LogbackMetrics.java:132) ~[micrometer-core-1.1.3.jar:1.1.3]
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.11.2.jar:2.11.2]
at org.apache.logging.slf4j.SLF4JLogger.isEnabled(SLF4JLogger.java:121) ~[log4j-to-slf4j-2.11.2.jar:2.11.2]
at org.apache.logging.log4j.spi.AbstractLogger.isEnabled(AbstractLogger.java:1504) ~[log4j-api-2.11.2.jar:2.11.2]
at org.jboss.logging.Log4j2Logger.isEnabled(Log4j2Logger.java:46) ~[jboss-logging-3.3.2.Final.jar:3.3.2.Final]
at org.jboss.logging.Logger.isTraceEnabled(Logger.java:98) ~[jboss-logging-3.3.2.Final.jar:3.3.2.Final]
at org.jboss.logging.DelegatingBasicLogger.isTraceEnabled(DelegatingBasicLogger.java:54) ~[jboss-logging-3.3.2.Final.jar:3.3.2.Final]
// Repeated over and over
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:4269) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:511) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:481) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:222) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:124) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:92) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1257) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1140) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:682) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:464) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.ManyToOneType.resolve(ManyToOneType.java:239) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:457) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.type.ComponentType.resolve(ComponentType.java:688) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.extractKeysFromResultSet(Loader.java:862) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:718) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.processResultSet(Loader.java:990) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQuery(Loader.java:948) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:340) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:310) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.Loader.loadEntity(Loader.java:2281) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:64) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:54) ~[hibernate-core-5.3.7.Final.jar:5.3.7.Final]
Solution
The solution was to remove @JoinColumn
from the parent entity (User) and use the mappedBy attribute with @OneToMany
as suggested by piyush-tyagi:
@Entity
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@NoArgsConstructor @RequiredArgsConstructor @AllArgsConstructor
public class User {
@Id
@NonNull @NotNull
private String id;
// ... Other non pk fields
@JsonIgnore
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private Collection<UserPermission> permissions;
}
Use @EmbeddedId
for the pk in the child entity (UserPermission):
@Entity
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
@NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor
public class UserPermission {
@EmbeddedId
private PK pk;
@Embeddable
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
public static class PK implements Serializable {
private String user;
private String permission;
}
}
And map the parent entities using @MapsId
@MapsId("user")
@NonNull @NotNull
@ManyToOne
@JoinColumn(name = "user", updatable = false, insertable = false)
private User user;
@MapsId("permission")
@NonNull @NotNull
@ManyToOne
@JoinColumn(name = "permission", updatable = false, insertable = false)
private Permission permission;
Finally, implement the getters/setters so the fields are in sync:
@JsonIgnore
public PK getPk() {
if (pk == null) {
pk = new PK();
}
return pk;
}
public void setPk(PK pk) {
this.pk = pk;
this.user = new User(pk.getUser());
this.permission = new Permission(pk.getPermission());
}
public User getUser() {
if (user == null) {
user = new User(getPk().getUser());
}
return user;
}
public void setUser(User user) {
this.user = user;
getPk().setUser(user.getId());
}
public Permission getPermission() {
if (permission == null) {
permission = new Permission(getPk().getPermission());
}
return permission;
}
public void setPermission(Permission permission) {
this.permission = permission;
getPk().setPermission(permission.getId());
}
Answered By - Fabian Mendez