Unexpected RollBackException on merge in hibernate (RESOLVED) - java

I'm having UnexpectedRollBack exception on a simple merge method with hibernate.
I'm using JSP and controller structure.
it should be a simple update, but it returns an exception.
I have this:
User.java
public class user{
private Integer id;
String name;
String active;
}
#Id
#Column(name="ID", unique = true, nullable = false, etc...)
public Integer getId(){return this.id}
#Column(name = "NAME", nullable = true, length = 9)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#Column(name = "ACTIVE", nullable = true, length = 9)
public String getActive() {
return this.active;
}
public void setActive(String active) {
this.active= active;
}
Controller:
Public class UpdateUserController{
//Here's the main issue
User userToUpdate = findUserById("1");
//user properly recovered from the DB
userToUpdate.setActive("N");
//UserService is the class that contains the merging method
userService.merge(userToUpdate);
System.out.println(userToUpdate.getActive());
}
service class:
#Transactional(propagation = Propagation.REQUIRED)
public user merge(User detachedInstance) {
try {
User result = entityManager.merge(detachedInstance);
return result;
} catch (RuntimeException re) {
throw re;
}
}
It's a simple update of a String, only one cel in a row, this shouldn't return an exception but it does.
I've seen on some posts that the problem may be caused by the method try to update an element that is currently null, that may cause hibernate returns RollBackException by default.
I've checked and there is nothing null at the moment of the merge.
Thank you for the help.
RESOLVED:
The element name in data base was varchar (5)
On my code was trying to update a name with 7 char long, this caused the RollbackException.

Related

Hibernate: Program run persists new created entities together with entities which were persisted in previous program run and which I had deleted

This is maybe a beginner question on hibernate. I am doing my first steps, I designed a simple datamodel consisting of about 10 entities and I use hibernate to persist them to my Oracle XE database. Now I am facing the following problem: First time, when I do a transaction to persist some entities, they are persisted properly. I verify, that the data exists in the database and then I delete all the entries from all database tables. I verify that all tables are empty again. Then I run my program again to persist some new entities - and here happens something really strange: Afterwards I find in my databse the new entries as well as the old ones, which were persisted last time and which I had deleted! They contained the old IDs and the old data fields! How can this be? This happens even if I shut down my computer after the first time the program runs! How does it remember the old entries and where are they saved? Do you have any ideas?
Some information, that might be useful:
I am using annotations (instead of config files) for the mapping.
Following you see the classes used for persisting as well as one example of an entity (I am showing only one entity to avoid making the question too long).
As you see, I am using FetchType = EAGER on my MANY to MANY mappings (as I understand, this makes sure, that all related entities are loaded immediately together with any loaded entity). Can this have any impact?
Thanks for any help!
public class PersistenceManager {
private static final SessionFactory factory = new Configuration().configure().buildSessionFactory();
public static void sampleData() {
try(Session session = factory.openSession()) {
SampleDataLoader.loadSampleData(session);
} catch(HibernateException e) {
System.out.println("Exception during persisting! Message: " + e.getMessage());
e.printStackTrace();
}
}
}
public class SampleDataLoader {
static void loadSampleData(Session session) {
Language french = new Language("French");
Language german = new Language("German");
Noun garcon = new Noun(french, "garcon", false);
Noun junge = new Noun(german, "Junge", false);
junge.addTranslation(garcon);
ZUser user = new ZUser("Daniel", "password");
user.setOwnLanguage(german);
user.setEmail("abc#somemail.de");
user.setDateRegistered(LocalDateTime.now());
user.addForeignLanguage(french);
Transaction transaction = session.beginTransaction();
session.save(user);
session.save(french);
session.save(german);
session.save(junge);
transaction.commit();
}
}
#Entity
public class ZUser {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column
private String name;
#Column
private String password;
#Column
private String email;
#Column
private String picturePath;
#Column
private LocalDateTime dateRegistered;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="OWNLANGUAGE_ID")
private Language ownLanguage;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name="USER_LANGUAGE",
joinColumns=#JoinColumn(name="USER_ID"),
inverseJoinColumns=#JoinColumn(name="LANGUAGE_ID")
)
private Set<Language> foreignLanguages = new HashSet<>();
public ZUser() { }
public ZUser(String n, String p) {
name = n;
password = p;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPicturePath() { return picturePath; }
public void setPicturePath(String picturePath) { this.picturePath = picturePath; }
public LocalDateTime getDateRegistered() { return dateRegistered; }
public void setDateRegistered(LocalDateTime dateRegistered) { this.dateRegistered = dateRegistered; }
public Language getOwnLanguage() { return ownLanguage; }
public void setOwnLanguage(Language ownLanguage) { this.ownLanguage = ownLanguage; }
public void addForeignLanguage(Language language) {foreignLanguages.add(language);}
public Set<Language> getForeignLanguages() {return Collections.unmodifiableSet(foreignLanguages); }
}
Clarified by the comment of Jagger (see comments). Indeed, I was using Oracle SQL command line to delete the entries and I had rgotten, that I need to explicitely commit after deleting. The solution can be so easy :)

Hibernate call DELETE from table after method end

I have problem, and I don't know how to solve it.
I have entity:
#Entity
#Table(name = "entity_languagetree")
#AttributeOverride(name = "id", column = #Column(name = "languagetree_id"))
public class LanguageTree extends BaseObject {
#ElementCollection(targetClass = java.lang.String.class, fetch = FetchType.EAGER)
#CollectionTable(name = "view_languagetree_to_stringlist")
private List<String> relationship = new ArrayList<>();
public LanguageTree() {
//
}
public List<String> getRelationship() {
return relationship;
}
public void setRelationship(List<String> relationship) {
this.relationship = relationship;
}
}
where BaseObject is
#MappedSuperclass
public class BaseObject {
#Id
#GeneratedValue
#Column(name = "entity_id")
private Long id;
/**
*
* #return true if the entity hasn't been persisted yet
*/
#Transient
public boolean isNew() {
return id == null;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Bean getBean() {
return null;
}
}
Work with object - in my servlet, I am calling jsVarTree() like this:
String var = jsVarTree();
My problem is, that after method jsVarTree is finished, hibernate delete my relationship list from entity LanguageTree. I don't know why! I am not calling any delete and etc.. (I AM SURE, I SPENT A LOT OF TIME IN DEBUGER!)
:
#Override
public String jsVarTree() {
TreeBuilder tb = new TreeBuilder(getLanguageList());
return tb.getJsVarString(); // THIS METHOD IS ONLY GETTER !!!!
}
#Override
public List<String> getLanguageList() {
LanguageTree lt = getLanguageTreeObject();
return lt.getRelationship();
}
#Override
public LanguageTree getLanguageTreeObject() {
long fakingId = languageTreeDao.getLastId();
ServerLogger.logDebug("LAST FAKING ID: " +fakingId);
return languageTreeDao.findOne(fakingId);
}
I found this log in loggor:
HibernateLog --> 15:01:03 DEBUG org.hibernate.SQL - delete from
view_languagetree_to_stringlist where LanguageTree_languagetree_id=?
Can somebody tell me, why hibernate call delete over my table?
I saw a table in phpmyadmin..
TABLE IS FULL.
String var = jsVarTree();
TABLE IS EMPTY.
Table is deleted after return tb.getJsVarString(); is finished.
Thank you for any help!

Spring JSR 303 Validation access other field value while Edit/Add

I have a requirement wherein I want to use a Bean for both update/add. Now i have a validation as in the name should be unique.
Now during add the validation part is working correctly as it is checking for unique value by querying DB.
Now when i wanted to update the same record, it is trying to check the unique constraint in the DB and fails as the record already exists.
Role Bean
public class Role {
#NotEmpty
#Pattern(regexp = "[a-zA-Z ]*")
#UniqueValue(query = AppConstants.UNIQUE_VALIDATION_DB_QUERY)
private String roleName;
private String roleDesc;
private boolean active;
private String maskRoleName;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleDesc() {
return roleDesc;
}
public void setRoleDesc(String roleDesc) {
this.roleDesc = roleDesc;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
My Custom Annotation Validator
public class UniqueValueValidator implements ConstraintValidator<UniqueValue, String> {
#Autowired
private ValidationDAO validationDAO;
private String query;
public void initialize(UniqueValue uniqueValue) {
this.query = uniqueValue.query();
}
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (!StringUtils.isEmpty(value) && !StringUtils.isEmpty(query)) {
return validationDAO.isValidUniqueField(query, value);
}
return true;
}
}
Now when I update only the RoleDesc Field from screen the role name is validated and throws the validation error as the same role name exists in DB. Is there a way wherein I can send other variable to my custom validator from screen saying the following is update screen so only validate the field if it is changed from its previous value?
I came with a work around by annotating on a getter method where all the required fields are returned as a single map through that method and in the validationIMPL I retrieved all the required information and processed accordingly.
private String roleName;
#UniqueValue(query = AppConstants.UNIQUE_VALIDATION_DB_QUERY)
public Map<String,String> getUniqueValidator(){
Map<String,String> validatorMap=new HashMap<String,String>();
validatorMap.put("ACTION",type of action(update/new)):
validatorMap.put("VALUE",this.roleName):
return validatorMap;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
What you are probably looking for are Groups. You would modify your annotation to:
#UniqueValue(query = AppConstants.UNIQUE_VALIDATION_DB_QUERY, groups = {CreationGroup.class})
You'll also need to create a CreationGroup interface.
Then you will need to update your interceptor that calls the bean validation to use contextual information (possibly provided by another annotation wrapping the method where the validation is happening) to be something like this:
if (myMethodIsCreatingANewRecord()) {
validator.validate(address, Default.class, CreationGroup.class);
} else {
validator.validate(address, Default.class);
}

Is this a good practise to check for DUPLICATE value with spring-boot, CrudRepository, MySQL

I have this repository:
public interface AccountsRepository extends CrudRepository<Account, Long> {
}
Then I have this service class:
public class AccountService {
private AccountsRepository accountsRepository;
public AccountService(AccountsRepository accountsRepository) {
this.accountsRepository = accountsRepository;
}
public Account createNewAccount() {
Account account = new Account();
account = tryStoreAccountElseRepeatWithDifferentIdentifier(account);
return account;
}
private Account tryStoreAccountElseRepeatWithDifferentIdentifier(Account account) {
account.setIdentifier(IdentifierGenerator.generateString(6));
try {
return accountsRepository.save(account);
} catch (DataIntegrityViolationException e) {
return tryStoreAccountElseRepeatWithDifferentIdentifier(account);
}
}
}
Unit test:
public class AccountServiceUnitTests {
AccountService fixture;
AccountsRepository mockAccountRespository;
#Before
public void setup() {
mockAccountRespository = mock(AccountsRepository.class);
fixture = new AccountService(mockAccountRespository);
}
#Test
public void repeatCreateAccountWhenIdentifierIsDuplicateValue() {
Account account = new Account();
account.setId(123L);
account.setIdentifier("ABCDEF");
when(mockAccountRespository.save(any(Account.class)))
.thenThrow(DataIntegrityViolationException.class)
.thenThrow(DataIntegrityViolationException.class)
.thenThrow(DataIntegrityViolationException.class)
.thenReturn(account);
Account newAccount = fixture.createNewAccount();
assertEquals(account, newAccount);
}
}
Entity:
#Entity
#Table(name = "accounts")
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "identifier", unique = true)
private String identifier;
#Column(name = "username", unique = true)
private String username;
// getter, setter shortened
}
Thing is, I want to store a new Account into the database. Some columns have index UNIQUE. So when you try to insert data MySQL throws an exception if there is a DUPLICATE value.
Since the save(Entitiy) method does not throw a documented exception I tried what would happen and saw that a DataIntegrityViolationException is thrown in case I try to add duplicate value.
So my idea was basically try recursively to insert a new row till no exception is thrown. See: tryStoreAccountElseRepeatWithDifferentIdentifier
Is this way of checking for duplicate value good practise? Or is there a "built-in" solution I don't know?

how do I locate cause of errors in BindingResult

In a spring mvc web application using hibernate in eclipse and tomcat server, I changed a couple of text fields to drop down lists in a jsp, so that a person's gender and race can each be selected from its own drop down menu. I was careful to change other levels of the application, including setting up joined tables for gender and race in the underlying database, and changing code in the model and repository levels. The application compiles, and the jsp loads with the correct selected values for the selected person in each dropdown list, but clicking the submit/update button causes a BindingResult.hasErrors() problem which does not help me localize the cause of the problem.
Can someone help me find the cause of the failure to process the update?
Here is the processUpdatePatientForm() method that is called in the controller class. Note that it triggers the System.out.println() which shows that BindingResult.hasErrors() and returns the jsp:
#RequestMapping(value = "/patients/{patientId}/edit", method = RequestMethod.PUT)
public String processUpdatePatientForm(#Valid Patient patient, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
System.out.println(":::::::::::::::: in PatientController.processUpdatePatientForm() result.hasErrors() ");
List<ObjectError> errors = result.getAllErrors();
for(int i=0;i<result.getErrorCount();i++){System.out.println("]]]]]]] error "+i+" is: "+errors.get(i).toString());}
return "patients/createOrUpdatePatientForm";}
else {
this.clinicService.savePatient(patient);
status.setComplete();
return "redirect:/patients?patientID=" + patient.getId();
}
}
When the jsp is returned, the following error messages are included:
//This is printed out in my jsp below the Sex drop down list:
Failed to convert property value of type java.lang.String to required type org.springframework.samples.knowledgemanager.model.Gender for property sex; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Gender] for property sex: no matching editors or conversion strategy found
//This is printed out in my jsp below the Race drop down list:
Failed to convert property value of type java.lang.String to required type org.springframework.samples.knowledgemanager.model.Race for property race; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Race] for property race: no matching editors or conversion strategy found
The following is all that is printed in the eclipse console:
Hibernate: select gender0_.id as id1_2_, gender0_.name as name2_2_ from gender gender0_ order by gender0_.name
Hibernate: select race0_.id as id1_7_, race0_.name as name2_7_ from race race0_ order by race0_.name
:::::::::::::::: in PatientController.processUpdatePatientForm() result.hasErrors()
]]]]]]] error 0 is: Field error in object 'patient' on field 'race': rejected value [Hispanic]; codes [typeMismatch.patient.race,typeMismatch.race,typeMismatch.org.springframework.samples.knowledgemanager.model.Race,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.race,race]; arguments []; default message [race]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.springframework.samples.knowledgemanager.model.Race' for property 'race'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Race] for property 'race': no matching editors or conversion strategy found]
]]]]]]] error 1 is: Field error in object 'patient' on field 'sex': rejected value [Male]; codes [typeMismatch.patient.sex,typeMismatch.sex,typeMismatch.org.springframework.samples.knowledgemanager.model.Gender,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.sex,sex]; arguments []; default message [sex]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.springframework.samples.knowledgemanager.model.Gender' for property 'sex'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Gender] for property 'sex': no matching editors or conversion strategy found]
Note that the values [Hispanic] and [Male] are shown in the error message as triggering the error. The problem might be that the name property of Gender and Race is being passed to Spring MVC, when the id property should be passed instead. But how do I fix this in the code?
Can someone help me get to the bottom of this? The first step would be how can I get a more useful error message which locates the location in my code where the problem is being triggered.
EDIT:
Per Sotirios's request, the following is my form in the jsp:
<form:form modelAttribute="patient" method="${method}" class="form-horizontal" id="add-patient-form">
<petclinic:inputField label="First Name" name="firstName"/>
<petclinic:inputField label="Middle Initial" name="middleInitial"/>
<petclinic:inputField label="Last Name" name="lastName"/>
<div class="control-group">
<petclinic:selectField label="Sex" name="sex" names="${genders}" size="5"/>
</div>
<petclinic:inputField label="Date of Birth" name="dateOfBirth"/>
<div class="control-group">
<petclinic:selectField label="Race" name="race" names="${races}" size="5"/>
</div>
<div class="form-actions">
<c:choose>
<c:when test="${patient['new']}">
<button type="submit">Add Patient</button>
</c:when>
<c:otherwise>
<button type="submit">Update Patient</button>
</c:otherwise>
</c:choose>
</div>
</form:form>
And the Patient.java class is:
#Entity
#Table(name = "patients")
public class Patient extends BaseEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "patient", fetch=FetchType.EAGER)
private Set<Document> documents;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "patient", fetch=FetchType.EAGER)
private Set<Address> addresses;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "patient", fetch=FetchType.EAGER)
private Set<PhoneNumber> phonenumbers;
#Column(name = "first_name")
#NotEmpty
protected String firstName;
#Column(name = "middle_initial")
protected String middleInitial;
#Column(name = "last_name")
#NotEmpty
protected String lastName;
#ManyToOne
#JoinColumn(name = "sex_id")
protected Gender sex;
#Column(name = "date_of_birth")
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#DateTimeFormat(pattern = "yyyy/MM/dd")
protected DateTime dateOfBirth;
#ManyToOne
#JoinColumn(name = "race_id")
protected Race race;
////////////// Document methods
protected void setDocumentsInternal(Set<Document> documents) {this.documents = documents;}
public Set<Document> getFaxes() {
Set<Document> faxes = new HashSet<Document>();
for (Document doc : getDocumentsInternal()) {if (doc.getType().getName().equals("ScannedFaxes")) {faxes.add(doc);}}
return faxes;
}
public Set<Document> getForms() {
Set<Document> forms = new HashSet<Document>();
for (Document doc : getDocumentsInternal()) {if (doc.getType().getName().equals("ScannedPatientForms")) {forms.add(doc);}}
return forms;
}
protected Set<Document> getDocumentsInternal() {
if (this.documents == null) {this.documents = new HashSet<Document>();}
return this.documents;
}
public List<Document> getDocuments() {
List<Document> sortedDocuments = new ArrayList<Document>(getDocumentsInternal());
PropertyComparator.sort(sortedDocuments, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedDocuments);
}
public void addDocument(Document doc) {
getDocumentsInternal().add(doc);
doc.setPatient(this);
}
public Document getDocument(String name) {return getDocument(name, false);}
/** Return the Document with the given name, or null if none found for this Patient.
* #param name to test
* #return true if document name is already in use
*/
public Document getDocument(String name, boolean ignoreNew) {
name = name.toLowerCase();
for (Document doc : getDocumentsInternal()) {
if (!ignoreNew || !doc.isNew()) {
String compName = doc.getName();
compName = compName.toLowerCase();
if (compName.equals(name)) {
return doc;
}
}
}
return null;
}
//////////// Address methods
protected void setAddressesInternal(Set<Address> addresses) {this.addresses = addresses;}
protected Set<Address> getAddressesInternal() {
if (this.addresses == null) {this.addresses = new HashSet<Address>();}
return this.addresses;
}
public List<Address> getAddresses() {
List<Address> sortedAddresses = new ArrayList<Address>(getAddressesInternal());
PropertyComparator.sort(sortedAddresses, new MutableSortDefinition("address", true, true));
return Collections.unmodifiableList(sortedAddresses);
}
public void addAddress(Address addr) {
getAddressesInternal().add(addr);
addr.setPatient(this);
}
public Address getAddress(String address) {return getAddress(address, false);}
/** Return the Address with the given name, or null if none found for this Patient.
* #param name to test
* #return true if document name is already in use
*/
public Address getAddress(String addr, boolean ignoreNew) {
addr = addr.toLowerCase();
for (Address address1 : getAddressesInternal()) {
if (!ignoreNew || !address1.isNew()) {
String compName = address1.getAddress();
compName = compName.toLowerCase();
if (compName.equals(addr)) {
return address1;
}
}
}
return null;
}
//////////// PhoneNumber methods
protected void setPhoneNumbersInternal(Set<PhoneNumber> phonenumbers) {this.phonenumbers = phonenumbers;}
protected Set<PhoneNumber> getPhoneNumbersInternal() {
if (this.phonenumbers == null) {this.phonenumbers = new HashSet<PhoneNumber>();}
return this.phonenumbers;
}
public List<PhoneNumber> getPhoneNumbers() {
List<PhoneNumber> sortedPhoneNumbers = new ArrayList<PhoneNumber>(getPhoneNumbersInternal());
PropertyComparator.sort(sortedPhoneNumbers, new MutableSortDefinition("phonenumber", true, true));
return Collections.unmodifiableList(sortedPhoneNumbers);
}
public void addPhoneNumber(PhoneNumber pn) {
getPhoneNumbersInternal().add(pn);
pn.setPatient(this);
}
public PhoneNumber getPhoneNumber(String pn) {return getPhoneNumber(pn, false);}
/** Return the PhoneNumber with the given name, or null if none found for this Patient.
* #param name to test
* #return true if phone number is already in use
*/
public PhoneNumber getPhoneNumber(String pn, boolean ignoreNew) {
pn = pn.toLowerCase();
for (PhoneNumber number : getPhoneNumbersInternal()) {
if (!ignoreNew || !number.isNew()) {
String compName = number.getPhonenumber();
compName = compName.toLowerCase();
if (compName.equals(pn)) {
return number;
}
}
}
return null;
}
public String getFirstName(){return this.firstName;}
public void setFirstName(String firstName){this.firstName = firstName;}
public String getMiddleInitial() {return this.middleInitial;}
public void setMiddleInitial(String middleinitial) {this.middleInitial = middleinitial;}
public String getLastName() {return this.lastName;}
public void setLastName(String lastName) {this.lastName = lastName;}
public Gender getSex() {return this.sex;}
public void setSex(Gender sex) {this.sex = sex;}
public void setDateOfBirth(DateTime birthDate){this.dateOfBirth = birthDate;}
public DateTime getDateOfBirth(){return this.dateOfBirth;}
public Race getRace() {return this.race;}
public void setRace(Race race) {this.race = race;}
#Override
public String toString() {
return new ToStringCreator(this)
.append("id", this.getId())
.append("new", this.isNew())
.append("lastName", this.getLastName())
.append("firstName", this.getFirstName())
.append("middleinitial", this.getMiddleInitial())
.append("dateofbirth", this.dateOfBirth)
.toString();
}
}
SECOND EDIT:
Per Alexey's comment, the following is the method in the controller class which has always had the #InitBinder annotation. It is identical to a method in the controller of a similar module which works:
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {dataBinder.setDisallowedFields("id");}
THIRD EDIT:
PatientController.java:
#Controller
#SessionAttributes(types = Patient.class)
public class PatientController {
private final ClinicService clinicService;
#Autowired
public PatientController(ClinicService clinicService) {this.clinicService = clinicService;}
#ModelAttribute("genders")
public Collection<Gender> populateGenders() {return this.clinicService.findGenders();}
#ModelAttribute("races")
public Collection<Race> populateRaces() {return this.clinicService.findRaces();}
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {dataBinder.setDisallowedFields("id");}
#RequestMapping(value = "/patients/new", method = RequestMethod.GET)
public String initCreationForm(Map<String, Object> model) {
Patient patient = new Patient();
model.put("patient", patient);
return "patients/createOrUpdatePatientForm";
}
#RequestMapping(value = "/patients/new", method = RequestMethod.POST)
public String processCreationForm(#Valid Patient patient, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {return "patients/createOrUpdatePatientForm";}
else {
this.clinicService.savePatient(patient);
status.setComplete();
return "redirect:/patients?patientID=" + patient.getId();
}
}
#RequestMapping(value = "/patients", method = RequestMethod.GET)
public String processFindForm(#RequestParam("patientID") String patientId, Patient patient, BindingResult result, Map<String, Object> model) {
Collection<Patient> results = this.clinicService.findPatientByLastName("");
model.put("selections", results);
int patntId = Integer.parseInt(patientId);
Patient sel_patient = this.clinicService.findPatientById(patntId);//I added this
model.put("sel_patient",sel_patient);
return "patients/patientsList";
}
#RequestMapping(value = "/patients/{patientId}/edit", method = RequestMethod.GET)
public String initUpdatePatientForm(#PathVariable("patientId") int patientId, Model model) {
Patient patient = this.clinicService.findPatientById(patientId);
model.addAttribute(patient);
return "patients/createOrUpdatePatientForm";
}
#RequestMapping(value = "/patients/{patientId}/edit", method = RequestMethod.PUT)
public String processUpdatePatientForm(#Valid Patient patient, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
System.out.println(":::::::::::::::: in PatientController.processUpdatePatientForm() result.hasErrors() ");
List<ObjectError> errors = result.getAllErrors();
for(int i=0;i<result.getErrorCount();i++){System.out.println("]]]]]]] error "+i+" is: "+errors.get(i).toString());}
return "patients/createOrUpdatePatientForm";}
else {
this.clinicService.savePatient(patient);
status.setComplete();
return "redirect:/patients?patientID=" + patient.getId();
}
}
}
FOURTH EDIT:
Gender.java
#Entity
#Table(name = "gender")
public class Gender extends NamedEntity {}
NamedEntity.java:
#MappedSuperclass
public class NamedEntity extends BaseEntity {
#Column(name = "name")
private String name;
public void setName(String name) {this.name = name;}
public String getName() {return this.name;}
#Override
public String toString() {return this.getName();}
}
BaseEntity.java:
#MappedSuperclass
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
public void setId(Integer id) {this.id = id;}
public Integer getId() {return id;}
public boolean isNew() {return (this.id == null);}
}
You need to add a converter or a proper editor. I prefer the first one. Refer to section 6.5. on this page for the details.
Your converter would have to get the Entity with the given name from the database and return it. The code would be something like this:
class StringToGender implements Converter<String, Gender> {
#Autowired
private GenderRepository repository;
public Gender convert(String name) {
return repository.getGenderByName(name);
}
}
And in your application context xml (if you use xml):
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.example.StringToGender"/>
</set>
</property>

Categories