designing a persistence framework with patterns
Post on 27-Jan-2016
104 Views
Preview:
DESCRIPTION
TRANSCRIPT
DESIGNING A PERSISTENCE
FRAMEWORK WITH PATTERNS
The Problem: Persistent Objects
persistent object An object that can survive the process or thread
that created it. A persistent object exists until it is explicitly deleted
ProductDescription Storage Mechanisms and Persistent ObjectsObject databases Relational databases others
The Solution: A Persistence Service from a Persistence Framework
The framework should provide functions such as:store and retrieve objects in a persistent
storage mechanismcommit and rollback transactions
persistence framework general-purpose, reusable, and extendable set of
types that provides functionality to support persistent objects
framework A set of collaborating abstract and concrete
classes that may be used as a template to solve a related family of problems. It is usually extended via subclassing for application-specific behavior
Key Ideas
MappingObject identity Database mapper Materialization and dematerialization CachesTransaction state of object Transaction operations Lazy materialization Virtual proxies
The Representing Objects as Tables
patternHow do you map an object to a record or relational database schema?
The Representing Objects as Tables pattern
Manufacturer
namecity...
...
name city
Now&Zen Mumbai
MANUFACTURER TABLE: Manufacturer
name = Now&Zencity = Mumbai
CelestialShortening
San Ramon
UML Data Modeling Profile
«Table»ProductDescription
«PK» OID : char(16)Description : varchar(100) ...«FK» Manu _OID : char(16)
«Table»Manufacturer
«PK» OID : char(16)Name : varchar(100) City : varchar(50)
*1
aggregate signifies a referential constraint: a ProductDescription row can't exist without a related Manufacturer row
PK - primary keyFK - foreign key
对关系的存储设计
对泛化的存储设计
Method 1
Method 2a
Method 2b
object identifier (OID)pattern
OID
xyz123
abc345
This is a simplified design. In reality, the OID may be placed in a Proxy class.
primary key
Manufacturer
citynameoid : OID...
...
name city
Now&Zen Mumbai
MANUFACTURER TABLE
: Manufacturercity = Mumbainame = Now&Zenoid = xyz123
CelestialShortening
San Ramon
Accessing a Persistence Service with a Facade
1PersistenceFacade
...
getInstance() : PersistenceFacade
get( OID, Class ) : Objectput( OID, Object )...
: DBProductsAdapter1
: PersistenceFacade
pd = get(...)
// example use of the facade
OID oid = new OID("XYZ123");ProductDescription pd = (ProductDescription) PersistenceFacade.getInstance().get( oid, ProductDescription.class );
Mapping Objects: Database Mapper or
Database Broker Pattern
Who should be responsible for materialization and dematerialization of objects (for example, a ProductDescription) from a persistent store?
The PersistenceFacade—as true of all facades—does not do the work itself, but delegates requests to subsystem objects.direct mapping
persistent object class itself indirect mapping
Database Broker patternDatabase Mapper pattern
Metadata-Based Mappers
class PersistenceFacade{ / / . . . public Object get( OID oid, Class persistenceClass ) { // an IMapper is keyed by the Class of the persistent object IMapper mapper = (IMapper) mappers.get( persistenceClass ); // delegate return mapper.get( oid ); } //... }usage:
(Manufacturer) PersistenceFacade.getInstance(). get( manuOID, Manufacturer.class) );
each mapper gets and puts objects in its own unique way ,
depending on the kind of data store and format
1
PersistenceFacade
getInstance () : PersistenceFacade
get ( OID , Class ) : Object
put ( OID , Object )
...
ProductSpecification
RDBMapper
...
get ( OID ) : Object
put ( OID , Object )
...
ProductSpecification
FlatFileMapper
...
get ( OID ) : Object
put ( OID , Object )
...
Manufacturer
RDBMapper
...
get ( OID ) : Object
put ( OID , Object )
...
note that the Class as a
parameter is no longer
needed in this version of
get , as the class is
"hardwired " for a particular
persistent type
1
«interface»
IMapper
get (OID ) : Object
put ( OID , Object )
...
Class
UML notation : This is a qualified assocation . It means :
1 . There is a 1 -M association from PersistenceFacade to IMapper objects .
2 . With a key of type Class , an IMapper is found (e .g ., via a HashMap lookup )
Template Method Pattern
GUIComponent
update()
repaint()
MyExcellentButton
repaint()
// this is the template method// its algorithm is the unvarying part
public void update(){ clearBackground();
// this is the hook method // it is the varying part repaint();}
hook method
- varying part- overriden in subclass-may be abstract, or have a default implementation
hook method overriden
- fills in the varying part of the algorithm
HOLLYWOOD PRINCIPLE: Don't call us, we'll call you
Note that the MyExcellentButton--repaint method is called from the inherited superclass update method. This is typical in plugging into a framework class.
FRAMEWORK class
OUR class
template method
hook method
Framework Design with the Template Method Pattern if (object in cache) return it else create the object from its representation in
storage save object in cache return it
Abstract
PersistenceMapper
+ get ( OID ) : Object { leaf }
# getObjectFromStorage (OID ) : Object {abstract }
...
«interface»
IMapper
get (OID ) : Object
put ( OID , Object )
...
// template method
public final Object get ( OID oid )
{
obj := cachedObjects .get (oid );
if (obj == null )
{
// hook method
obj = getObjectFromStorage ( oid );
cachedObjects .put ( oid , obj );
}
return obj ;
} HOOK
TEMPLATE
How to use the Framework
ProductDescriptionRDBMapper
# getObjectFromStorage(OID) : Object
AbstractPersistenceMapper
+ get( OID) : Object {leaf}
# getObjectFromStorage(OID) : Object {abstract}...
// template methodpublic final Object get( OID oid ){obj := cachedObjects.get(oid); if (obj == null ) { // hook method obj = getObjectFromStorage( oid );
cachedObjects.put( oid, obj ) }return obj}
// hook method overrideprotected Object getObjectFromStorage( OID oid ){String key = oid.toString();dbRec = SQL execution result of: "Select * from PROD_DESC where key =" + key
ProductDescription pd = new ProductDescription();pd.setOID( oid );pd.setPrice( dbRec.getColumn("PRICE") );pd.setItemID( dbRec.getColumn("ITEM_ID") );pd.setDescrip( dbRec.getColumn("DESC") );
return pd;}
IMapper
Further factoring out the varying and unvarying parts of the algorithm.
Final Framework
1
«interface»
IMapper
get (OID ) : Object
put ( OID , Object )...
Class
1+ PersistenceFacade
getInstance () : PersistenceFacade
get ( OID , Class ) : Objectput ( OID , Object )
...
Abstract
PersistenceMapper
+ get ( OID ) : Object {leaf }
# getObjectFromStorage (OID ) : Object ...
AbstractRDBMapper
+ AbstractRDBMapper (tableName )
# getObjectFromStorage (OID ) : Object {leaf }
# getObjectFromRecord (OID , DBRecord ) : Object
- getDBRecord (OID ) : DBRecord
Persistence
NextGen Persistence
ProductDescription
RDBMapper
+ ProductDescriptionRDBMapper (tableName )# getObjectFromRecord (OID , DBRecord ) : Object
ProductDescription
FileWithXMLMapper
# getObjectFromStorage (OID ) : Object
Sale
RDBMapper
...
# getObjectFromRecord (OID , DBRecord ) : Object
ProductDescription
InMemoryTestDataMapper
# getObjectFromStorage (OID ) : Object
AbstractPersistenceMapper
+ get( OID) : Object {leaf, guarded}...
// Javapublic final synchronized Object get( OID oid ){ ... }
{guarded} means a "synchronized" method; that is, only 1 thread may execute at a time within the family of guarded methods of this object.
IMapper
Configuring Mappers
class MapperFactory
{
public IMapper getProductSpecificationMapper(){...}
public IMapper getSaleMapper() {...}
}
class MapperFactory{
public Map getAllMappers( ) {...}}
class PersistenceFacade{ private java.util.Map mappers = MapperFactory.getlnstance( ).getAllMappers( ); }
ProductDescriptionRDBMapper
# getObjectFromStorage(OID) : Object
AbstractPersistenceMapper
+ get( OID) : Object {leaf}
# getObjectFromStorage(OID) : Object {abstract}...
// template methodpublic final Object get( OID oid ){obj := cachedObjects.get(oid); if (obj == null ) { // hook method obj = getObjectFromStorage( oid );
cachedObjects.put( oid, obj ) }return obj}
// hook method overrideprotected Object getObjectFromStorage( OID oid ){String key = oid.toString();dbRec = SQL execution result of: "Select * from PROD_DESC where key =" + key
ProductDescription pd = new ProductDescription();pd.setOID( oid );pd.setPrice( dbRec.getColumn("PRICE") );pd.setItemID( dbRec.getColumn("ITEM_ID") );pd.setDescrip( dbRec.getColumn("DESC") );
return pd;}
IMapper
class ProductSpecificationRDBMapper extends …{// hook method overrideprotected Object getObjectFromStorage( OID oid ){
String key = oid.toString();dbRec = SQL execution result of: "Select * from PROD_SPEC where key =" +
key
ProductSpecification ps = new ProductSpecification();
ps.setOID( oid );ps.setPrice( dbRec.getColumn("PRICE") );ps.setItemID( dbRec.getColumn("ITEM_ID") );ps.setDescrip( dbRec.getColumn("DESC") );return ps;
}}
class RDBOperations{ public ResultSet getProductDescriptionData( OID oid ) {...} public ResultSet getSaleData( OID oid ) {...} ...}
class ProductDescriptionRDBMapper extends AbstractPersistenceMapper{
protected Object getObjectFromStorage( OID oid ){ ResultSet rs = RDBOperations.getInstance().getProductDescriptionData( oid );
ProductDescription ps = new ProductDescription(); ps.setPrice( rs.getDouble( "PRICE" ) ); ps.setOID( oid ); return ps;}
Pattern: Cache Management
to maintain materialized objects in a local cache to improve performance (materialization is relatively slow) and support transaction management operations such as a commit. When objects are materialized, they are placed in the cache, with their OID as the key. Subsequent requests to the mapper for an object will cause the mapper to first search the cache, thus avoiding unnecessary materialization
Transactional States and the State Pattern
Persistent objects can be inserted, deleted, or modified.
Operating on a persistent object (for example, modifying it) does not cause an immediate database update; rather, an explicit commit operation must be performed.
OldClean OldDirty
OldDelete
commit / delete
delete
New
[ from DB ]
[new (not from DB )]
save
commit / update
delete
rollback / reload
rollback / reloadcommit / insert
State chart : PersistentObject
Legend :
New--newly created ; not in DB
Old--retrieved from DB
Clean --unmodified
Dirty --modified
Deleted
PersistenceDomain
ProductDescription
...
PersistentObject
oid : OIDtimeStamp: DateTime
commit()delete()rollback()save()...
GoF State pattern
Context/ProblemAn object's behavior is dependent on its state, and its methods contain case logic reflecting conditional state-dependent actions. Is there an alternative to conditional logic?SolutionCreate state classes for each state, implementing a common interface. Delegate state-dependent operations from the context object to its current state object. Ensure the context object always points to a state object reflecting its current state.
PersistentObject
oid : OIDstate : PObjectState
commit()delete()rollback()save()setState(PObjectState)...
PObjectState
commit(obj : PersistentObject)delete(obj : PersistentObject)rollback(obj : PersistentObject)save(obj : PersistentObject)
OldDirtyState
commit(...)delete(...)rollback(...)
1
OldCleanState
delete(...)save(...)
NewState
commit(...)
OldDeleteState
commit(...)rollback(...)
ProductSpecification
...
...
Sale
...
...
*
{ state.delete( this ) }
{ // default no-op // bodies for // each method }
{ // deleteobj.setState( OldDeleteState.getInstance() ) }
{ // saveobj.setState( OldDirtyState.getInstance() ) }
{ // rollbackPersistenceFacade.getInstance().reload( obj )obj.setState( OldCleanState.getInstance() ) }
{ // commitPersistenceFacade.getInstance().update( obj )obj.setState( OldCleanState.getInstance() ) }
{ state.rollback( this ) } { state.commit( this ) }{ state.save( this ) }
{ // commitPersistenceFacade.getInstance().insert( obj )obj.setState( OldCleanState.getInstance() ) }
{ // commitPersistenceFacade.getInstance().delete( obj )obj.setState( DeletedState.getInstance() ) }
Designing a Transaction with the Command Pattern
Ordering the database tasks
Table A: caseNo StudentNo
Health
Table B: StudentNo StudentName
Inseart a record (“05001”,”wang”) to Bupdate A ("001","05001"),
Command
Context/ProblemHow to handle requests or tasks that need functions such as sorting (prioritizing), queueing, delaying, logging, or undoing?SolutionMake each task a class that implements a common interface
actions become objects, and thus can be sorted, logged, queued, and so forth.
«interface»ICommand
execute( )undo()
DBInsertCommand
execute()
DBUpdateCommand
execute()
DBDeleteCommand
execute()
Transaction
commands : List
commit()addDelete(obj: PersistentObject)addInsert( obj: PersistentObject)addUpdate( obj: PersistentObject)sort()...
1..*
DBCommand
object : PersistentObject
execute() { abstract}undo() {leaf}
undo is a no-op for this example, but a more complex solution adds a polymorphic undo to each subclass which uniquely knows how to undo an operation
PersistentObject
commit()...1{
commands.add( new DBUpdateCommand(obj) );}
use SortStrategy objects to allow different sort algorithms to order the
Commands
perhaps simply object. commit()but each Command can perform its own unique actions
{sort()for each ICommand cmd
cmd.execute() }
Lazy Materialization with a Virtual Proxy
ManufacturerProxy
realSubject : IManufacturer
- getRealSubject() : IManufacturer
+ getAddress()...
Manufacturer
address
getAddress()...
«interface»IManufacturer
getAddress()...
Proxy-for 1
realSubject
{return getRealSubject().getAddress()}
ProductSpecification
manufacturer : IManufacturer...
getManufacturerAddress() : Address
1
{if ( realSubject == null ) realSubject = PersistenceFacade.get(oid, Manufacturer.class);return realSubject;}
PersistentObject
oid
...
1
{return manufacturer.getAddress()}
actually references an instance of ManufacturerProxy
1
23
// EAGER MATERIALIZATION OF MANUFACTURERclass ProductSpecificationRDBMapper extends AbstractPersistenceMapper{ protected Object getObjectFromStorage( OID oid ){ ResultSet rs =
RDBOperations.getlnstance().getProductSpecificationData( oid ); ProductSpecification ps = new ProductSpecification(); ps.setPrice( rs.getDouble( "PRICE" ) );
// here's the essence of it String manufacturerForeignKey = rs.getString( "MANU_OID" ); OID manuOID = new OID( manufacturerForeignKey );
ps.setManufacturer((Manufacturer) PersistenceFacade.getInstance(). get(manuOID,
Manufacturer.class) );
// or LAZY MATERIALIZATION OF MANUFACTURERps.setManufacturer( new ManufacturerProxy( manuOID ) );
the Representing Object Relationships as Tables
one-to-one associationsPlace an OID foreign key in one or both
tables representing the objects in relationship.
Or, create an associative table that records the OIDs of each object in relationship.
one-to-many associations, such as a collection
many-to-many associationsCreate an associative table that records
the OIDs of each object in relationship.
Unresolved Issues
• dematerializing objects Briefly, the mappers must define putObjectToStorage.
methods. Dematerializing composition hierarchies requires collaboration
between multiple mappers and the maintenance of associative tables (if an RDB is used).
• materialization and dematerialization of collections• queries for groups of objects• thorough transaction handling• error handling when a database operation fails• multiuser access and locking strategies• security—controlling access to the database
top related