I am updating a one to many from the parent domain model in a controller.
The code looks like this:
def update() {
def parentInstance = Parent.get(params.id)
params.childerenDetails.each {
parentInstance.addToChildrens(
newChildren(
name:params.get(name_"${it}"),
age:params.get(age_"${it}"))
}
}
if(parentInstance.validate()) {
parentInstance.save(flush:true)
} else {
render "error found at"+parentInstance.errors
}
}
....here I have custom validation in Parent class when adding the children values getting that parent validation error but children objects are saving into the db ..how to prevent if validation error comes in parent domain
If you want to prevent children to be stored into the DB you need to either move this code into a Service, which is transactional by default, or encapsulate all the code inside a Parent.withTransaction { // }. If you choose to use the service you can throw a RuntimeException when you detect the validation error to force a rollback. If you use the withTransaction, you can manually rollback it with status.setRollbackOnly:
def parentInstance = Parent.get(params.id)
Parent.withTransaction { status ->
params.childerenDetails.each {
parentInstance.addToChildrens(
newChildren(
name:params.get(name_"${it}"),
age:params.get(age_"${it}"))
}
}
if(parentInstance.validate()) {
parentInstance.save(flush:true)
} else {
status.setRollbackOnly()
}
}
Not sure if a parentInstance.save(flush: true, failOnError: true) would trigger a rollback as well.
I would prefer though to move the business logic to a Service. See withTransaction and Services docs in the Ref Guide.
Related
If I declare a method to be non transactional, then I save a record, do some stuff, save another record, will the first save be committed even if the second save fails and throws an exception, no matter what?
If the NonTransactional serivce method is called from another service method which IS transactional, what happens? Does it now become part of the outer transaction, so if SomeOtherdomainObject().save() fails, the first object will be rolled back?
E.g.
#Transactional
class SomeService {
#NotTransactional
def someMethod() {
new SomeDomainObject().save(failOnError:true, flush:true)
// do stuff, possibly throw a RuntimeException
new SomeOtherdomainObject().save(failOnError:true)
// do more stuff, possibly throw a RuntimeException
}
}
Called thusly (in the non transactional calling case):
class SomeControler{
def someService
def someControllerMethod() {
someService.someMethod()
}
}
As far as I am aware:
Yes.
Yes.
Why not set up some integration tests to confirm this (and let us know the results)? There is a good guide here. Note that the test class needs to be made non-transactional in order to test the transactional functionality. The example page I link to uses JUnit, but here is some Spock code that should do what you need.
// MyDomainObject.groovy
class MyDomainObject {
String details
static constraints = {
// this is default anyway, but I want to make it obvious
// not setting details and then calling save will cause an exception if
// save's failOnError is true
details nullable: false
}
}
// MyService.groovy
class MyService {
// only make methods transactional when we explicitly want them to be
static transactional = false
// create 2 objects and save, 1st should save ok and second should fail
def nonTransactionalDoubleSave() {
def objA = new MyDomainObject()
objA.details = "This should save ok"
objA.save(flush: true, failOnError: true)
def objB = new MyDomainObject()
objB.details = null // null by default, but I'm just making the point
objB.save(flush: true, failOnError: true) // this will trigger an exception
}
def nonTransactionalSingleSave() {
def objA = new MyDomainObject()
objA.details = "This should save ok"
objA.save(flush: true, failOnError: true)
}
#Transactional
def transactionalSave() {
nonTransactionalSingleSave() // this should create 1 object
// this should create 2 objects, but the 2nd will trigger an exception and rollback the transaction, meaning there should be no objects in the DB
nonTransactionalDoubleSave()
}
}
import spock.lang.*
import grails.test.spock.*
class MyServiceIntegrationSpec extends IntegrationSpec {
static transactional = false // the test case must not be transactional
def myService = new MyService()
def setup() {
// remove all my domain objects from the database that might be in there
MyDomainObject.where{}.deleteAll()
}
def cleanup() {
// remove all my domain objects from the database that a test may have created
MyDomainObject.where{}.deleteAll()
}
def "Question 1: nonTransactionalDoubleSave should create 1 object only"() {
expect: "a clean database"
MyDomainObject.count() == 0
when: "nonTransactionalDoubleSave is called"
myService.nonTransactionalDoubleSave()
then: "we get an exception but still get one object in the database"
thrown(Exception)
MyDomainObject.count() == 1
def obj = MyDomainObject.list().getAt(0)
obj.details = "This should save ok"
}
def "Question 2: transactionalSave should create no objects"() {
expect: "a clean database"
MyDomainObject.count() == 0
when: "transactionalSave is called"
myService.transactionalSave()
then: "we get an exception and no objects in the database"
thrown(Exception)
MyDomainObject.count() == 0
}
}
I want to call a service inside my grails domain objects beforeDelete() event. Unfortunately it always crashes reproducibly when the event is fired. I built an example that reproduces the problem. The domain object:
class Gallery {
def myService
def beforeDelete() {
// System.out.println(myService); // not even this works, same error!
System.out.println(myService.say());
}
}
The service:
class MyService {
String say() {
"hello"
}
}
The test controller:
class DeleteController {
def index() {
Gallery.list().each {
it.delete()
}
}
def create() {
def gallery = new Gallery()
gallery.save()
}
}
If I start the application and call create followed by index I get:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [testbeforedelete.Gallery#1]
What I want to accomplish is to call my service, which is a bit more complicated than this example. I cannot explain this behavior and I don't know how cope with this. I know that the Hibernate events need special care yet I'm stuck.
The beforeDelete actually makes a change to your domainclass. I agree that you would not expect this behaviour. Hibernate thinks that you are modifying the instance. You could use the follow code to get around your problem
def beforeDelete() {
Gallery.withNewSession {
System.out.println(myService.say());
}
}
I have a need to have a method to return Id in case of success and list of errors in case of fail.
ex code snippet:
def save = {
def errors = []
if(Employee.save(flush:true)){
return Employee.id
}else{
errors.add("Can't be saved")
return errors.
}
}
In Service class
ICalling
Employee.save() - .. so how to check if it is error or id that save method returns
Any suggestions around would be appreciated.
I agree with Burk not to return different types, it can lead to unexpected errors.
Another solution to the problem is using Java's exception handling mechanism. You can add a context field to the Exception which will hold the list of validation errors.After catching the exception you can extract the errors.
void save(Employee employee) {
// do save
// ...
// on error:
def errors = [ "terrible error nr. 5" ]
throw new ValidationException(errors)
}
try {
fooService.save(employee)
} catch(ValidationException e) {
def errors = e.erorrs
// do stuff with the errors
}
An additional advantage: When no validation error is expected, the try-catch block can be ommited in Groovy, which makes the code cleaner because you don't have to care about any validation error fields.
Don't do this - even if you can make it somewhat more usable with Groovy, it's a bad idea. In this case though, there are a few simple solutions. If you're just passing the Employee instance and saving it in the service method, you don't need to return anything:
void save(Employee employee) {
employee.save(flush:true)
}
This is because if it's successful, the id will be set on the instance you passed in, and if not there will be one or more validation errors in the errors property (there's no need for you to return a generic error message when there are actually useful error messages available).
For example this would be the code you'd have in a controller calling the service:
def employee = new Employee(...)
fooService.save(employee)
if (employee.hasErrors()) {
// do something with employee.errors
}
else {
// success - use the id if you need via employee.id
}
If you want to pass in the data to create and save the new instance and return an Employee (this is the approach I usually take), it's similar:
Employee save(String name, int foo, boolean bar, ...) {
Employee employee = new Employee(name: name, foo: foo, bar: bar, ...)
employee.save(flush:true)
return employee
}
In this second case it's important to separate the save call and the return, since if there is a validation error save returns null and you want to always return a non-null instance. So do not do this:
return employee.save(flush:true)
If you separate them you can check the errors and/or the id.
Also, make sure that you do not use closures in services like you have in your code (def save = { ...). Only methods will be transactional since the Spring transaction handling doesn't know about Groovy closures - they're just fields that Groovy calls as if they were methods, but they're not.
I need to make changes to other domain classes when an instance of a particular domain class is deleted. What is the best way to do this? I don't want to wait until commit or flush so I don't think the "beforeDelete" callback will help. I would like to "override" delete, do some stuff and call super.delete():
class Foo {
Bar bar
void delete() {
if (bar) bar.foo = null
super.delete() -- this doesn't work
}
}
Currently I have named "delete" cancel but would like to call it "delete" but then I cannot call the original delete().
To add to what #sbglasius said, here's the link to the docs on GORM events
Complete example:
class Foo {
Bar bar
def beforeDelete() {
if(bar) {
bar.foo = null
}
}
}
I haven't tried overriding GORM methods myself, but this might give some insight on what's involved:
"Overloading" standard GORM CRUD methods
I would put the "delete" logic in a service and call that instead:
class FooService {
def deleteInstance(foo) {
if (foo?.bar) {
foo.bar.foo = null
// might have to call foo.bar.save() here
// then foo.bar = null
}
foo.delete()
}
}
I'm missing something....
I have a Grails webflow that looks like this:-
def childFlow = {
start {
action {
def targets = []
Target.list().each {target ->
targets.add(new TargetCommand(name: target.name, id: target.id))
}
log.debug "targets are $targets"
[children: targets]
}
on('success').to('selectChild')
}
...
TargetCommand is serializable. but I get this error:-
Caused by: java.io.NotSerializableException: com.nerderg.groupie.donate.Target
For some reason the "target" object that is inside the Target.list().each {} closure is getting put into the flow scope, and I can't figure out how to mark it as transient.
I have some code in a Service that has objects placed in the flow scope when I don't want them to too.
How do I stop local transient variables in closures being put in the flow scope?
Refining the above answer instead of clearing the persistenceContext we simply evict the instances as we finish with them, like so:
Target.list().each {
targets.add(new TargetCommand(name: it.name, id: it.id))
flow.persistenceContext.evict(it)
}
This is still a work-around for not being able to mark the closure variables as transient
The answer to my question is:
the flow object is a map that contains a reference to the "persistenceContext" which is a org.hibernate.impl.SessionImpl so the flow tries to store the entire session, even if the objects are not changed (for context I suppose)
this incorrect example from grails 1.1.x doc gives us a clue what to do:
processPurchaseOrder {
action {
def a = flow.address
def p = flow.person
def pd = flow.paymentDetails
def cartItems = flow.cartItems
flow.clear()
def o = new Order(person:p, shippingAddress:a, paymentDetails:pd)
o.invoiceNumber = new Random().nextInt(9999999) cartItems.each { o.addToItems(it) }
o.save()
[order:o] }
on("error").to "confirmPurchase"
on(Exception).to "confirmPurchase"
on("success").to "displayInvoice"
}
The flow.clear() clears the entire flow map including the persistenceContext or the session, which then makes the whole flow fail due to lack of a session.
so the intermediate "solution" is to use the persistenceContext and in this case clear it. So this works:-
def childFlow = {
start {
action {
sponsorService.updateTargetsFromTaggedContent()
def targets = []
Target.list().each {
targets.add(new TargetCommand(name: it.name, id: it.id))
}
flow.persistenceContext.clear()
[children: targets]
}
on('success').to('selectChild')
on(Exception).to 'finish'
}
The obvious problem with this is that the session is cleared completely, instead of just keeping out things I don't want in the flow.
for want of a better way, here is a generalised solution that removes any non Serializable objects from the persistenceContext of the flow. This could be a service method given the flow:-
def remove = []
flow.persistenceContext.getPersistenceContext().getEntitiesByKey().values().each { entity ->
if(!entity instanceof Serializable){
remove.add(entity)
}
}
remove.each {flow.persistenceContext.evict(it)}
If like me you need to evict all maybe you like to do
flow.persistenceContext.flush()
flow.persistenceContext.persistenceContext.clear()