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
Objects
Object 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 mechanism
commit 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
Mapping
Object identity
Database mapper
Materialization and dematerialization
Caches
Transaction state of object
Transaction operations
Lazy materialization
Virtual proxies
The Representing
Objects as Tables
pattern
How do you map an object to a record
or relational database schema?
The Representing Objects as Tables
pattern
Manufacturer
name
city
...
...
MANUFACTURER TABLE
: Manufacturer
name = Now&Zen
city = Mumbai
name
city
Now&Zen
Mumbai
Celestial
Shortening
San Ramon
UML Data Modeling
Profile
aggregate signifies a referential constraint: a ProductDescription
row can't exist without a related Manufacturer row
«Table»
Manufacturer
«PK» OID : char(16)
Name : varchar(100)
City : varchar(50)
*
1
PK - primary key
FK - foreign key
«Table»
ProductDescription
«PK» OID : char(16)
Description : varchar(100)
...
«FK» Manu_OID : char(16)
对关系的存储设计
对泛化的存储设计
Method 1
Method 2a
Method 2b
object identifier
(OID)pattern
MANUFACTURER TABLE
Manufacturer
city
name
oid : OID
...
: Manufacturer
city = Mumbai
name = Now&Zen
oid = xyz123
OID
name
city
xyz123
Now&Zen
Mumbai
abc345
Celestial
Shortening
San Ramon
...
This is a simplified design.
In reality, the OID may be
placed in a Proxy class.
primary key
Accessing a Persistence
Service with a Facade
1
1
: DBProductsAdapter
: PersistenceFacade
PersistenceFacade
...
getInstance() : PersistenceFacade
pd = get(...)
get( OID, Class ) : Object
put( OID, Object )
...
// 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 pattern
Database 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) );
UML notation
: This is a qualified assocation
1 . There is a
1 -M association from PersistenceFacade to IMapper objects
2 . With a key of type Class
. It means :
, an IMapper is found
.
(e .g ., via a HashMap lookup
)
1
PersistenceFacade
«interface»
IMapper
1
getInstance
Class
() : PersistenceFacade
get (OID ) : Object
put ( OID , Object
...
get ( OID , Class ) : Object
put ( OID , Object )
)
...
ProductSpecification
RDBMapper
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
ProductSpecification
FlatFileMapper
...
...
RDBMapper
...
get ( OID ) : Object
put ( OID , Object
Manufacturer
...
get ( OID ) : Object
)
put ( OID , Object
get ( OID ) : Object
)
put ( OID , Object
...
each mapper gets and puts objects in its own unique way
depending on the kind of data store and format
...
,
)
Template Method
Pattern
// this is the template method
// its algorithm is the unvarying part
HOLLYWOOD PRINCIPLE:
Don't call us, we'll call you
public void update()
{
clearBackground();
Note that the MyExcellentButton--repaint method is
called from the inherited superclass update
method. This is typical in plugging into a
framework class.
// this is the hook method
// it is the varying part
repaint();
GUIComponent
FRAMEWORK class
}
hook method
update()
template method
- varying part
- overriden in subclass
-may be abstract, or have
a default implementation
repaint()
hook method
hook method overriden
MyExcellentButton
- fills in the varying part of
the algorithm
repaint()
OUR class
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
«interface»
IMapper
get (OID ) : Object
put ( OID , Object )
...
// template method
public final Object get
{
obj := cachedObjects
( OID oid
)
.get (oid );
if (obj == null )
{
Abstract
PersistenceMapper
// hook method
obj = getObjectFromStorage
cachedObjects
( oid );
.put ( oid , obj );
TEMPLATE
+ get ( OID ) : Object
{leaf }
}
return obj
}
;
# getObjectFromStorage
...
(OID ) : Object
{abstract }
HOOK
How to use the Framework
// template method
public final Object get( OID oid )
{
obj := cachedObjects.get(oid);
if (obj == null )
{
// hook method
obj = getObjectFromStorage( oid );
cachedObjects.put( oid, obj )
IMapper
Abstract
PersistenceMapper
+ get( OID) : Object {leaf}
}
return obj
}
# getObjectFromStorage(OID) : Object {abstract}
...
// hook method override
protected Object getObjectFromStorage( OID oid )
{
String key = oid.toString();
dbRec = SQL execution result of:
"Select * from PROD_DESC where key =" + key
ProductDescription
RDBMapper
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;
}
# getObjectFromStorage(OID) : Object
Further factoring out the varying and
unvarying parts of the algorithm.
Final Framework
NextGen Persistence
ProductDescription
RDBMapper
ProductDescription
FileWithXMLMapper
+ ProductDescriptionRDBMapper
(tableName )
# getObjectFromRecord
(OID , DBRecord ) : Object
Sale
RDBMapper
...
# getObjectFromRecord
# getObjectFromStorage
(OID ) : Object
ProductDescription
InMemoryTestDataMapper
(OID , DBRecord ) : Object
# getObjectFromStorage
(OID ) : Object
Persistence
1
+ PersistenceFacade
getInstance () : PersistenceFacade
1
Class
get ( OID , Class ) : Object
put ( OID , Object )
...
Abstract
RDBMapper
+ AbstractRDBMapper (tableName )
# getObjectFromStorage
(OID ) : Object {leaf }
# getObjectFromRecord
(OID , DBRecord ) : Object
- getDBRecord (OID ) : DBRecord
«interface»
IMapper
get (OID ) : Object
put ( OID , Object )
...
Abstract
PersistenceMapper
+ get ( OID ) : Object {leaf }
# getObjectFromStorage
(OID ) : Object
...
{guarded} means a "synchronized" method; that is,
only 1 thread may execute at a time within the
family of guarded methods of this object.
// Java
public final synchronized Object get( OID oid )
{ ... }
Abstract
PersistenceMapper
IMapper
+ get( OID) : Object {leaf, guarded}
...
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( );
}
// template method
public final Object get( OID oid )
{
obj := cachedObjects.get(oid);
if (obj == null )
{
// hook method
obj = getObjectFromStorage( oid );
cachedObjects.put( oid, obj )
IMapper
Abstract
PersistenceMapper
+ get( OID) : Object {leaf}
}
return obj
}
# getObjectFromStorage(OID) : Object {abstract}
...
// hook method override
protected Object getObjectFromStorage( OID oid )
{
String key = oid.toString();
dbRec = SQL execution result of:
"Select * from PROD_DESC where key =" + key
ProductDescription
RDBMapper
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;
}
# getObjectFromStorage(OID) : Object
class ProductSpecificationRDBMapper extends …{
// hook method override
protected 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.
[ from DB ]
State chart : PersistentObject
[new (not from DB )]
New
commit / insert
OldClean
save
rollback / reload
commit / update
OldDirty
delete
Legend :
New --newly created ; not in DB
Old--retrieved from DB
Clean --unmodified
Dirty --modified
delete
OldDelete
rollback / reload
Deleted
commit / delete
Domain
ProductDescription
...
Persistence
PersistentObject
oid : OID
timeStamp:
DateTime
commit()
delete()
rollback()
save()
...
GoF State pattern
Context/Problem
An 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?
Solution
Create 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.
{ state.delete( this ) }
{ state.rollback( this ) }
{ state.commit( this ) }
{ state.save( this ) }
PersistentObject
PObjectState
oid : OID
state : PObjectState
{
commit()
delete()
rollback()
save()
setState(PObjectState)
...
*
1
commit(obj : PersistentObject)
delete(obj : PersistentObject)
rollback(obj : PersistentObject)
save(obj : PersistentObject)
OldDirty
State
Product
Specification
// default no-op
// bodies for
// each method
OldClean
State
}
New
State
OldDelete
State
Sale
...
...
...
...
{ // commit
PersistenceFacade.getInstance().update( obj )
obj.setState( OldCleanState.getInstance() ) }
{ // rollback
commit(...)
delete(...)
rollback(...)
delete(...)
save(...)
commit(...)
commit(...)
rollback(...)
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 B
update A ("001","05001"),
Command
Context/Problem
How to handle requests or tasks that
need functions such as sorting
(prioritizing), queueing, delaying,
logging, or undoing?
Solution
Make each task a class that implements
a common interface
actions become objects, and thus can
be sorted, logged, queued, and so forth.
{
sort()
for each ICommand cmd
cmd.execute()
}
Transaction
commands : List
commit()
addDelete(obj:PersistentObject )
addInsert( obj:PersistentObject )
addUpdate ( obj:PersistentObject )
sort()
...
use SortStrategy objects to allow
different sort algorithms to order the
Commands
«interface»
ICommand
execute( )
undo()
DBCommand
PersistentObject
object : PersistentObject
commands .add( new DBUpdateCommand (obj) );
perhaps simply
object.commit()
but each Command can
perform its own unique
actions
1..*
undo is a no-op fo
this example , but
more complex
solution adds a
polymorphic undo
to each subclass
which uniquely
knows how to und
an operation
DBUpdateCommand
execute()
execute() {abstract}
undo() {leaf}
DBInsertCommand
execute()
1
commit()
...
DBDeleteCommand
execute()
Lazy Materialization
with a Virtual Proxy
actually references an
instance of
ManufacturerProxy
PersistentObject
oid
...
ProductSpecification
1
manufacturer : IManufacturer
...
1
getAddress()
...
getManufacturerAddress() : Address
{
return manufacturer.getAddress()
}
«interface»
IManufacturer
Manufacturer
Proxy
1
Manufacturer
realSubject : IManufacturer
- getRealSubject() : IManufacturer
+ getAddress()
...
{
if ( realSubject == null )
realSubject = PersistenceFacade.get(oid, Manufacturer.class);
return realSubject;
}
3
1
Proxy-for
realSubject
address
getAddress()
...
{
return getRealSubject().getAddress()
}
2
// EAGER MATERIALIZATION OF MANUFACTURER
class 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 MANUFACTURER
ps.setManufacturer( new ManufacturerProxy( manuOID ) );
the Representing Object
Relationships as Tables
one-to-one associations
Place 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 associations
Create 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