From 76bfc68872006e9c3b5b74d041d31b71e141b255 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 1 Feb 2006 23:20:01 +0000 Subject: [PATCH] dev on uow docs --- doc/build/content/unitofwork.myt | 118 +++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/doc/build/content/unitofwork.myt b/doc/build/content/unitofwork.myt index 69f1886f69..d3ddb2b366 100644 --- a/doc/build/content/unitofwork.myt +++ b/doc/build/content/unitofwork.myt @@ -15,7 +15,125 @@
  • Thread-local operation. the Identity map as well as the Unit of work itself are normally instantiated and accessed in a manner that is local to the current thread. Another concurrently executing thread will therefore have its own Identity Map/Unit of Work, so unless an application explicitly shares objects between threads, the operation of the object relational mapping is automatically threadsafe. Unit of Work objects can also be constructed manually to allow any user-defined scoping.
  • + <&|doclib.myt:item, name="getting", description="Accessing UnitOfWork Instances" &> +

    To get a hold of the current unit of work, its available inside a thread local registry object (an instance of sqlalchemy.util.ScopedRegistry) in the objectstore package:

    + <&|formatting.myt:code&> + u = objectstore.uow() + +

    You can also construct your own UnitOfWork object. However, to get your mappers to talk to it, it has to be placed in the current thread-local scope:

    + <&|formatting.myt:code&> + u = objectstore.UnitOfWork() + objectstore.uow.set(u) + +

    Whatever unit of work is present in the registry can be cleared out, which will create a new one upon the next access:

    + <&|formatting.myt:code&> + objectstore.uow.clear() + +

    The uow attribute also can be made to use "application" scope, instead of "thread" scope, meaning all threads will access the same instance of UnitOfWork:

    + <&|formatting.myt:code&> + objectstore.uow.defaultscope = 'application' + +

    Although theres not much advantage to doing so, and also would make mapper usage not thread safe.

    + +

    The objectstore package includes many module-level methods which all operate upon the current UnitOfWork object. These include begin(), commit(), clear(), delete(), has_key(), and import_instance(), which are described below.

    + <&|doclib.myt:item, name="begincommit", description="Begin/Commit" &> +

    The current thread's UnitOfWork object keeps track of objects that are modified. It maintains the following lists:

    + <&|formatting.myt:code&> + # new objects that were just constructed + objectstore.uow().new + + # objects that exist in the database, that were modified + objectstore.uow().dirty + + # objects that have been marked as deleted via objectstore.delete() + objectstore.uow().deleted + +

    To commit the changes stored in those lists, just issue a commit. This can be called via objectstore.uow().commit(), or through the module-level convenience method in the objectstore module:

    + <&|formatting.myt:code&> + objectstore.commit() + +

    The commit operation takes place within a SQL-level transaction, so any failures that occur will roll back the state of everything to before the commit took place.

    +

    When mappers are created for classes, new object construction automatically places objects in the "new" list on the UnitOfWork, and object modifications automatically place objects in the "dirty" list. To mark objects as to be deleted, use the "delete" method on UnitOfWork, or the module level version:

    + <&|formatting.myt:code&> + objectstore.delete(myobj1, myobj2, ...) + + +

    Commit() can also take a list of objects which narrow its scope to looking at just those objects to save:

    + <&|formatting.myt:code&> + objectstore.commit(myobj1, myobj2, ...) + +

    This feature should be used carefully, as it may result in an inconsistent save state between dependent objects (it should manage to locate loaded dependencies and save those also, but it hasnt been tested much).

    + + <&|doclib.myt:item, name="begin", description="Controlling Scope with begin()" &> + +

    The "scope" of the unit of work commit can be controlled further by issuing a begin(). A begin operation constructs a new UnitOfWork object and sets it as the currently used UOW. It maintains a reference to the original UnitOfWork as its "parent", and shares the same "identity map" of objects that have been loaded from the database within the scope of the parent UnitOfWork. However, the "new", "dirty", and "deleted" lists are empty. This has the effect that only changes that take place after the begin() operation get logged to the current UnitOfWork, and therefore those are the only changes that get commit()ted. When the commit is complete, the "begun" UnitOfWork removes itself and places the parent UnitOfWork as the current one again.

    + <&|formatting.myt:code&> + # modify an object + myobj1.foo = "something new" + + # begin an objectstore scope + # this is equivalent to objectstore.uow().begin() + objectstore.begin() + + # modify another object + myobj2.lala = "something new" + + # only 'myobj2' is saved + objectstore.commit() + +

    As always, the actual database transaction begin/commit occurs entirely within the objectstore.commit() operation.

    + +

    Since the begin/commit paradigm works in a stack-based manner, it follows that any level of nesting of begin/commit can be used:

    + <&|formatting.myt:code&> + # start with UOW #1 as the thread-local UnitOfWork + a = Foo() + objectstore.begin() # push UOW #2 on the stack + b = Foo() + objectstore.begin() # push UOW #3 on the stack + c = Foo() + + # saves 'c' + objectstore.commit() # commit UOW #3 + + d = Foo() + + # saves 'b' and 'd' + objectstore.commit() # commit UOW #2 + + # saves 'a', everything else prior to it + objectstore.commit() # commit thread-local UOW #1 + + + <&|doclib.myt:item, name="transactionnesting", description="Nesting UnitOfWork in a Database Transaction" &> +

    The UOW commit operation places its INSERT/UPDATE/DELETE operations within the scope of a database transaction controlled by a SQLEngine: + <&|formatting.myt:code&> + engine.begin() + try: + # run objectstore update operations + except: + engine.rollback() + raise + engine.commit() + +

    If you recall from the <&formatting.myt:link, path="dbengine_transactions"&> section, the engine's begin()/commit() methods support reentrant behavior. This means you can nest begin and commits and only have the outermost begin/commit pair actually take effect (rollbacks however, abort the whole operation at any stage). From this it follows that the UnitOfWork commit operation can be nested within a transaction as well:

    + <&|formatting.myt:code&> + engine.begin() + try: + # perform custom SQL operations + objectstore.commit() + # perform custom SQL operations + except: + engine.rollback() + raise + engine.commit() + + + + + <&|doclib.myt:item, name="identity", description="The Identity Map" &> + + <&|doclib.myt:item, name="import", description="Bringing External Instances into the UnitOfWork" &> <&|doclib.myt:item, name="rollback", description="Rollback" &> -- 2.47.2