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
}
}
}
Related
I have a grails domain object that I validate and then save as such:
if(foo.hasErrors()) {
transactionStatus.setRollbackOnly()
respond foo.errors, view: 'create'
return
}
foo.save flush:true, failOnError:true
println(foo)
There are no errors given on the save. But when I call the println, it says my object is unsaved. However, if I check the database, it has indeed been persisted. Is there something I can check to tell me why grails is telling me it is unsaved. The grails version is 3.0.9.
After more testing, it looks like it is related to the way I specify the key in my domain mapping.
static mapping = {
version false
autoTimestamp false
id name:'foo_id', generator:'increment'
}
If I remove the id field and let Grails handle it by default the object is saved properly. If I add the id field back, it has the issue described above. Is there a reason for it?
I wouldn't be too concerned about the format of toString(). You're calling save with failOnError: true, so it's safe to assume that an exception would be thrown if persistence fails. If you don't specify this argument, you can check that persistence succeeds like so
if (foo.save()) {
println 'it worked'
} else {
println 'if failed'
}
try
foo = foo.save flush:true, failOnError:true
println(foo)
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'm using grails 1.3.7 together with Oracle 11g and trying to manage inner transactions.
I have a bean Person that is passed to a transactional (Propagation.REQUIRED) service method who makes some modification. Then it is passed to another transactional (propagation = Propagation.REQUIRES_NEW) method that makes some other modification and then throws an Exception.
What I expected to see is the rollback of all the modification of the second service but still valid those of the first one. This is the situation:
//outer transaction
class MyService {
def nestedService
#Transactional(propagation = Propagation.REQUIRED)
public void testRequiredWithError(Person person) {
person.name = 'Mark'
try {
nestedService.testRequiresNewWithError(person)
} catch (RuntimeException e) {
println person.age //this prints 15
println e
}
}
}//end MyService
//inner transaction
class NestedService{
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void testRequiresNewWithError(Person person) {
person.age = 15 //expected after Exception will be discarded
throw new RuntimeException("Rollback this transaction!")
}
}
then I run grails console and check on the DB after it ends.
...
def p = Person.get(671)
def myService = ctx.myService
println p.name //'John'...from DB
println p.age //25...from DB
myService .testRequiredWithError(p)
println p.name // 'Mark'....correct
println p.age // 15....UNEXPECTED..
//same result checking on the DB after console ends and transaction flushes
I tried to use Propagation.NESTED after activating it in the bootstrap by transactionManager.setNestedTransactionAllowed(true)and use savepoints like in this post
grails transaction set savepoint
but still got same result.
What am I missing????
Thank you in advance.
I'm a little late to the party but in case you haven't found your answer I think I know why you're having issues with transactions.
I looked at your link to the discussion about your issues with savepoints. According to that discussion you are using MySQL as your datasource. MySQL does not support transactions by default and in order to make it do so, you need to create your tables in a special way. I have provided a link below that explains how you should create your tables in MySQL when you want to use transactions:
http://www.tutorialspoint.com/mysql/mysql-transactions.htm
EDIT: This article suggests setting the type of your table to InnoDB in order to support transactions. Here is an example of what that looks like:
mysql> create table tcount_tbl
-> (
-> tutorial_author varchar(40) NOT NULL,
-> tutorial_count INT
-> ) TYPE=InnoDB;
It might be worth noting that this is not the only type that supports transaction but is the most common. MySQL 5.5 and greater should create tables as type InnoDB automatically.
Hope this helps!
I'd test for p.isAttached() as transaction rollback detaches the domain object from the Hibernate session. Also in the test I'd reload the domain object from database - effectively p = Person.get(671) again to reload data from database.
I think the reason the age property is set to 15 after the test is that after the exception the domain object and the database are out of sync (and the domain object is detached).
For more see: https://weblogs.java.net/blog/blog/davidvc/archive2007/04/jpa_and_rollbac.html
On a Grails 2.1.0 I am trying to dynamically updating a field on a domain class. The object gets binded and it looks fine, until the save method is called, which throws the following exception:
java.lang.IllegalStateException: Cannot make an immutable entity modifiable.
try {
def bindParams = [:]
bindParams."$paramsFieldName" = "$paramsValue"
def domainClass = grailsApplication.domainClasses.find { it.clazz.simpleName == paramsDomain }.clazz
def objectInstance = domainClass.findById(paramsId)
objectInstance."$paramsFieldName" = "$paramsValue"
bindData(objectInstance, bindParams)
objectInstance.save(flush:true ,failOnError:false)
return objectInstance
}
catch (Exception ex) {
log.error ex
return null
}
I tried to bind the field using direct assigment and worked well.
objectInstance."$paramsFieldName" = convertToType( fieldType.name,paramsValue)
but then I need to handle the type conversion for each case (I assume). What I need is the BindDynamicMethod handles the binding for me. What happens to the object when binding it using the BindDynamicMethod that makes is immutable?. Or what am I doing wrong that is causing it?
=========================================================
PARTIALLY SOLVED
It turned out that this was happening on some of the domains, but some that were using cache on their mapping was throwing this exception.
class UploadSettings {
String profile = "default"
String displayName
String name
String value
String defaultValue
static mapping = {
//cache usage:'read-only'
}
}
So I guess now my question is if a domain is using cache , why cant we update its value? Or how can we do that? Is there a way to capture if the domain is immutable?
Thanks
Yes by setting it to read-only you are making the object immutable as the error says, IMHO this is misleading as we are in the context of caching but there is some rationale behind this.
If you need caching at the domain level then setting it to read-write should do the trick
See cache usages
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)