I am developing a Grails 2.3.7 application and I'm having trouble changing a domain property with a select box. Every time I try to change the property and save, I get a HibernateException: identifier of an instance of Ethnicity was altered from X to Y
. I don't want to change the ID of the ethnicity, I simply want to change the ApplicationPerson
s ethnicity from one to another.
A few things to note:
personInstance.ethnicity
to null
right before personInstance.properties = params
will make the save work, but I
don't know why, and I don't want to do this for every association
that I want to change. Here are my domain classes:
class ApplicationPerson implements Serializable {
Integer appId
Integer applicationSequenceNumber
String firstName
String lastName
Ethnicity ethnicity
static mapping = {
id composite: ['appId', 'applicationSequenceNumber'],
generator: 'assigned'
}
}
class Ethnicity {
String code
String description
static mapping = {
id name: 'code', generator: 'assigned'
}
}
Here is my _form.gsp
to update the Ethnicity
(I removed all the other properties that are saving just fine):
<div class="fieldcontain ${hasErrors(bean: personInstance,
field: 'ethnicity', 'error')} ">
<label for="ethnicity">Ethnicity</label>
<g:select id="ethnicity"
name="ethnicity.code"
from="${Ethnicity.list()}"
optionKey="code"
value="${personInstance?.ethnicity?.code}" />
</div>
And lastly, my controller action that the form POSTs to:
def save() {
Application app = applicationService.getCurrentApplication()
// Find/Create and save Person
ApplicationPerson personInstance = app.person
if (!personInstance) {
personInstance =
new ApplicationPerson(appId: app.id,
applicationSequenceNumber: app.sequenceNumber)
}
personInstance.properties = params
if (!personInstance.validate()) {
respond personInstance.errors, view:'edit'
return
}
personInstance.save flush:true
redirect action: 'list'
}
Modify the name in the select
element from ethnicity.code
to personInstance.etnicity.code
as shown below:
<g:select id="ethnicity"
name="personInstance.ethnicity.code"
from="${Ethnicity.list()}"
optionKey="code"
value="${personInstance?.ethnicity?.code}" />
The name of selected option gets bound to params
as key and the selected value as the
value
against the key. Using ethnicity.code
would try to modify the primary key of an existing ethnicity
instead of modifying the ethnicity of an application person.
UPDATE
Above change in name is optional (can be used in case you don't need params
to be assigned as properties of domain class). Previous name ethnicity.code
should work as well but below changes are also required in the controller action in order to set ethnicity:
//or use params.ethnicity.code
//if name is ethnicity.code
person.ethnicity = Ethnicity.load(params.personInstance.ethnicity.code)
//or use params if name is ethnicity.code
person.properties = params.personInstance
Load an existing Ethnicity
based on the passed in code
and then set it in person
before setting properties.
The issue lies with code
being the primary key of Ethnicity
. If Ethnicity
had a separate identity column (for example, the default Long id
) then your exact implementation in question would work with the help of data binding. But since it is mentioned that you are working with legacy database, I suppose you won't be able to modify the tables to add another column for id
. So your best bet will be to load
(cheap compared to get, as row is loaded from hibernate cache) ethnicity from the passed in code and then set it to person.
You can also see try caching the Ethnicity domain if possible because that will be master data and a good candidate for caching.