Do I need a double save() after a domain modification using afterInsert()? - grails

I have a domain class that modifies one of its properties in the afterInsert event.
A small example:
class Transaction {
Long transactionId
static constraints = {
transactionId nullable: true
}
def afterInsert() {
// copy the record id to transactionId;
transactionId = id
}
}
Whenever I save the domain object (transaction.save(flush: true)) in
my unit tests, all is well, and the transactionId is updated. But when I try to find the saved record using Transaction.findByTransactionId(), I get no results:
// do something
transaction.save(flush: true)
Transaction transaction = Transaction.findByTransactionId(1)
// !! no results; transaction == null
And I have to do a double save() before I can find the record using findByTransactionId():
// do something
transaction.save(flush: true)
transaction.save(flush: true)
Transaction transaction = Transaction.findByTransactionId(1)
// !! it works....
The double save() seems awkward. Any suggestions on how to eliminate the need for it?

The call to save() will return the persisted entity if validation passes, so there isn’t any reason to look it up separately afterwards. I think that your problem is that you’re re-instantiating the transaction variable (using that same name). If you must look it up (I don’t suggest doing so), call it something else. Also, the 1 id that you’re looking up may not exist if the column is an AUTO-INCREMENT.
def a = a.save(flush: true)
a?.refresh() // for afterInsert()
Transaction b = (a == null) ? null : Transaction.findByTransactionId(a.id)
// (Why look it up? You already have it.)
Update:
Because you’re using afterInsert(), Hibernate may not realize that it needs to refresh the object. Try using the refresh() method after you call save().

This small piece of code makes it obviously work:
def afterInsert() {
transactionId = id
save() // we need to call save to persist the changens made to the object
}
So calling save in the afterInsert is needed to persist the changes made in afterInsert!

Related

Grails: Does the addTo() method not work on newly created objects?

I'm using Grails 2.5.5, and I have a simple scenario here: A has many Bs.
And so I have a transactional service method which basically does the following:.
A a
if (isNew) {
a = new A()
} else {
a = A.findById(someId)
}
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)
The interesting thing is that if I create a new object (as in, isNew is true in the above logic), then I get the following exception when save() is called: reassociated object has dirty collection reference (or an array).
However, if I get an object which already exists in the DB, then everything works perfectly.
The workaround I found is that if I save the new object before adding Bs to A, then things work. But I would rather not have to call save() twice, and the code is just a lot cleaner if the call was just at the end.
I've googled the exception but nothing seems to explain what's going on here.
Can somebody help out with this?
Like others have said in the comments, you need to save the object so it exists in the database, and has an id. When you have a A has many Bs relationship, a new table in the database (something like a_b) is created to map A.id to B.id, which is why you can't add Bs to A without saving first.
A a
if (isNew) {
a = new A()
a.save()
} else {
a = A.findById(someId)
}
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)
Use findOrSaveBy for such an operations. You will get the proper object from db or persist new one:
def a = A.findOrSaveByField(field)
List<B> bList = getBsFromSomeOtherMethod()
bList.each { a.addToBs(it) }
a.save(failOnError: true)

I need to add a collection value to database . Which is the efficient way to do it?

Do I need to pass a class objects to the Model method and process it one at a time?
Eg.
public async Task<int> SaveCollectionValues(Foo foo)
{
....
//Parameters
MySqlParameter prmID = new MySqlParameter("pID", MySqlDbType.Int32);
prmID.Value = foo.ID;
sqlCommand.Parameters.Add(prmID);
....
}
(OR)
2. Shall I pass the Collection value to the Model method and use foreach to iterate through the collection
public async Task<int> SaveCollectionValues(FooCollection foo)
{
....
//Parameters
foreach(Foo obj in foo)
{
MySqlParameter prmID = new MySqlParameter("pID", MySqlDbType.Int32);
prmID.Value = foo.ID;
sqlCommand.Parameters.Add(prmID);
....
}
....
}
I just need to know which of the above mentioned method would be efficient to use?
Efficient is a bit relative here since you didn't specify which database. Bulk insert might change from one to another DB. SQL Server, for instance, uses BCP, while MySQL has a way to disable some internals while sending many insert/update commands.
Apart from that, if you're submitting a single collection at once and that should be handled as a single transaction, than the best option, from both code organization and SQL optimization, is to use both connection sharing and a single transaction object, as follows:
public void DoSomething(FooCollection collection)
{
using(var db = GetMyDatabase())
{
db.Open();
var transaction = db.BeginTransaction();
foreach(var foo in collection)
{
if (!DoSomething(foo, db, transaction))
{ transaction.Rollback(); break; }
}
}
}
public bool DoSomething(Foo foo, IDbConnection db, IDbTransaction transaction)
{
try
{
// create your command (use a helper?)
// set your command connection to db
// execute your command (don't forget to pass the transaction object)
// return true if it's ok (eg: ExecuteNonQuery > 0)
// return false it it's not ok
}
catch
{
return false;
// this might not work 100% fine for you.
// I'm not logging nor re-throwing the exception, I'm just getting rid of it.
// The idea is to return false because it was not ok.
// You can also return the exception through "out" parameters.
}
}
This way you have a clean code: one method that handles the entire collection and one that handles each value.
Also, although you're submitting each value, you're using a single transaction. Besides of a single commit (better performance), if one fails, the entire collection fails, leaving no garbage behind.
If you don't really need all that transaction stuff, just don't create the transaction and remove it from the second method. Keep a single connection since that will avoid resources overuse and connection overhead.
Also, as a general rule, I like to say: "Never open too many connections at once, specially when you can open a single one. Never forget to close and dispose a connection unless you're using connection poolling and know exactly how that works".

Saving an array of new entities via breeze context "saveChanges"

If I have an entity that contains a 1:n collection of another entity then what is the correct way to save the collection?
I've got to this (code not finalised/checked yet):
return datacontext.savechanges([parentEntity]).then(function() {
for (var i=0;i < childArray.length;i++) {
var newChildEntity;
return datacontext.makeNewChildEntity(newChildEntity).then(function() {
newChildEntity.parentID(parentEntity().id());
...
//set other newChildEntity properties
...
return datacontext.savechanges([newChildEntity]).then(function() {
//set finished flag and exit function...
}
}
}
}
"datacontext" is an async module with various exposed methods including creating a new childEntity.
Whereas I haven't tested this yet, it kind of logically works for me, but am I right to be looping around in the createEntity/modifyEntity/saveEntity loop for each new child object I want to add to the collection of childEntities? Is this the only way to do it or is there a way of doing all childEntities in one hit?
The "parentID" is an identity field - store generated so I have to wait for the intitial parent save to finish before I can use the returned ID.
Edited to add: I don't think I need to have a "then" on the end of the async newChildEntity save, do I? I need it to be async still so it can go off and loop through multiple childEntities quickly as the dependent bit is the id from the parent record that's already generated. All the child records share the same parentID so I can set off multiple saves without waiting for the saveChanges method to respond, right?
Breeze's EntityManager.saveChanges can save any number of entities in a single call, which is substantially more performant than trying to call saveChanges once per entity.
Also, I'm not sure why your makeNewChildEntity needs to be asynchronous, Breeze's EntityManager.createEntity itself is synchronous, so I think that all you need to do is something like this.
return datacontext.savechanges([parentEntity]).then(function() {
var listOfEntities = [parentEntity];
for (var i=0;i < childArray.length;i++) {
// synchronous makeNewChildEntity
var newChildEntity = datacontext.makeNewChildEntity();
newChildEntity.parentID(parentEntity().id());
...
//set other newChildEntity properties
...
listOfEntities.push(newChildEntity);
}
// alternatively use datacontext.savechanges(listOfEntities).then(...)
return datacontext.savechanges().then(function() {
//set finished flag and exit function...
}
}
}

Grails isDirty() not working with associations

in my app, I'm updating an object Voucher which has 1:1 association to Patient entity. In my controller, I call "voucherInstance.properties = params" to bind the new values. But when I change the Patient in Voucher (not saving it yet), and then I call isDirty('patient'), which IMO should return true in this case, it actually returns false.
Also, the getPersistentValue('patient') returns the changed value, not the original one. Do I undestand these methods correctly?
Thanks,
Lojza
In my controller class:
def update() {
Voucher voucherInstance = voucherService.get(id)
voucherInstance.properties = params // patient is being sent from view by params.patient.id
voucherService.update(voucherInstance)
}
In my VoucherService class:
public Voucher update(Voucher voucher) {
if (voucher.isDirty('patient')) { // returns false
// do something
Patient oldPatient = voucher.getPersistentValue('patient') // returns the updated patient
}
voucher.save(flush: true)
}
The correct use here should be voucherInstance.patient.isDirty. The parameterized version of isDirty is meant for bean fields iirc.
I did some more googling and found one solution, though not a good one: http://stuff4j.blogspot.com/2011/05/i-encountered-few-times-strange.html
def update() {
Voucher voucherInstance = voucherService.get(id)
voucherInstance.patient = null
voucherInstance.properties = params // patient is being sent from view by params.patient.id
voucherService.update(voucherInstance)
}
This seems to work. But I have to explicitly set all associations to null before I can update them.

Transaction when saving many object in Grails service

I am having a problem with transaction in Grails. I want to save a list of object to DB by a checking condition at each object. All these process I want to put to one transaction, it means if the k-th object does not satisfied the checking condition, all previous objects (from the first object to the (k-1)th one) will be rolled back from DB. Here is my example:
static transactional = true
public void saveManyPeople() {
// ...
List<People> peoples = new ArraysList();
for(i = 0, i < n, i++) {
People newPeople = createPeopleFromRawData(); // return a people object in memory
if(<checking-condition>) {
newPeople.save(flush : false)
} else {
throw new MyCustomizedException() // MyCustomizedException has extended from RuntimException
}
}
// ...
}
As you may see, I set transactional variable to true and I've tried to use flush : true and flush : false, but it didn't work as I want. I've read this article Rolling back a transaction in a Grails Service
And the author recommended that the service method should throw a RuntimeException then the process will be rollbacked. But if I want to throw another exception, so what I have to do?
Could you please give me some suggestions on this problem?
Thank you so much!
You can throw any exception that extends from RuntimeException to rollback the transaction. Or you can use Programmatic Transactions, using withTransation, to have more control over the transaction.
Could you verify that saveManyPeople() is within a Service and not a Controller?
The static transactional = true isn't respected in a Controller. I am suspecting that this is the issue.
If you need to have transactional support with the controller, you could always use DomainClass.withTransaction. Reference Documentation
Example:
Account.withTransaction { status ->
def source = Account.get(params.from)
def dest = Account.get(params.to)
def amount = params.amount.toInteger()
if(source.active) {
source.balance -= amount
if(dest.active) {
dest.amount += amount
}
else {
status.setRollbackOnly()
}
}
}

Resources