Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 on updating of an entity via hibernate - spring

When trying to update an entity from the controller, I keep getting this exception. Rest assured, thorough research regarding this exception has been made, but none of the suggestions have worked in my case.
I am trying to update the doctor entity by calling hibernate session.update(entity);
Doctor model:
#Entity
#Table(name = "tbl_doctor")
public class Doctor {
private Long id;
private PersonInfo personInfo;
private String licenseNumber;
private User user;
private String specialization;
private String employmentStatus;
private String suffix;
private List<Patient> patients;
//
// #OneToMany
// #JoinTable(name = "Doctor_Appointment", joinColumns = { #JoinColumn(name
// = "doctor_id") }, inverseJoinColumns = { #JoinColumn(name = //
// "appointment_id") })
// private List<Appointment> appointments;
public Doctor() {
super();
user = new User();
personInfo = new PersonInfo();
}
#Override
public String toString() {
String patients= "";
for(Patient p: this.patients) {
patients += p.toString();
}
return "Doctor [id=" + id + ", personInfo=" + personInfo
+ ", licenseNumber=" + licenseNumber + ", user=" + user
+ ", specialization=" + specialization + ", employmentStatus="
+ employmentStatus + ", suffix=" + suffix + ", patients="
+ patients + "]";
}
#Column(name = "fld_license_number")
public String getLicenseNumber() {
return licenseNumber;
}
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "key_user")
public User getUser() {
return user;
}
#Column(name = "fld_specialization")
public String getSpecialization() {
return specialization;
}
#Column(name = "fld_employment_status")
public String getEmploymentStatus() {
return employmentStatus;
}
#Column(name = "fld_suffix")
public String getSuffix() {
return suffix;
}
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "key_patient")
#Fetch(value = FetchMode.SUBSELECT)
public List<Patient> getPatients() {
return patients;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "key_doctor")
public Long getId() {
return id;
}
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "key_person_info")
public PersonInfo getPersonInfo() {
return personInfo;
}
//setters
}
Person info just includes the biodata like first name, last name, sex, birthday etc.
Controller
#RequestMapping(value="/update_doctor.it", method=RequestMethod.POST)
public String updateDoctor(HttpSession session, #ModelAttribute("doctor") Doctor doctor, Model model, #RequestParam("id") long adminId) {
System.err.println("update doctor controller");
String username = session.getAttribute("user").toString();
session.setAttribute("user", username);
doctor.getUser().setEnabled(true);
doctor.getPersonInfo().setDateModified(LocalDate.now());
doctorDao.updateDoctor(doctor);
return "redirect:/view_doctor_profile.it?id=" + doctor.getId();
}
DaoImpl
#Override
public void updateDoctor(Doctor doctor) {
Session session = sessionFactory.getCurrentSession();
session.update(doctor);
}
JSP
form:form class="docForm" method="post" commandName="doctor" action="update_doctor.it?id=${adminUser.getId() }" >
<section>
<header>
<h3>Doctor Information</h3>
</header>
<form:hidden path="id" value="${doctor.id}" />
<ul class="fields">
<li><label>First Name</label>:<form:input value="${doctor.getPersonInfo().getFirstName()}" path="personInfo.firstName" type="text" required="true" /></li>
<li><label>Last Name</label>:<form:input value="${doctor.getPersonInfo().getLastName()}" path="personInfo.lastName" type="text" required="true" /></li>
<li><label>Suffix</label>:<form:input value="${doctor.getSuffix() }" path="suffix" /></li>
<li><label>License Number</label>:<form:input value="${ doctor.getLicenseNumber() }" path="licenseNumber" /></li>
<li><label>Occupation</label>:<form:input value="${doctor.getPersonInfo().getOccupation() }" path="personInfo.occupation" /></li>
<li><label>Specialization</label>:<form:input value="${ doctor.getSpecialization() }" path="specialization" required="true"/></li>
<li><label>Date of Birth</label>:<input value="${doctor.getPersonInfo().getDateOfBirth() }" name="personInfo.dateOfBirth" type="text" id="datepicker" readonly/></li>
<li><label>Gender</label>:
<label>Male</label><form:radiobutton path="personInfo.sex" value="male"/>
<label>Female</Label><form:radiobutton path="personInfo.sex" value="female"/>
</li>
<li><label>Phone Number</label>:<form:input value="${ doctor.getPersonInfo().getContacts().get(0).getPhoneNumber() }" path="personInfo.contacts[0].phoneNumber"/></li>
<li><label>Mobile Number</label>:<form:input value="${ doctor.getPersonInfo().getContacts().get(0).getMobileNumber() }" path="personInfo.contacts[0].mobileNumber" type="text" required="true"/></li>
<li><label>E-mail Address</label>:<form:input value="${ doctor.getPersonInfo().getEmail() }" path="personInfo.email" type="text" required="true"/></li>
</ul>
</section>
<section>
<header><h3>Address</h3></header>
<ul class="fields">
<li><label>Address</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(0).getAddress() }" path="personInfo.addresses[0].address"/></li>
<li><label>City</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(0).getCity() }" path="personInfo.addresses[0].city"/></li>
<li><label>Province</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(0).getProvince() }" path="personInfo.addresses[0].province"/></li>
<li><label>Zip Code</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(0).getZipCode() }" path="personInfo.addresses[0].zipCode" /></li>
</ul>
</section>
<section>
<header><h3>Account Details</h3></header>
<ul class="fields">
<li><label>Username</label>:<form:input value="${ doctor.getUser().getUsername() }" path="user.username" required="true"/></li>
<li><label>Password</label>:<form:password value="${ doctor.getUser().getPassword() }" path="user.password" required="true"/></li>
</ul>
</section>
<section>
<header>
<h3>Hospital Details</h3>
</header>
<ul class="fields">
<li><label>Name</label>:<form:input value="${doctor.getPersonInfo().getCompanyName() }" path="personInfo.companyName"/></li>
<li><label>Employment Status</label>:
<label>Full-time</label><form:radiobutton path="employmentStatus" value="full-time"/>
<label>Part-time</Label><form:radiobutton path="employmentStatus" value="part-time"/>
</li>
<li><label>Phone Number</label>:<form:input value="${ doctor.getPersonInfo().getContacts().get(1).getPhoneNumber()}" path="personInfo.contacts[1].phoneNumber"/></li>
<li><label>Work Mobile Number</label>:<form:input value="${ doctor.getPersonInfo().getContacts().get(1).getPhoneNumber()}" path="personInfo.contacts[1].mobileNumber" /></li>
<li><label>Address</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(1).getAddress() }" path="personInfo.addresses[1].address" /></li>
<li><label>City</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(1).getCity() }" path="personInfo.addresses[1].city"/></li>
<li><label>Province</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(1).getProvince() }" path="personInfo.addresses[1].province" /></li>
<li><label>Zip Code</label>:<form:input value="${ doctor.getPersonInfo().getAddresses().get(1).getZipCode() }" path="personInfo.addresses[1].zipCode" />
</ul>
</section>
<section>
<ul class="btnForm">
<li><span class="btn"><input type="submit"
value="Save" class="btnS"></span></li>
<li><span class="btn"><input type="button"
value="Cancel" class="btnCancel" onClick="viewPotentialsList()"></span></li>
</ul>
Error
SEVERE: Servlet.service() for servlet [emrMVC] in context with path [/emr] threw exception [Request processing failed; nested exception is org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1] with root cause
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:85)
at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:70)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:90)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:268)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:657)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:755)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.sun.proxy.$Proxy27.updateDoctor(Unknown Source)
at com.ust.emr.controller.admin.EditDoctorController.updateDoctor(EditDoctorController.java:56)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:838)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:315)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

I figured it out. A lot of classes or entities are dependent on the doctor, the entity I'm updating. In order for the batch update to execute successfully, I had to place a on the jsp, fetching all the ids of the dependent classes. Thank you!

Related

There is an error using Spring Boot to receive the value using 'UserInfoDto' and then store the value in 'Users'

This is view to receive the above values.
Press the Submit button at the end of the form tag to send the data.
(id, password, name, phone_number, ssn, city_name, town_name, street_name, zip_code, details)
Used : Mysql, Springboot, java, jpa, html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>signuppage</title>
<link rel = "stylesheet" href ="/css/sample.css">
<link href="/css/listpage_copy.css" rel="stylesheet">
<link href="/css/font.css" rel = "stylesheet">
</head>
<body>
<form class="row g-3" id = "row_g-3_cumstom" action="/signup_execute" name = "user_info" method="post">
<div class="col-md-6">
<label for="inputEmail4" class="form-label">이메일</label>
<input type="email" class="form-control" id="inputEmail4" placeholder="최대 20자리 까지 입력가능" name = "id" maxlength="40">
</div>
<div class="col-md-6">
<label for="inputPassword4" class="form-label">비밀번호</label>
<input type="password" class="form-control" id="inputPassword4" placeholder="최대 20자리 까지 입력가능" name = "password" maxlength="20">
</div>
<div class="col-md-6">
<label for="inputPassword4" class="form-label">닉네임</label>
<input type="text" class="form-control" placeholder="최대 25자리까지 입력가능" name = "name" maxlength="25">
</div>
<div class="col-md-6">
<label for="inputPassword4" class="form-label">전화번호</label>
<input type="tel" class="form-control" placeholder="ex) 01012345678" pattern="[0-9]{11}" name = "phone" maxlength="12">
</div>
<div class="col-12">
<label for="inputAddress" class="form-label">주민번호</label>
<input type="password" class="form-control" id="inputAddress" placeholder="13자리를 입려하세요." pattern="[0-9]{13}" name = "ssn" maxlength="13">
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="flexRadioDefault" id="flexRadioDefault1">
<label class="form-check-label" for="flexRadioDefault1">
남성
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="flexRadioDefault" id="flexRadioDefault2" checked>
<label class="form-check-label" for="flexRadioDefault2">
여성
</label>
</div>
<div class="row g-3">
<div class="col-sm-7">
도시명
<input type="text" class="form-control" placeholder="ex) 서울특별시" aria-label="City" name = "city_name" maxlength="20">
</div>
<div class="col-sm">
동명
<input type="text" class="form-control" placeholder="ex) 논현동" aria-label="State" name = "town_name" maxlength="20">
</div>
<div class="col-sm">
도로명
<input type="text" class="form-control" placeholder="ex) 테헤란로 221길" aria-label="Zip" name = "street_name" maxlength="20">
</div>
<div class="col-sm">
우편번호
<input type="text" class="form-control" placeholder="ex) 06049" aria-label="Zip" name = "zip_code" maxlength="20">
</div>
<div class="col-sm">
상세주소
<input type="text" class="form-control" placeholder="ex) 5층 505호" aria-label="Zip" name = "details" maxlength="20">
</div>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="gridCheck">
<label class="form-check-label" for="gridCheck">
확인했음.
</label>
</div>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary" >가입하기</button>
</div>
</form>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</body>
</html>
'Users' entity to register with DB.
package My_Project.integration.entity;
import My_Project.integration.entity.Dto.UserInfoDto;
import lombok.*;
import javax.persistence.*;
import java.awt.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
#Entity
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Setter
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = {"ssn","phone_number"})})
public class Users {
#Column(name = "id", length = 40, updatable = false)
#Id
private String id;
#Column(name = "password", length = 20, nullable = false)
private String password;
#Column(name = "name", length = 25, nullable = false)
private String name;
#Column(name = "phone_number", length = 12, nullable = false)
private String phoneNumber;
#Column(name = "ssn", length = 13, nullable = false, updatable = false)
private String ssn;
#Embedded
private Address address;
#Column(name = "point")
private Long point;
#OneToMany(mappedBy = "postedUser")
private List<PostInfo> uploadedPost = new ArrayList<>();
#OneToMany(mappedBy = "userId")
private List<PointHistory> pointHistories = new ArrayList<>();
#Embedded
private Dates dates;
public Users(UserInfoDto userInfoDto){
this.setId(userInfoDto.getId());
this.setPassword(userInfoDto.getPassword());
this.setPhoneNumber(userInfoDto.getPhoneNumber());
this.setSsn(userInfoDto.getSsn());
this.setPoint(0L);
Address address = new Address(
userInfoDto.getCityName(),
userInfoDto.getTownName(),
userInfoDto.getStreetName(),
userInfoDto.getZipCode(),
userInfoDto.getDetailsCode()
);
this.setAddress(address);
List<PostInfo> postInfoList = new ArrayList<>();
List<PointHistory> pointHistoryList = new ArrayList<>();
this.setUploadedPost(postInfoList);
this.setPointHistories(pointHistoryList);
Dates dates = new Dates(LocalDateTime.now(), LocalDateTime.now());
this.setDates(dates);
}
}
This is Embedded type 'Address' inside 'Users'
package My_Project.integration.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import javax.persistence.Column;
import javax.persistence.Embeddable;
#Embeddable
#Getter
#AllArgsConstructor
public class Address {
#Column(name = "city_name", length = 20, nullable = false) //도시명
private String cityName;
#Column(name = "town_name", length = 20) //동명
private String townName;
#Column(name = "street_name", length = 20, nullable = false) //도로명
private String streetName;
#Column(name = "zip_code", length = 20) // 우편번호
private String zipCode;
#Column(name = "details",length = 20) //상세주소
private String detailsCode;
protected Address() {
}
}
This is a Dto class that receives a value instead of 'Users'.
package My_Project.integration.entity.Dto;
import My_Project.integration.entity.PointHistory;
import My_Project.integration.entity.PostInfo;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
#Setter
#Getter
public class UserInfoDto {
private String id;
private String password;
private String name;
private String phoneNumber;
private String ssn;
private String cityName;
private String townName;
private String streetName;
private String zipCode;
private String detailsCode;
private Long point;
private List<PointHistory> pointHistories;
private List<PostInfo> postInfos;
public UserInfoDto(String id, String password, String name, String phoneNumber, String ssn, String cityName, String townName, String streetName, String zipCode, String detailsCode, Long point, List<PointHistory> pointHistories, List<PostInfo> postInfos) {
this.id = id;
this.password = password;
this.name = name;
this.phoneNumber = phoneNumber;
this.ssn = ssn;
this.cityName = cityName;
this.townName = townName;
this.streetName = streetName;
this.zipCode = zipCode;
this.detailsCode = detailsCode;
this.point = point;
this.pointHistories = pointHistories;
this.postInfos = postInfos;
}
public UserInfoDto() {
}
}
I want to put the values on 'Users' through 'UserInfoDto'. But It's doesn't work with error code above. What should I fix?
Error code
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : My_Project.integration.entity.Users.address.cityName; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : My_Project.integration.entity.Users.address.cityName
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:294)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at jdk.proxy4/jdk.proxy4.$Proxy109.save(Unknown Source)
at My_Project.integration.service.UserService.addUsers(UserService.java:26)
at My_Project.integration.service.UserService$$FastClassBySpringCGLIB$$636d190f.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at My_Project.integration.service.UserService$$EnhancerBySpringCGLIB$$7615f959.addUsers(<generated>)
at My_Project.integration.controller.SignupPageController.signUp(SignupPageController.java:24)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:696)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:779)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value : My_Project.integration.entity.Users.address.cityName
at org.hibernate.engine.internal.Nullability.checkNullability(Nullability.java:122)
at org.hibernate.engine.internal.Nullability.checkNullability(Nullability.java:55)
at org.hibernate.action.internal.AbstractEntityInsertAction.nullifyTransientReferencesIfNotAlready(AbstractEntityInsertAction.java:116)
at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:125)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:289)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:250)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:338)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:287)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:193)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:135)
at org.hibernate.event.internal.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:271)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:243)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:318)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:172)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:70)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:829)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:816)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362)
at jdk.proxy4/jdk.proxy4.$Proxy105.merge(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311)
at jdk.proxy4/jdk.proxy4.$Proxy105.merge(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:669)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:530)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:286)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:640)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:139)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:81)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
... 71 more
From the exception it seams that the field cityName is null somehow when you do the mapping. As there is a not null constraint on this field this causes the issue

Thymeleaf not loading content when I add validation in Spring Boot

Intro:
My app works in the general sense. All as I want. But when I set up validation (and it does work), it won't load all my hidden content. I moved it all to its own page, and it still did not work. So I am asking here with my original set up. I understand it is messy, but I will clean it up once I figure this out.
I can't find anything related to my problem, which makes me think, the way that I am doing it might not be the best approach. So pointers are more than welcome.
Problem:
Adding Validation to my forms breaks my Thymeleaf template. Unable to hide and unhide elements. It looks stuck. But on the terminal, the program does run fine.
My controller
(it's big... I still need to learn how to break it in smaller chunks.)
#Controller
#SessionAttributes({"guess", "word", "result", "level", "attempt", "message", "credits", "name", "gameScore"})
public class GameController {
private static final String WORD_TO_GUESS_CONSTANT = "WORD_TO_GUESS";
private static final String GUESSED_WORD_CONSTANT = "GUESSED_WORD";
private static final String RESULT_CONSTANT = "RESULT_WORD";
private static final String ATTEMPTS_CONSTANT = "ATTEMPTS";
private static final String TOTAL_ATTEMPTS_CONSTANT = "TOTAL_ATTEMPTS";
private static final String MESSAGE_CONSTANT = "MESSAGE";
private static final String CREDITS_CONSTANT = "CREDITS";
private static final String SELECTED_LEVEL_CONSTANT = "SELECTED_LEVEL";
private static final String NAME_CONSTANT = "NAME";
private static final String GAME_SCORE_CONSTANT = "GAME_SCORE";
private static final String SCORE_MULTIPLIER_CONSTANT = "SCORE_MULTIPLIER";
#Autowired
private RandomWordService randomWord;
#Autowired
private WordCheckService checkGuess;
#Autowired
private IsWordCorrectService isWordCorrectService;
#Autowired
private ScoreSavingService scoreSavingService;
#ModelAttribute("gameDto")
public GameDTO guessDTOForm() {
return new GameDTO();
}
#ModelAttribute("score")
public Score score() {
return new Score();
}
// GAME METHODS
#GetMapping(value = "/index")
public String home(Model model,
final HttpServletRequest request,
final HttpSession session,
GameDTO gameDTO,
Score score) {
model.addAttribute("name", session.getAttribute(NAME_CONSTANT));
model.addAttribute("levelSelected", session.getAttribute(SELECTED_LEVEL_CONSTANT));
model.addAttribute("attempt", session.getAttribute(ATTEMPTS_CONSTANT));
model.addAttribute("credits", session.getAttribute(CREDITS_CONSTANT));
model.addAttribute("attemptStart", session.getAttribute(TOTAL_ATTEMPTS_CONSTANT));
model.addAttribute("guess", session.getAttribute(GUESSED_WORD_CONSTANT));
model.addAttribute("result", session.getAttribute(RESULT_CONSTANT));
model.addAttribute("message", session.getAttribute(MESSAGE_CONSTANT));
model.addAttribute("gameScore", session.getAttribute(GAME_SCORE_CONSTANT));
model.addAttribute("lvlName", Level.values());
return "index";
}
#PostMapping(value = "/loadgame")
public String loadWord(
final HttpSession session, final HttpServletRequest request,
#ModelAttribute("score") Score score,
#Valid GameDTO gameDTO, BindingResult bindingResult,
Model model
) throws IOException {
if (bindingResult.hasErrors()) {
model.addAttribute("lvlName", Level.values());
model.addAttribute("name", session.getAttribute(NAME_CONSTANT));
model.addAttribute("levelSelected", session.getAttribute(SELECTED_LEVEL_CONSTANT));
model.addAttribute("attempt", session.getAttribute(ATTEMPTS_CONSTANT));
model.addAttribute("credits", session.getAttribute(CREDITS_CONSTANT));
model.addAttribute("attemptStart", session.getAttribute(TOTAL_ATTEMPTS_CONSTANT));
model.addAttribute("guess", session.getAttribute(GUESSED_WORD_CONSTANT));
model.addAttribute("result", session.getAttribute(RESULT_CONSTANT));
model.addAttribute("message", session.getAttribute(MESSAGE_CONSTANT));
model.addAttribute("gameScore", session.getAttribute(GAME_SCORE_CONSTANT));
return "index";
}
// NEW GAME
String word = (String) request.getSession().getAttribute(WORD_TO_GUESS_CONSTANT);
if (word == null) {
request.getSession().setAttribute(NAME_CONSTANT, gameDTO.getPlayerName());
request.getSession().setAttribute(ATTEMPTS_CONSTANT, gameDTO.getLvlName().getAttempts());
request.getSession().setAttribute(WORD_TO_GUESS_CONSTANT, randomWord.selectRandomWord());
request.getSession().setAttribute(CREDITS_CONSTANT, gameDTO.getCredit());
request.getSession().setAttribute(SELECTED_LEVEL_CONSTANT, gameDTO.getLvlName().getLvlName());
request.getSession().setAttribute(TOTAL_ATTEMPTS_CONSTANT, gameDTO.getLvlName().getAttempts());
request.getSession().setAttribute(GAME_SCORE_CONSTANT, gameDTO.getScore());
request.getSession().setAttribute(SCORE_MULTIPLIER_CONSTANT, gameDTO.getLvlName().getMultiplier());
gameDTO.setWord((String) session.getAttribute(WORD_TO_GUESS_CONSTANT));
}
model.addAttribute("message", "");
return "redirect:/index";
}
#PostMapping(value = "/guess")
public String guessWord(
final HttpSession session,
final HttpServletRequest request,
#ModelAttribute("score") Score score,
#Valid GameDTO gameDTO, BindingResult bindingResult) throws IOException {
if (bindingResult.hasErrors()) {
return "index";
}
// variables
int attempts = (int) session.getAttribute(ATTEMPTS_CONSTANT);
int credits = (int) session.getAttribute(CREDITS_CONSTANT);
int startAttempts = (int) session.getAttribute(TOTAL_ATTEMPTS_CONSTANT);
String name = (String) session.getAttribute(NAME_CONSTANT);
// check word
String wordToGuess = (String) session.getAttribute(WORD_TO_GUESS_CONSTANT);
String guess = gameDTO.getGuess();
String result = checkGuess.resultWord(wordToGuess, guess);
String lvl = (String) session.getAttribute(SELECTED_LEVEL_CONSTANT);
// adjust score according to result
boolean wordIsCorrect = isWordCorrectService.isTheWordCorrect(result, wordToGuess);
int gameScore = (int) session.getAttribute(GAME_SCORE_CONSTANT);
int scoreMultiplier = (int) request.getSession().getAttribute(SCORE_MULTIPLIER_CONSTANT);
int wrongWord = gameDTO.getWrongWord();
int initialScore = gameDTO.getStartScore();
int finalScorePerWord = initialScore * scoreMultiplier;
// GAME LOGIC
if (!wordIsCorrect) {
String message = "";
message = "Wrong! Try again!";
request.getSession().setAttribute(MESSAGE_CONSTANT, message);
request.getSession().setAttribute(ATTEMPTS_CONSTANT, --attempts);
request.getSession().setAttribute(GAME_SCORE_CONSTANT, gameScore - wrongWord);
log(GameController.class, "Updated score: " + session.getAttribute(GAME_SCORE_CONSTANT));
if (attempts == 0) {
request.getSession().setAttribute(CREDITS_CONSTANT, --credits);
message = "Sorry, the word was: [ " + session.getAttribute(WORD_TO_GUESS_CONSTANT) + " ]";
request.getSession().setAttribute(MESSAGE_CONSTANT, message);
request.getSession().setAttribute(ATTEMPTS_CONSTANT, startAttempts);
request.getSession().setAttribute(WORD_TO_GUESS_CONSTANT, randomWord.selectRandomWord());
}
if (credits == 0) {
message = "Game over!";
request.getSession().setAttribute(MESSAGE_CONSTANT, message);
request.getSession().setAttribute(GAME_SCORE_CONSTANT, gameScore);
// SAVE SCORE
score.setGameScore(gameScore);
score.setName(name);
score.setSelectedLevelName(lvl);
scoreSavingService.saveScore(score);
log(GameController.class, "Final score: " + session.getAttribute(GAME_SCORE_CONSTANT));
}
} else {
String message = "Correct! Guess another word!";
wordToGuess = randomWord.selectRandomWord();
gameDTO.setWord(wordToGuess);
request.getSession().setAttribute(MESSAGE_CONSTANT, message);
request.getSession().setAttribute(WORD_TO_GUESS_CONSTANT, wordToGuess);
request.getSession().setAttribute(ATTEMPTS_CONSTANT, startAttempts);
request.getSession().setAttribute(GAME_SCORE_CONSTANT, gameScore + finalScorePerWord);
log(GameController.class, "Current score 2: " + session.getAttribute(GAME_SCORE_CONSTANT));
}
request.getSession().setAttribute(GUESSED_WORD_CONSTANT, guess);
request.getSession().setAttribute(RESULT_CONSTANT, result);
log(GameController.class, "Attempts are now: " + session.getAttribute(ATTEMPTS_CONSTANT));
return "redirect:/index";
}
// BUTTONS
#PostMapping(value = "/save")
public String giveUpAndSaveScore(final HttpSession session,
final HttpServletRequest request,
#ModelAttribute("score") Score score) {
score.setGameScore((Integer) session.getAttribute(GAME_SCORE_CONSTANT));
score.setName((String) session.getAttribute(NAME_CONSTANT));
score.setSelectedLevelName((String) session.getAttribute(SELECTED_LEVEL_CONSTANT));
scoreSavingService.saveScore(score);
request.getSession().invalidate();
return "index";
}
#GetMapping(value = "/scores")
public String seeScores(final HttpServletRequest request, Model model) {
List<Score> scoreList = scoreSavingService.getScore(5, 1);
model.addAttribute("score", scoreList);
return "scores";
}
// CLOSE SESSION
#PostMapping(value = "/destroy")
public String restartGame(final HttpServletRequest request) {
log(GameController.class, " Session closing. Removing the data.");
request.getSession().invalidate();
return "redirect:/index";
}
// EXCEPTION HANDLERS
#ExceptionHandler(value = ArrayIndexOutOfBoundsException.class)
public String handleArrayIndexOutOfBoundsException(final Model model) {
String text = "ERROR: Could not check empty <<guess>>.";
model.addAttribute("text", text);
return "ExceptionPage";
}
#ExceptionHandler(value = NullPointerException.class)
public String handleNullPointerException(final Model model) {
String text = "ERROR: Cannot compare words because <<word to guess>> is null";
model.addAttribute("text", text);
return "ExceptionPage";
}
}
Thymeleaf template for index.html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd">
<html lang="en" xmlns:th="www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>5Letters</title>
<link th:href="#{/bootstrap.min.css}" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Quicksand&display=swap" rel="stylesheet">
</head>
<body>
<div class="container-sm">
<div class="col-xs-12" align="center">
<!-- HEADER -->
<img src="https://marisabel.nl/wp-content/uploads/2020/11/avatar-e1606738130828-150x150.png"
style="margin-top:2vh;">
<h1 style="margin-top:1vh; color:hotpink;"><b>5Letter Word Game</b></h1>
<h5>AKA: Lingo.</h5>
<hr>
</div>
<form method="get" th:action="#{/scores}">
<input type="submit" value="scores" class="btn btn-primary" />
</form>
<!-- Set Up Game Form -->
<div class="col-xs-12" align="center" th:hidden="${credits} != null">
<form th:action="#{/loadgame}" th:object="${gameDTO}" method="post">
<p>
<select class="form-group" th:field="*{lvlName}" id="dropDownList">
<option th:each="lvl : ${lvlName}" th:text="${lvl.lvlName}" th:value="${lvl.lvlName}">
</option>
</select>
</p>
<p><input type="text" class="form-group mx-sm-3 mb-2" id="name" th:field="*{playerName}"
th:placeholder="name" th:value="anonymous" /></p>
<p class="alert alert-danger" th:if="${#fields.hasErrors('playerName')}" th:errors="*{playerName}">
</p>
<p><input type="submit" value="start" class="btn btn-primary" /></p>
</form>
<p></p>
</div>
<!-- This whole session will load after name and level are chosen. Credits will be set to 3, triggering them to unhide -->
<div class="row">
<div class="col-xs-12 col-md-6" align="center" th:hidden="${credits} == null">
<!-- game information : always show after game setup -->
<h3 th:text="'Hello ' + ${name} + '!'"></h3>
<h3 th:text="'Level: '+ ${levelSelected}"></h3>
<h2 th:text="'Credits : '+ ${credits} + ' | Score: '+ ${gameScore}"></h2>
<h2 th:text="${attempt} + ' / ' + ${attemptStart}"></h2>
</div>
<div class="col-md-6" align="center">
<p>
<!-- Result messages and word after guessing -->
<h4 th:text="${message}"></h4>
<h2 id="result" th:text="${result}" th:hidden="${credits} == 0"></h2>
<!-- GUESS FORM -->
<form th:action="#{/guess}" th:object="${gameDTO}" method="post" th:hidden="${credits} == null">
<input id="guessField" type="text" th:field="*{guess}" placeholder="5letters" />
<p></p>
<p class="alert alert-danger" th:if="${#fields.hasErrors('guess')}" th:errors="*{guess}"></p>
<input type="submit" value="guess" th:disabled="${credits} == 0" class="btn btn-primary" />
</form>
</p>
</div>
</div>
<div class="row" style="margin-top:10vh;">
<div class="row justify-content-center" th:hidden="${credits} == null">
<div class="col col-lg-2 align-items-center">
<!-- Destroy session data and go to index -->
<form method="post" th:action="#{/destroy}">
<input type="submit" value="play again" class="btn btn-danger" />
</form>
<p></p>
</div>
<div class="col-md-auto align-items-center" width="50%">
<!-- Display last typed word -->
<h4 th:text="'Your guess was:'" th:hidden="${attempt} == ${attemptStart}"></h4>
<h2 id="guess" th:text="${guess}" th:hidden="${attempt} == ${attemptStart}"></h2>
<p></p>
</div>
<div class="col col-lg-2 align-items-center">
<!-- Stops the game if you are bored. Usually needed with EASY mode. -->
<form method="post" th:action="#{/save}">
<input type="submit" value="i'm tired" class="btn btn-danger" />
<p></p>
</form>
</div>
</div>
</div>
</body>
</html>
I moved the content I was hiding to its own page. It loads the page, but the content remains hidden. Even after taking a break I am still unable to find what is wrong. Specially when it works 100% without validation.

Spring Boot+Thymeleaf: th not able to resolve a Spring EL expression

I am using spring boot+thymeleaf+neo4j. Everything is working fine except that thymeleaf is not able to resolve a few of the attributes of the 'product' variable used in the th:each block in the template product_grid.html, which includes th:src="${product.URL}", th:text="${Product.title}" and the th:action="#{/product/(${Product.getId()})}" expression in form tag. The th:text="${Product.Price}" is working. When I check the code produced in the browser the src tag is empty (src:""), the text attribute containing the title tag is not shown in the browser. The th:action works fine but when I click the button defined inside the form, the url changes to http://localhost:8080/product/?btn=View+Product
instead of the following code which is shown in the browser console
http://localhost:8080/product/?1
Note: I am trying to get the image url from a field which is stored in neo4j database. The project directory is:
project directory image
Template:product_grid.html
<html xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Products</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js" integrity="sha384-feJI7QwhOS+hwpX2zkaeJQjeiwlhOP+SdQDqhgvvo1DsjtiSQByFdThsxO669S2D"
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Grada</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ">
<a class="nav-link" href="#">Home
<span class="sr-only">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link">My Best Products</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="#{/login}">Login</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<a class="btn btn-outline-success my-2 my-sm-0" href="file:///home/madhav/SPM/Grada/public_html/product.html">Search</a>
</form>
</div>
</nav>
<div class="container text-center">
<div class="row">
<div th:each="Product:${products}" class="col-lg-3 col-sm-12 col-md-6 my-2 p-auto">
<div class="card">
<div class="card-body">
<img src="http://via.placeholder.com/150x150/888/111" th:src="${Product.URL}" alt="img" class="card-img-top img-thumbnail img-fluid">
<div class="card-title lead" th:text="${Product.title}">Some product name</div>
<div class="card-text">Price: ₹<span th:text="${Product.Price}">400</span></div>
</div>
<form method="GET" action="/" th:action="#{/product/(${Product.getId()})}">
<input type="submit" name="btn" class="form-control btn btn-primary" value="View Product">
<input type="submit" name="btn" class="form-control btn btn-primary" value="Add to Cart">
</form>
</div>
</div>
</div>
</div>
</body>
</html>`
Product model:Product.html
package com.grada.ecommerce.Models;
import com.grada.ecommerce.Models.Seller;
import org.neo4j.ogm.annotation.*;
import java.util.HashSet;
import java.util.Set;
#NodeEntity(label = "Product")
public class Product
{
public Product()
{
}
public Product(String title, Double price, int quantity, float rating, String description, String url, String company)
{
this.title = title;
this.Rating = rating;
this.Description = description;
this.Price = price;
this.Quantity = quantity;
this.URL = url;
this.Company = company;
}
#Id
#GeneratedValue
private Long id;
#Property(name = "title")
public String title;
#Property(name = "Rating")
public float Rating;
#Property(name = "Description")
public String Description;
#Property(name = "Price")
public Double Price;
#Property(name = "Quantity")
public int Quantity;
#Property(name = "Company")
public String Company;
#Property(name = "URL")
public String URL;
#Override
public String toString()
{
return this.title;
}
public Long getId() {
return id;
}
public String getTitle()
{
return title;
}
#Relationship(type = "Sells", direction = Relationship.INCOMING)
public Seller Seller;
}
ProductController.java
package com.grada.ecommerce.Controllers;
import com.grada.ecommerce.Models.Product;
import com.grada.ecommerce.Models.Seller;
import com.grada.ecommerce.Services.ProductService;
import com.grada.ecommerce.Services.SellerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#Controller
public class ProductController
{
final ProductService productService;
final SellerService sellerService;
#Autowired
public ProductController(ProductService productService, SellerService sellerService)
{
this.productService = productService;
this.sellerService = sellerService;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public String Index(Model model)
{
Iterable<Product> products = productService.products();
model.addAttribute("products", products);
return "product_grid";
}
#RequestMapping(value = "/product", method = RequestMethod.GET)
public String ShowProduct(#RequestParam(value = "id") Long id, Model model)
{
Product product = productService.findProductByID(id);
if(product == null)
return "redirect:/";
model.addAttribute("product", product);
return "productid";
}
#RequestMapping(value = "/add")
public String AddProduct(Model model)
{
model.addAttribute("product", new Product());
return "add";
}
#RequestMapping(value = "/add", method = RequestMethod.POST)
public String AddProduct(#ModelAttribute Product product)
{
productService.addProduct(product);
return "redirect:/";
}
#RequestMapping(value = "/delete", method = RequestMethod.GET)
public String DeleteProduct(Model model)
{
model.addAttribute("product", new Product());
return "delete";
}
#RequestMapping(value = "/delete", method = RequestMethod.POST)
public String DeleteProduct(#ModelAttribute Product product)
{
productService.deleteProduct(product);
return "redirect:/";
}
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String LoginPage(Model model)
{
return "login";
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String Authenticate(Model model, String username, String password)
{
if (username.equalsIgnoreCase("seller#fake.com") && password.equals("fakeseller"))
{
Iterable<Product> products = productService.products();
model.addAttribute("products", products);
return "loggedin";
}
else
return "redirect:/login";
}
#RequestMapping(value = "/policy", method = RequestMethod.GET)
public String PolicyPage()
{
return "policies";
}
}
Welcome to SO.
Include setX methods for your variables in the Product class. Thymeleaf needs these to bind.
IMO, a great way to do this is to use Project Lombok and simply annotate your class with #Data. Then you won't even need to specify getters or setters (or your toString()) at all.
Use lower-case for your variables since by convention variables with a capital first letter refers to the class, not an instance variable.
As mentioned, use POST instead of GET in your form since you are submitting data.
Use the shorthand #GetMapping and #PostMapping to make it easier to read.

Cannot update entity

I'm trying to update an entity with a particular mapping :
#Entity
#Table(name = "obs_structure2")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Structure implements Serializable{
private String id;
private String tag;
private String description;
private Set<Version> parentVersions = new HashSet<>();
private List<StructureElement> children = new ArrayList<>();
private List<StructureElement> slaves = new ArrayList<>();
private PersistenceSignature signature = new PersistenceSignature();
#OneToMany(mappedBy = "parentStructure")
public List<StructureElement> getChildren() {
return children;
}
public void addChild(StructureElement se)
{
if(this.children.contains(se))
{
this.children.add(se);
}
}
and
#Entity
#Table(name = "obs_structure_element2")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class StructureElement implements Serializable{
private long id;
private String tag;
private String description;
private boolean repetitive = false;
private boolean optional = false;
private Integer position = 1;
private Structure parentStructure;
private Structure typeStructure;
private PersistenceSignature signature = new PersistenceSignature();
#ManyToOne
#JoinColumn(name = "parent_id")
#JsonIgnore
public Structure getParentStructure() {
return parentStructure;
}
and this is my controller :
#PutMapping("/update")
public StructureElement update(#RequestBody StructureElement se){
logger.info("Call to StructureElementController.update with se = " + se);
this.connectToDatabase();
StructureElement updatedSE;
try{
boolean exists = this.repository.exists(se.getId());
if(!exists){
logger.error("Could not find StructureElement to update. se = " + se);
return null;
}
updatedSE = this.repository.save(se);
logger.info("Updated SE = " + updatedSE);
}catch (Exception e){
logger.error("Could not update structure element. se = " + se, e);
return null;
}
return updatedSE;
}
When I try to update an element the parentStructure is overwritten to null.
If I delete the #JsonIgnore annotation I'm getting this StackOverflow error:
[ERROR] 2018-01-03 14:22:38.577 [http-nio-8080-exec-5] o.a.c.c.C.[.[.[.[dispatcherServlet] 181 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError: null
at java.util.AbstractCollection.toString(AbstractCollection.java:454) ~[?:1.8.0_151]
at java.lang.String.valueOf(String.java:2994) ~[?:1.8.0_151]
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[?:1.8.0_151]
at be.groups.observatory.server.model.Structure.toString(Structure.java:147) ~[classes/:?]
at java.lang.String.valueOf(String.java:2994) ~[?:1.8.0_151]
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[?:1.8.0_151]
at be.groups.observatory.server.model.StructureElement.toString(StructureElement.java:116) ~[classes/:?]
at java.lang.String.valueOf(String.java:2994) ~[?:1.8.0_151]
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[?:1.8.0_151]
at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[?:1.8.0_151]
at java.lang.String.valueOf(String.java:2994) ~[?:1.8.0_151]
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[?:1.8.0_151]
at be.groups.observatory.server.model.Structure.toString(Structure.java:147) ~[classes/:?]
at java.lang.String.valueOf(String.java:2994) ~[?:1.8.0_151]
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[?:1.8.0_151]
at be.groups.observatory.server.model.StructureElement.toString(StructureElement.java:116) ~[classes/:?]
at java.lang.String.valueOf(String.java:2994) ~[?:1.8.0_151]
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[?:1.8.0_151]
at java.util.AbstractCollection.toString(AbstractCollection.java:462) ~[?:1.8.0_151]
at java.lang.String.valueOf(String.java:2994) ~[?:1.8.0_151]
at java.lang.StringBuilder.append(StringBuilder.java:131) ~[?:1.8.0_151]
at be.groups.observatory.server.model.Structure.toString(Structure.java:147) ~[classes/:?]
and for those who know vue.js here is my component :
<template>
<div class="t-body">
<obs-sidebar></obs-sidebar>
<main class="t-content">
<div v-if="updated" class="c-notification c-notification--success" role="dialog" aria-label="first notification" aria-describedby="desc_1">
<p id="desc_1" class="c-notification__message">L'élément de structure à bien été mise à jour.</p>
</div>
<div v-if="errors && errors.length">
<div v-for="error of errors" class="c-notification c-notification--danger">
{{ error.message }}
</div>
</div>
<div class="l-row l-row--gutter">
<div class="l-col">
<h1>{{ msg }} - {{ element_id }}</h1>
</div>
</div>
<loader v-if="visible"></loader>
<div class="l-row l-row--gutter" v-else>
<div class="l-col-4">
<form class="c-form full-width" #submit.prevent="updateStructureElement">
<div class="l-row">
<fieldset class="c-form__fieldset">
<legend class="c-form__legend">Remplissez le formulaire</legend>
<div class="l-row nice-top">
<div class="l-col-3 l-justify--end nice-right">
<div class="c-form__field-group">
<label for="tag" class="c-form__label">Tag <span class="s-text s-text--danger">* </span></label>
</div>
</div>
<div class="l-col-7">
<div class="c-form__field-group full-width">
<input id="tag" class="c-form__field full-width" type="text" name="tag" v-model="element.tag"/>
</div>
</div>
</div>
<div class="l-row nice-top">
<div class="l-col-3 l-justify--end nice-right">
<div class="c-form__field-group">
<label for="description" class="c-form__label">Description <span class="s-text s-text--danger">* </span></label>
</div>
</div>
<div class="l-col-7">
<div class="c-form__field-group full-width">
<textarea id="description" class="c-form__field minimal-h" name="description" v-model="element.description"></textarea>
</div>
</div>
</div>
<div class="l-row nice-top">
<div class="l-col-6 l-justify--end nice-right">
<div class="c-form__field-group">
<label for="optional" class="c-form__label">Optional <span class="s-text s-text--danger">* </span></label>
</div>
</div>
<div class="l-col-6">
<div class="c-form__field-group">
<input id="optional" class="c-form__field" type="checkbox" name="optional" v-model="element.optional"/>
</div>
</div>
</div>
<div class="l-row nice-top">
<div class="l-col-6 l-justify--end nice-right">
<div class="c-form__field-group">
<label for="repetitive" class="c-form__label">Répétitif <span class="s-text s-text--danger">* </span></label>
</div>
</div>
<div class="l-col-6">
<div class="c-form__field-group full-width">
<input id="repetitive" class="c-form__field" type="checkbox" name="repetitive" v-model="element.repetitive"/>
</div>
</div>
</div>
</fieldset>
</div>
<div class="l-row nice-top">
<div class="l-col-offset-3 l-col-7">
<button class="c-btn c-btn--primary c-btn--raised c-btn--ripple c-form__button full-width s-text--center" type="submit">Soumettre</button>
</div>
</div>
</form>
</div>
</div>
</main>
</div>
</template>
<script>
import Loader from 'gso-loader';
import {state, show, hide} from 'gso-loader/store';
import {HTTP} from '../http-common';
import ObsSidebar from './parts/sidebar.vue';
export default{
name: 'update-structure-element',
props: ['element_id'],
data() {
return {
msg: 'Mise à jour de l\'élément',
element: [],
errors: [],
updated: false
};
},
computed: {
visible() {
return state.visible;
}
},
methods: {
updateStructureElement: function () {
let seToUpdate = this.element;
console.log('To update = ' + JSON.stringify(seToUpdate));
HTTP.put('structure-element/update', seToUpdate, {headers: {'Content-Type': 'application/json'}})
.then(response => {
console.log('response = ' + response);
this.element = response.data;
this.updated = true;
})
.catch(e => {
console.log('Error = ' + e);
this.errors.push(e);
});
}
},
components: {ObsSidebar, Loader},
created() {
show();
let elementId = this.$route.params.element_id;
HTTP.get('structure-element/' + elementId)
.then(response => {
this.element = response.data;
hide();
})
.catch(e => {
this.errors.push(e);
hide();
});
}
};
</script>
Well, I've looking in some articles and changed the way I update the entity:
#PutMapping("/update")
public StructureElement update(#RequestBody StructureElement se){
StructureElement databaseElement;
logger.info("Call to StructureElementController.update with se = " + se);
this.connectToDatabase();
try{
databaseElement = this.repository.findOne(se.getId());
if(databaseElement == null){
logger.error("Could not find StructureElement to update. se = " + se);
return null;
}
// Update
databaseElement.setDescription(se.getDescription());
databaseElement.setTag(se.getTag());
databaseElement.setOptional(se.isOptional());
databaseElement.setRepetitive(se.isRepetitive());
databaseElement.getSignature().setModificationDate(new Date());
databaseElement = this.repository.save(databaseElement);
logger.info("Updated SE = " + databaseElement);
}catch (Exception e){
logger.error("Could not update structure element. se = " + se, e);
return null;
}
return databaseElement;
}
Now this works.
Seems that Spring data JPA doesnt want to update an entity like a simple JPA EntityManager....

Springboot Thymeleaf : How to format row according to a condition

I have a page that displays the list of all the journals available. I want to write a thymeleaf expression language that highlights the already subscribed journals using there journal id . So for all the subscribed journals the text for the hyper-link href should be "Unsubscribe" and vice verse if its not subscribed.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<title>TBD</title>
<!--/*/ <th:block th:include="fragments/headinc :: head"></th:block> /*/-->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" />
</head>
<body>
<div class="container">
<h1 th:text="'Hello, ' + ${user.fullname} + '!'" />
<p>Manage your subscriptions here</p>
<form role="form" id="form-search" class="form-inline"
th:action="#{/subscriptions}" method="get">
<input type="text" class="form-control" id="filter" name="filter"
placeholder="Enter filter"></input>
<button type="submit" class="btn btn-default">
<span class="glyphicon glyphicon-search"></span> Search
</button>
<a th:href="#{/logout}" class="btn btn-link" role="button">Logout</a>
</form>
<div th:if="${not #lists.isEmpty(journals)}">
<form role="form" id="form-subscribe" th:action="#{/subscribe}"
method="post">
<input type="hidden" name="journalId" id="journalId" />
</form>
<table id="table" class="table">
<thead>
<tr>
<th>Subject</th>
<th>Filename</th>
<th>Tags</th>
<th>View</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr th:each="journal : ${journals}">
<td th:text="${journal.subject}">Id</td>
<td th:text="${journal.filename}">Product Id</td>
<td th:text="${journal.tags}">Description</td>
<td><a>View</a></td>
<td><a id="href"
th:href="'javascript:subscribe(\'' + ${journal.id} + '\');'">Subscribe</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
<script type="text/javascript">
function subscribe(journalId) {
$('#journalId').val(journalId);
$('#form-subscribe').submit();
}
</script>
<script type="text/javascript" th:inline="javascript">
/*<![CDATA[*/
$(document).ready(function() {
var modelAttributeValue = [[${subscriptions}]];
console.log(modelAttributeValue);
alert(modelAttributeValue);
var array = modelAttributeValue.split(';');
console.log(array);
alert(array);
});
/*]]>*/
</script>
</html>
Controller
#Controller
public class SubscriptionController {
#Autowired
private SubscriberService subscriberService;
#RequestMapping(value = "/subscribe", method = RequestMethod.POST)
String subscribe(Model model, #RequestParam("journalId") Integer journalId) {
JournalToken token = (JournalToken) SecurityContextHolder.getContext().getAuthentication();
Account user = (Account) token.getCredentials();
model.addAttribute("user", user);
Journal journal = this.subscriberService.findJournalById(journalId);
this.subscriberService.subscribeJournalForSubscriber(journal, user);
return "redirect:subscriptions";
}
#RequestMapping(value = "/subscriptions", method = RequestMethod.GET)
String list(Model model) {
JournalToken token = (JournalToken) SecurityContextHolder.getContext().getAuthentication();
Account user = (Account) token.getCredentials();
model.addAttribute("user", user);
ArrayList<Journal> journals = this.subscriberService.FindAllJournals();
model.addAttribute("journals", journals);
StringBuilder sub = new StringBuilder();
ArrayList<Subscription> subscribed = this.subscriberService.getSubscribedJournalsForSubscriber(user);
model.addAttribute("subscriptions", subscribed);
return "subscriptions";
}
}
Model subscriptions
#Entity
#Table(uniqueConstraints={#UniqueConstraint(columnNames={"userId", "journalId"})})
public class Subscription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Version
private Integer version;
private Integer userId;
private Integer journalId;
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return this.id;
}
public void setVersion(Integer version) {
this.version = version;
}
public Integer getVersion() {
return this.version;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getUserId() {
return this.userId;
}
public void setJournalId(Integer journalId) {
this.journalId = journalId;
}
public Integer getJournalId() {
return this.journalId;
}
}
you can change your arrayList subscribed to have only the ids of journals (more optimised).
So, in the controller you can have something like this
ArrayList<Integer> subscribed =
this.subscriberService.getSubscribedJournalsForSubscriber(user); //modify it so it returns the journals ids instead of the whole object(Subscription)
then in the thymeleaf change the anchor with something like this
<a id="href" th:href="'javascript:subscribe(\'' + ${journal.id} + '\');'">
<span th:if="${#lists.contains(subscriptions, journal.id) }" th:text="Unsubscribe"> Unsubscribe </span>
<span th:if="not ${#lists.contains(subscriptions, journal.id) }" th:text="Subscribe"> Subscribe </span>
</a>
Have a look at the documentation of thymeleaf http://www.thymeleaf.org/doc/tutorials/2.1/usingthymeleaf.html
/*
* Check if element or elements are contained in list
*/
${#lists.contains(list, element)}
${#lists.containsAll(list, elements)}

Resources