Update object instances
Usually, updating an object instance is done by setting attributes. Technically, creating or deleting referenced instances might also result in an instance modification, but usually, it does not. Only, when creating the first instance or deleting the last instance from a property, that does not allow asynchronously updates ( PropertyDefinition ::asynchronUpdate() ), the object instance data will be updated.
In order to store an instance after changing one or more attribute values, save() might be called ( explicitly storing data ). Otherwise, the instance will be stored to database, when changing the selection in the property handle referring to the instance ( implicitly storing data ). Storing data implicitly to the database makes programming much easier, but the application has to be tolerant against errors. E.g. most of the ODABA GUI tools are storing data implicitly, since the moment for storing data would require an explicit user action, which makes applications clumsy.
When running in a transaction it is suggested to call save() before committing or rolling back the transaction.
In order to reset changes not yet stored to database, one may call reset() or cancel() .
... fragment ( Property &person ) {
person.get("P0010");
person.value("first_name") = "Mary-Anne";
// ...
person.save();
}
A number of attributes may have been assigned to key definitions as key component. Those keys might be used for creating collection indices. Thus, when changing key component attributes, all key affected have to be updated in all the indexes where those are stored. Since an instance might be element of any number of collections, it is not a rare case, that 10 or 20 collection indexes have to be updated. This is automatically done by the system, while storing the updated instance.
However, keys in an index might be defined as unique and thus, changing a key value may result in a key conflict, because the key value does already exist in an index. In this case the update fails. When using implicit data storing, the error is reported to the error log, but no exception is thrown for the function causing unselecting the updated instance. Thus, the application will not take notice of the problem.
In order to avoid this, save() should be called in order to explicitly store the instance. In this case, errors storing the instance will result in an exception and the application may react on the problem.
Concurrency problems arise, when one and the same instance is going to be updated while being selected in different property handles (different users, different applications, but also different property handles in the same application).
ODABA supports optimistic and pessimistic instance locking in order to avoid those conflicts. Instances are locked (write lock) automatically, when being selected by a property handle, which has been opened with access mode Write ( Property ::accessMode() ). When another property tries to select the instance, it gets the instance read-only and cannot update it. This technique is save, but leads, unfortunately, in some cases to dead locks.
Optimistic locking will be achieved by opening the property handle in Update mode. In this case, several property handle may select the instance and will get update access. When saving the instance, the first property handle will store the instance without problems. The second will fail (error code SDBError 0067), since the instance has been updated by another property handle after being read by the current property handle.
The application may decide now, whether to overwrite changes made by the other property handle, which is - in most cases - not a good idea, or to repeat changes with the updated instance. In order to detect the problem, save() should be called in order to explicitly store the instance. When the problem arises while implicitly storing the instance, the error is written to the error log, but no exception is thrown.
// pessimistic locking (access mode Write)
bool ... fragment ( Property &person ) {
int tries = 10;
bool result = false;
try {
person.get("P0010");
// leave the loop after a number of attepts
while ( tries-- &&!person.canUpdate() ) {
// here, one should wait a little bit
person.reset();
}
if ( person.canUpdate() ) {
person.value("first_name") = "Mary-Anne";
person.save();
result = true;
}
} catch ( ... ) {
result = false;
}
reurn result;
}
// optimistic locking (access mode Update)
bool ... fragment ( Property &person ) {
bool result = true;
bool done = false;
person.get(GetPersonID());
while ( !done ) {
try {
person.value("first_name") = GetNewName();
person.save();
done = true;
} catch ( odaba::Exception e ) {
if ( errorCode() != 67 ) {
done = true;
result = false;
} else // otherwize reset(re-read) instance and repeat changes
person.reset();
} catch ( ... ) {
// ...
}
}
reurn result;
}