I have been struggling with this error for a week now, and I am seriously losing my mind over this! I have tried multible implementations and work-arounds and hacks and what not, but I just keep stubling into just another exception.
I am using the Executor plugin to run a method asynchroniously:
runAsync{
run(...)
}
The method initially deletes some objects:
page.delete(flush:true)
And then later possibly recreating those objects:
def page = new Page(type : Page.TYPE_TABLE, domain : domainVersion.domain, identifier : tableName)
page.save(flush: true, failOnError: true)
But that fails with the following exception:
Caused by: org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.ramboll.egs.ohs.domain.Domain#1]
The relationship between the Page and Domain is simply implemented by Page having a Domain attribute. No hasMany og belongsTo - as I was discouraged from this in an earlier post due to performance issues.
I think I have tried all thinkable combinations of save, merge, withTransachtion and PersistenceContextInterceptor...
How is this supposed to work? Examples please.
Thanks in advance!
It doesn't appear that working in a new thread is the issue, it looks like a standard validation problem. It's saying that the Page is null, which indicates a validation error since save() returns the instance if it was successful, or null if there's a one or more validation errors. There are a few options:
def page = new Page(type : Page.TYPE_TABLE,
domain: dbUpdate.domainVersion.domain, identifier: tableName)
page.save(flush:true)
if (page.hasErrors()) {
// handle errors
}
else {
def pageVersion = createPageVersion(page, dbUpdate.domainVersion,
con, tableName, dbUpdate.author).save(flush:true)
}
or use failOnError to throw an exception:
def page = new Page(type : Page.TYPE_TABLE, identifier: tableName,
domain: dbUpdate.domainVersion.domain).save(flush:true, failOnError: true)
def pageVersion = createPageVersion(page, dbUpdate.domainVersion,
con, tableName, dbUpdate.author).save(flush:true)
Related
We notices after the update from Grails 3.1.11 to 3.2.0 that one action of a controller is no longer working:
#Transactional(readOnly = true)
class RoomPlanController {
...
def show(RoomPlan roomPlan) {
...
}
def getRooms(RoomPlan roomPlan) {
...
}
}
The problem is that when we call roomPlan/getRooms/1 roomPlan is null. If we call the show action with the same parameter roomPlan is set correct.
A call of getErrors() inside the controller gives us the following error message:
Could not obtain current Hibernate Session; nested exception is org.hibernate.HibernateException: No Session found for current thread
which has it's origin from grails.artefact.Controller.initializeCommandObject. After some more debugging I noticed a difference in the stacktrace between show and getRooms
Stacktrace of show:
show:100, RoomPlanController (at.byte_code.businessSuite.hotel)
$tt__show:-1, RoomPlanController (at.byte_code.businessSuite.hotel)
doCall:-1, RoomPlanController$_show_closure13 (at.byte_code.businessSuite.hotel)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
Stacktrace of getRooms:
getRooms:109, RoomPlanController (at.byte_code.businessSuite.hotel)
getRooms:-1, RoomPlanController (at.byte_code.businessSuite.hotel)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
The error message and the different stacktrace let us assume it has something to do with the database session/transaction and after adding #Transactional(readOnly = true) to the action everything work as expected and before the update to grails 3.2.0. If we remove the annotation and fails again.
We were not able see the issue in any other controller and were not able to reproduce it in a small test project. We already tried to rebuild the project, also on a completely new workstation we were not.
Did anybody else observed such an issue?
I don't think you would even need #Transactional(readOnly = true) in controller.
Grails controllers are by default readOnly. You can simply delete the annotation from the controller.
In contrast, Grails service class are transactional by default. If you need to call the save() method, it's more desirable to call that method in the service class.
When I do custom rejectValue in a service method grails loses that error(s) between service method and return to controller. This seems to happen when updating a row instance, but not when creating one.
In service
def specialValidation(petInstance){
if(petInstance.petType.requiresStateId && !petInstance.StateId){
petInstance.errors.rejectValue('StateId','StateId required');
}
println petInstance.errors //shows 1 error
return petInstance;
}
In controller
...
petInstance.properties=params;
petInstance=petService.specialValidation(petInstance);
println petInstance.errors //shows 0 errors
How is the error being lost when the instance changes hands from service to controller?
It can be because of transactional service. Service opens separate transaction for each method and clears entities after method end. You can find this mentioned in docs(read the last paragraph of part )
I had the same problem. Than I've added NotTransactional annotation to validation method, and it helped. Errors were saved.
Well I did something simular :
orderService.validate(order, params)
if (order.hasErrors()) {
return render(view: 'create', model: [order: order])
}
In the Service I do some validation like this:
if (end.before(start)) {
order.errors.rejectValue("end", '', 'ERROR');
}
The different to yours is that i didn't set the errorCode but the message at itself, have a look at the rejectValue Methods:
void rejectValue(String field, String errorCode);
void rejectValue(String field, String errorCode, String defaultMessage);
You could also try to use the rejectValue method like me, maybe it helps.
I found you can also avoid this by using
MyDomain.read(id)
instead of
MyDomain.get(id)
I have a scaffolded domain class and I am trying to get it to throw an optimistic locking exception, but it won't. I put the same instance in edit mode in two different browsers. I edit a field in browser 1 and click update. I verified that the version increments. I then edit in browser 2 and click update. It just overwrites the browser 1 changes with the browser 2 changes. I've seen many questions here about people getting the exception when they don't want it, so I am wondering if they turned it off in Hibernate somehow. I even tried changing the update method so all it does is execute instance.save(). Am I not understanding what optimistic locking is supposed to do?
package demo
class issue {
String synopsis
String details
static constraints = {
synopsis()
details(size: 0..1000)
}
}
I haven't tried Grails 2.3.x, but in versions up to 2.2.4 the version check is happening exactly in update action and not deep inside hibernate:
def update(Long id, Long version) {
def priceInstance = Price.get(id)
//..
if (version != null) {
if (priceInstance.version > version) {
priceInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
[message(code: 'price.label', default: 'Price')] as Object[],
"Another user has updated this Price while you were editing")
render(view: "edit", model: [priceInstance: priceInstance])
return
}
}
priceInstance.properties = params
if (!priceInstance.save(flush: true)) {
render(view: "edit", model: [priceInstance: priceInstance])
return
}
...
}
if you are not using that or similar code, GORM stores the instance as it is without checking for version
All of this is on Grails 2.2.3.
I have two classes in a One-to-many relationship, and a service which removes a list of ids
class Box {
String name
static hasMany = [items:ItemDomain]
static constraints = {
items(nullable:true)
}
}
and
class ItemDomain { String name Box box
static belongsTo = Box
static constraints = {
name(blank:false,unique:['box'], maxSize:127)
box(nullable:false) } }
In the service, here's the problem section:
def itemsToDelete = params.itemsToDelete //List of DB ids
List<ItemDomain> items= []
items.addAll(box.items) //Copy the list to avoid concurrent mod exception
for(ItemDomain item : items)
{
if(itemsToDelete.contains(item.id))
{
box.removeFromItems(item)
item.delete()
}
box.save(flush: true)
}
This works fine when running the application, but from integration testing it fails with
InvalidDataAccessApiUsageException: deleted object would be re-saved by cascade (remove deleted object from associations)
If I take out the flush, and eventually it will fail with:
Field error in object 'mypackage.ItemDomain' on field 'box': rejected value [null];
Adding logging, I see the size of box.items before entering the loop is the same as it is after exiting the loop, printing the items in the loop before and after shows that the item.box field for the deleted items changes to null. I've tried messing with the cascade mapping in the parent class... I'm at a loss as to whether I'm doing something wrong or if this is an issue with integration testing. The only similar issues I found were against grails 1.1 and had no resolution in the threads that I found.
I appreciate any feedback.
So, not surprisingly, I was doing something wrong. It turns out that my equals() and hashCode() implementations on the ItemDomain class were including a field that was never supposed to change, but due to requirements creep, was now changing and the methods never got updated properly.
tl:dr; This is a bit involved of a problem, any advice is welcome, appreciate reading in advance :)
My coworkers and I have been struggling a bit with an odd behavior in our batch processing application. We recently upgraded it from Grails 1.3.7 to 2.1
The stacktrace is showing the following error:
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException:
Cannot insert the value NULL into column 'date_created',
table 'dev.dbo.notification_log'; column does not allow nulls. INSERT fails.
...
[quartzScheduler_Worker-1] [||] ERROR hibernate.AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
org.hibernate.AssertionFailure: null id in com.virtuwell.domain.NotificationLog entry (don't flush the Session after an exception occurs)
at org.quartz.core.QuartzScheduler.notifyJobListenersWasExecuted(QuartzScheduler.java:1891)
at org.quartz.core.JobRunShell.notifyJobListenersComplete(JobRunShell.java:352)
at org.quartz.core.JobRunShell.run(JobRunShell.java:223)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:546)
[quartzScheduler_Worker-1] [||] ERROR listeners.SessionBinderJobListener - Cannot flush Hibernate Sesssion, error will be ignored
org.hibernate.AssertionFailure: null id in com.virtuwell.domain.NotificationLog entry (don't flush the Session after an exception occurs)
at org.quartz.core.QuartzScheduler.notifyJobListenersWasExecuted(QuartzScheduler.java:1891)
at org.quartz.core.JobRunShell.notifyJobListenersComplete(JobRunShell.java:352)
at org.quartz.core.JobRunShell.run(JobRunShell.java:223)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:546)
Here is the code of that particular Domain Object (NotificationLog)
class NotificationLog implements Serializable{
Date dateCreated
Notification notification
NotificationDeliveryState deliveryState
String message
static mapping = {
message type: 'text'
}
}
What's strange, however, is this error doesn't occur EVERY time that domain object is persisted, and we only have one place in the code that object is ever persisted, shown below:
class NotificationLogService {
boolean transactional = true
def logNotification(Notification notification, message, deliveryState) {
def notificationLog = new NotificationLog(
notification: notification,
deliveryState: deliveryState,
message:message
)
try{
notificationLog.save(failOnError:true)
} catch (Exception e) { // Failure to save a notificationLog should not rollback the calling transaction
log.error "NotificationLog State:[$deliveryState] for notification:${notification?.id} did not save. Errors: ${notificationLog?.errors}, Message:$message", e
}
}
}
We've found a 'hack' of a workaround in the below SO question where we are no longer periodically seeing the error in the logs, by adding this to the Domain Object
static mapping = {
autoTimestamp true
}
But this isn't the only domain we're seeing with the SAME periodic failure to save (thus, I need to add the mapping to other domains), and if this truly is necessary for dateCreated to function properly in Grails 2.1, I need to add it to a LOT more domains!
Worse, I can't reproduce it in a Unit or Integration test, its only happening on our running Dev and QA instances.
So, 2 Questions:
Does anyone know why this error might be periodically occurring?
If not, is there a way I can globally add this autoTimestamp true mapping to ALL of my project's domain objects (I've been unable to find documentation for how to add it at all, other than to set it to false)
Relevant SO Question:
dateCreated, lastUpdated fields in Grails 2.0
Relevant Grails Maillist discussion
http://grails.1312388.n4.nabble.com/dateCreated-lastUpdated-in-Grails-2-0-td4337894.html
Relevant Grails docs on autoTimestamp GORM properties
http://grails.org/doc/latest/guide/GORM.html#eventsAutoTimestamping
To answer both of the questions:
EDIT Try flush: true while save otherwise autoTimestamp true is the last resort. I have not researched to find out the cause of this issue.
You can set this property in Config.groovy to make it applicable for all domain classes.
grails.gorm.default.mapping = {
autoTimestamp true //or false based on your need
}
Have you tried to manually insert the date when creating a new NotificationLog?
Like this:
class NotificationLogService {
boolean transactional = true
def logNotification(Notification notification, message, deliveryState) {
def notificationLog = new NotificationLog(
dateCreated: new Date(),
notification: notification,
deliveryState: deliveryState,
message:message
)
try{
notificationLog.save(failOnError:true)
} catch (Exception e) { // Failure to save a notificationLog should not rollback the calling transaction
log.error "NotificationLog State:[$deliveryState] for notification:${notification?.id} did not save. Errors: ${notificationLog?.errors}, Message:$message", e
}
}
}