In Grails i want to catch the exception when foreign key constraint appears, this is my code
try {
instance.delete(flush:true)
flash.message = message(code : "default.deleted.message", args : [instance])
flash.level = "info"
} catch(org.springframework.dao.DataIntegrityViolationException | Exception e) {
flash.message = message(code : "default.not.deleted.message", args : [instance])
flash.level = "danger"
}
The problem is when there is a foreign key constraint, it never enters the catch block.
Any idea what exception i should add ?
Thanks,
I am using grails 2.3.5 and following code works for me
try {
user.delete(flush: true)
render "OK"
} catch (org.springframework.dao.DataIntegrityViolationException e) {
render "DataIntegrityViolationException"
} catch (Throwable v) {
render "Throwable"
}
Try this..,.
I think it is because MySQLIntegrityConstraintViolationException is thrown on update or insert not delete
Exception thrown when an attempt to insert or update data results in violation of an
integrity constraint. Note that this is not purely a relational concept; unique primary
keys are required by most database types.
Try with com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException
EDIT
Which version of Grails are you using ?
I found this issue GRAILS-9357
Related
I try to save a duplicate book to my Book domain class for testing. I have a try-catch(Exception ex) to get the error.
BookService bookService
try {
def book = new Book()
book.id = 'Lord of the Flies'
bookSerivce.save(book)
def anotherBook = new Book()
anotherBook.id = 'Lord of the Flies'
bookService.save(anotherBook)
} catch (Exception ex) {
System.out.println(ex.getMessage())
}
I use ex.getMessage(). I am getting this error message. This one is pretty much useless.
could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
On the other hand, I am getting this error message from the command prompt console. This one I can use. It clearly says that I already have a book called 'Lord of the Flies' in the database.
2020-11-02 14:46:39.790 ERROR --- [eduler_Worker-1] o.h.engine.jdbc.spi.SqlExceptionHelper : (conn=665) Duplicate entry 'Lord of the Flies' for key 'PRIMARY'
I understand I should not use the catch-all Exception. I will need to use a more specific Exception class. I already tried catch (SQLException ex) but it did not catch the error. I also tried some others Exception classes but they didn't catch the error either.
It also looks like book.save() and bookSerive.save(book) throw a different Exception.
Would you show me which Exception class I can use in the catch() function to capture the correct error please?
Thanks!
The question and the comments below it aren't especially clear to me but something to investigate is the root cause of the exception:
BookService bookService
try {
def book = new Book()
book.id = 'Lord of the Flies'
bookSerivce.save(book)
def anotherBook = new Book()
anotherBook.id = 'Lord of the Flies'
bookService.save(anotherBook)
} catch (Exception ex) {
System.out.println(ex.getMessage())
Throwable t = ex.getCause()
if(t) {
// interrogate t as the root cause
}
}
I am throwing a custom exception inside the withTransaction method based on a scenarion when author not found.But the issue I am facing is even if the code is enetering inside the exception block for non existent authors, it is not existing out of the flow but continuing with the flow.
Just wanted to check is there anything i am missing here or doing wrong.
Author.withTransaction() {
authStatus -> def author = Author.get(id)
if (!author) {
log.warn "author not found"
throw new NotFoundException('author not found')
}
author.status = 'completed'
author.save()
}
Thanks
Sam
do you really have authStatus -> def author = Author.get(id) all on one line ? or is authStatus -> on withTransaction line, usually a return stops something from continuing but since you are throwing there shouldn't be a need for that. Why not reverse that logic to
if (author) {
do something
return
}
//obviously we didn't have an author since we haven't returned so back to your throw
log.warn "author not found"
throw new NotFoundException('author not found')
I would change that to
Author.withTransaction {
def author = Author.get(id)
if (author) {
author.status = 'completed'
author.save()
return author
}
log.warn "author not found"
throw new NotFoundException('author not found')
}
Personally I would probably wrap entire thing around try catch and not even throw that specific case but instead try to capture get and save errors with one throw at bottom of try catch since you may have got the record but did you manage to save it correctly ?
Does anyone have a simple way of handling this exception when updating a record to one that already exists in the database?
Try this:
catch (UpdateException ex)
{
SqlException innerException = ex.InnerException as SqlException;
if (innerException != null && innerException.Number == ??????)
{
//Place you exception code handling here..
}
else
{
throw; //(bubble up)
}
}
This is a simple solution, but you may have issues in the future should the error number change which is unlikely).
I'm trying to implement exception handling for Optimistic lock type exceptions that are thrown by Hibernate but I've encountered a strange issue. It seems I'm unable to catch any Gorm exceptions.
For example I have this code in my service:
try {
User user = User.get(1);
Thread.sleep(10000);
user.viewedAt(new Date());
user.save(flush:true);
} catch (OptimisticLockingException ex) {
log.error("Optimistic lock exception");
} catch (StaleObjectStateException ex) {
log.error("Optimistic lock exception");
}
When I hit this block with two threads, it blows up and the exception propagates to Grails' standard exception handler. The catch blocks are never invoked even though the reported exception is StaleObjectStateException.
I've noticed that I can catch the exception if I let it propagate to the controller and catch it there, but it seems I can't implement exception handling in the service which is weird.
What am I missing?
I got to the bottom of this and I'm posting it in case anyone else runs into this. The issue occurred because the try/catch block was in a transactional service. Although grails reported that the exception was thrown during the save() call, in reality it was called AFTER the entire method, when the transaction was committed.
So it seems that:
flush: true has no effect on transactional services
It's not possible to catch GORM related exceptions in transactional services, at least not without some work
I finally worked around this by manually managing the transaction myself i.e.
try {
User.withNewTransaction {
User user = User.get(id); // Must reload object
.. // do stuff
user.save(flush:true)
}
} catch (OptimisticLockingException ex) {
...
}
I hope this is of use to someone else!
I spent some time working on this problem and have written a more complete solution to handle the case of an optimistic locking exception in Grails.
Firstly, though the exception reported in the stack trace is StaleObjectStateException, the actual exception that gets thrown is HibernateOptimisticLockingFailureException (not "OptimisticLockingException"). Secondly, if you want to generalize this to handle arbitrary closures which modify domain objects, you need to rethrow exceptions thrown inside the closure.
The following static function will take an object and a closure that operates on the object, save it, and if it fails, retry again until it succeeds:
public static retryUpdate(Object o, Closure c) throws Exception {
def retVal
int retryCount = 0
while (retryCount < 5) {
try {
Model.withTransaction { status ->
retVal = c(status)
o.save()
}
return retVal
} catch (HibernateOptimisticLockingFailureException e) {
log.warn "Stale exception caught saving " + o
if (++retryCount >= 3) { // if retry has failed three times, pause before reloading
Thread.sleep(1000)
}
o.refresh()
} catch (UndeclaredThrowableException e2) {
// rethrow exceptions thrown inside transaction
throw e2.getCause()
}
}
return null
}
Model in this case is any GORM model class, doesn't matter which one. In particular it doesn't matter if it is the class of the passed-in object.
Example of use:
AnotherModelClass object = AnotherModelClass.get(id)
retryUpdate(object) {
object.setField("new value")
}
How to catch duplicate key exceptions in Grails . when trying to save existing integer for a unique column constraint, the error is generating while saving/updating a record .
Also used
try{
object.save(flush:true)
}
catch(org.springframework.dao.DataIntegrityViolationException e){
println e.message
}
catch(org.hibernate.exception.ConstraintViolationException ex){
println e.message
}
catch(Exception e){
println e.message
}
But unable to catch this issue .
23:41:13,265 ERROR [JDBCExceptionReporter:101] Duplicate entry '1' for
key 2 23:41:13,281 ERROR [AbstractFlushingEventListener:324]
Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not
execute JDBC batch update at
org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94)
at
org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at
org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
at
org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
at
org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
at
org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at
org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
Could you please share the solution to solve this .
You're trying to save a record with an id that already exists.
If id is auto-generated, don't set it manually
If id is not auto-generated, set it to something else, for example max(id) + 1
surely no Exception should be thrown for constraint violation, but rather object.save() should return null? i.e.
if(object.save() == null) {
// save failed
} else {
// save succeeded
}
If you defined the uniqueness through a Grails constraint, you have to look for a ValidationException. This is thrown when object.validate() fails; Validation is done before any object.save().
try {
object.save(failOnError: true)
}
catch(ValidationException ve) {
// Do something ...
}
But remember: Any constraint violation, for any member variable can cause a ValidationException ... so you have to distinguish yourself.
Edit:
This applies, if you use the Grails 1.2 failOnError feature ...
I am looking for the same problem so maybe not a complete answer but what you can do is to force validation and look in the errors, identify the case and place the actions you want:
if(instance.validate()) {
//everything ok
} else {
instance.errors.each {
//identify the case and place actions
}
}
Also note that error is: className.propertyName.unique
Possibly it should work:
import org.springframework.dao.DuplicateKeyException
try {
domainInstance.save(flush: true)
} catch(DuplicateKeyException e) {
// ...
}