Issue
here's one more of these as I've read through the rest and tried to implement the fixes, but to no avail. My problem is that my User object is being saved correctly (I can see them pop into the database), but my UserPreferences object throws the error. This is really strange, as I'm doing everything explicitly right now, and know, for sure, that at the time of the "save" call to the repository, no fields in the Preferences object is null. Least of all the "user" and "user_id" (PKEY).
Here's the class for UserPreferences:
package gbw.TheScheduler.models;
@Entity
@Table(name="user_preferences")
@JsonSerialize
public class UserPreferences implements Serializable {
static final long serialVersionUID = 828325321321L;
public static UserPreferences getDefault(User user){
return new UserPreferences(user,60);
}
public UserPreferences(){}
public UserPreferences(User user, int hours){
this.user = user;
this.userId = user.getId();
this.hours = hours;
this.timeslots = new HashSet<>();
}
@Id
@Column(name = "user_id")
private int userId;
private int hours;
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy="preferences", fetch = FetchType.LAZY)
private Set<PreferencesTimeSlot> timeslots = new HashSet<>();
@JsonProperty
public int getHours(){
return hours;
}
@JsonProperty
public Set<PreferencesTimeSlot> getTimeslots(){
return timeslots;
}
public void setUser(User user){
this.user = user;
this.userId = user.getId();
}
public void setHours(int i){
this.hours = i;
}
public void setTimeslots(Set<PreferencesTimeSlot> list){
this.timeslots = list;
}
@JsonProperty
public int getId() {
return user.getId();
}
}
Here's my "User" class:
package gbw.TheScheduler.models;
import static gbw.TheScheduler.util.ArrayUtil.*;
@Entity
@Table(name = "users")
@JsonSerialize
public class User implements Serializable {
static final long serialVersionUID = 321321321321L;
@Id
private int id;
@JsonProperty
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
private UserPreferences userPreferences;
@JsonProperty
@ManyToMany(fetch = FetchType.LAZY)
private Set<MonthlySchedule> schedules = new HashSet<>();
@JsonProperty("first_name")
@Column(name = "first_name")
private String firstName;
@JsonProperty("last_name")
@Column(name = "last_name")
private String lastName;
@JsonProperty("initials")
@Column(name = "initials")
private String initials;
@JsonProperty("email")
@Column(name = "email")
private String email;
@JsonProperty("password")
@Column(name = "password")
private String password;
@JsonProperty("admin")
@Column(name = "admin")
private boolean admin;
public User(@JsonProperty("initials") String initials, @JsonProperty("password") String password,
@JsonProperty("admin") boolean state, @JsonProperty("email") String email, @JsonProperty("first_name") String firstName,
@JsonProperty("last_name") String lastName){
this.initials = initials;
this.password = password;
this.admin = state;
this.email = email;
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString(){
return initials + " " + password;
}
@JsonProperty
public Set<MonthlySchedule> getSchedules(){
return schedules;
}
@JsonProperty
public void setSchedules(Set<MonthlySchedule> schedules){
this.schedules = schedules;
}
@JsonProperty
public UserPreferences getPreferences(){
return userPreferences;
}
@JsonProperty
public void setPreferences(UserPreferences pref){
this.userPreferences = pref;
}
@JsonProperty
public int getId() {
return id;
}
@JsonProperty
public void setAdmin(boolean state){
admin = state;
}
@JsonProperty
public boolean isAdmin() {
return admin;
}
public void setId(int id) {
this.id = id;
}
@JsonProperty
public String getFirstName() {
return firstName;
}
@JsonProperty
public void setFirstName(String name) {
this.firstName = name;
}
@JsonProperty
public String getInitials() {
return initials;
}
@JsonProperty
public String getLastName(){
return lastName;
}
@JsonProperty
public void setInitials(String initials) {
this.initials = initials;
}
@JsonProperty
public String getEmail() {
return email;
}
@JsonProperty
public String getPassword() {
return password;
}
@JsonProperty
public void setEmail(String email) {
this.email = email;
}
public User(){}
@JsonProperty
public void setPassword(String password) {
this.password = password;
}
@JsonProperty
public void setLastName(String value) {
this.lastName = value;
}
}
Here's my UserController, where the issue occurs in line 68 according to the stacktrace:
@RequestMapping(path=path+"/add/{username}/{password}", method = RequestMethod.POST) // Map ONLY POST Requests
public @ResponseBody ResponseEntity<User> addNewUser (@RequestBody String requestText, @RequestHeader(required = false) String headerText, @PathVariable String username, @PathVariable String password) {
UserService.UserValidationResult validationBaseAccess = userService.validateUserAdminAccess(username,password);
if(validationBaseAccess.error() != null){
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
JSONWrapper parsed = new JSONWrapper(requestText);
User user = new User(
parsed.get("initials"),
parsed.get("password"),
BooleanUtil.parseOr(parsed.get("admin"), false),
parsed.get("email"),
parsed.getOr("first_name","Guest"),
parsed.getOr("last_name","User")
);
user.setId(userService.getValidId());
UserPreferences prefs = UserPreferences.getDefault(user);
User asSaved = userService.save(user);
prefRepo.save(prefs); <---------Issue occurs right here!
asSaved.setPreferences(prefs);
return new ResponseEntity<>(asSaved,HttpStatus.OK);
}
And the stacktrace:
org.hibernate.AssertionFailure: null identifier
at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:51) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.internal.AbstractSharedSessionContract.generateEntityKey(AbstractSharedSessionContract.java:559) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.type.OneToOneType.isNull(OneToOneType.java:108) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:463) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.type.EntityType.resolve(EntityType.java:458) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.type.EntityType.replace(EntityType.java:359) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.type.AbstractType.replace(AbstractType.java:164) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:205) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:487) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:241) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:318) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:172) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:70) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:829) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:816) ~[hibernate-core-5.6.11.Final.jar:5.6.11.Final]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311) ~[spring-orm-5.3.23.jar:5.3.23]
at jdk.proxy4/jdk.proxy4.$Proxy111.merge(Unknown Source) ~[na:na]
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:669) ~[spring-data-jpa-2.7.3.jar:2.7.3]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:530) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:286) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:640) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:139) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:81) ~[spring-data-commons-2.7.3.jar:2.7.3]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-5.3.23.jar:5.3.23]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) ~[spring-tx-5.3.23.jar:5.3.23]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-5.3.23.jar:5.3.23]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-5.3.23.jar:5.3.23]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174) ~[spring-data-jpa-2.7.3.jar:2.7.3]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.23.jar:5.3.23]
at jdk.proxy4/jdk.proxy4.$Proxy120.save(Unknown Source) ~[na:na]
at gbw.TheScheduler.controllers.UserController.addNewUser(UserController.java:68) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.23.jar:5.3.23]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.23.jar:5.3.23]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.23.jar:5.3.23]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.65.jar:4.0.FR]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.65.jar:9.0.65]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.23.jar:5.3.23]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.23.jar:5.3.23]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.65.jar:9.0.65]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
And finally my application.properties:
server.port=6969
security.ignored=/**
management.security.enabled=false
spring.autoconfigure.exclude[0]=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
spring.jpa.hibernate.ddl-auto=none
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.jpa.show-sql=false
spring.jpa.open-in-view=false
spring.datasource.username=postgres
spring.datasource.password=definetlyMyPassword
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL81Dialect
spring.main.allow-bean-definition-overriding=true
If you know anything, please. I've been stuck with this for way too long for just a fun little side-project. :(
Solution
I found out, that my one to one mapping was wrong and my auto generated ID's went awry somewhere along the road. I think its a bug where hibernate doesn't realize that an autogenerated id should actually be null in the case of a new user.
Although confusing to look at, my one to one mapping ended up looking like this:
In User (parent):
@Id
@Column(name = "id")
private Long id;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="id"<!CHILD ID-FIELD!>, referencedColumnName = "id"<!PARENT ID-FIELD!>)
private UserPreferences userPreferences;
In UserPreferences (child):
@Id
@Column(name = "id")
private Long id;
@OneToOne(mappedBy="userPreferences", fetch = FetchType.EAGER)
private User user;
Do note that I'm doing a lot of this mapping manually as well. So, if you're relying more on Hibernate's native way of doing things, this might very well not work for you. (See my UserController to see what I mean).
Answered By - G. B. Wanscher
Answer Checked By - Senaida (JavaFixing Volunteer)