The following paragraphs outline design and functionality of the IOPC 2 library predecessors.

The common predecessor of IOPC, IOPC 2 and POLiTe 2 libraries - POLiTe - represents a persistence layer for C++ applications. The library itself is written in C++. Applications incorporate the library by including its header files and by linking its object code. The library offers following features:

POLiTe contains several classes that provide database access. At the time, the library supports the Oracle 7 database and the code uses OCI[11] 7 interface to access it. The classes are accessed via common interface that can be used for implementing other RDBMs to the library.

The interface consists of a set of abstract classes - Database, Connection and Cursor. Communication with the database flows exclusively through this interface and its implementation (OracleDatabase, OracleConnection and OracleCursor). The interface Database provides a logical representation of a database (e.g. an Oracle instance), Database can create one or more connections (Connection) to the database. The Connection interface represents the communication channel with the database. Using the implementations of the Connection interface it is possible to send SQL statements to database and receive responses in the form of cursors (Cursor). The response consists of a set of one or more rows that can be iterated through the Cursor.

Every persistent class maintainable by the POLiTe library has to be described by a set of pre-processor macro calls. These calls are included directly into the class definitions or near them. Description of the class attributes and the necessary mapping information has to be provided together with declaration of every persistent class. Metainformation covers class name, associated database table, parents, all persistent attributes with their data types and corresponding table columns and more. For complete list see [08]. Example 3.1, “Definition of a class in the POLiTe library” displays definition of our classes Person and Student in the POLiTe library.


Because the library needs to keep track of the dirty status of persistent objects, the programmer has to maintain this flag either by himself or better he should restrict manipulation with persistent attributes to the use of getter and setter methods defined by the macros. For every class T described by these macros the library creates an associated template class prototype Proto<T>. The solitary instance of this prototype class holds information about the metamodel described by the macros and provides the actual database mapping. Prototypes are registered within the ClassRegister. Using ClassRegister, the library and/or application can search for prototypes by their names, and access methods needed for CRUD[12] operations.

Persistent classes inherit their behaviour from one of four base classes defined in the library - the Object, ImmutableObject, DatabaseObject or PersistentObject class. Depending on what the parent is, different features of the persistence are supported:

  • Object - instances of descendants of this class can be obtained as database query results. These objects do not have any database identity and can represent results from complex queries containing aggregate functions. More instances can thus be the same.

  • ImmutableObject - instances of this class have a database identity mapped to one or more column(s) in the associated table (or view) and represent concrete rows in database tables or views. They can be loaded repetitively, but the ImmutableObject class descendants still do not propagate changes made to them back to the database. To use this class as a query result, the query has to return rows that match rows in corresponding database tables or views.

  • The DatabaseObject class is much the same as the ImmutableObject, but changes are propagated back to the database.

  • The PersistentObject class offers the most advanced persistence options. The PersistentObject defines and maintains a unique attribute OID that holds the identity of every PersistentObject's instance within the database. Unlike previous classes, persistence of whole type hierarchies is expected and supported.

As mentioned before, the library offers vertical mapping for descendants of the PersistentObject. Tables related to mapped inheritance hierarchies are joined using the surrogate OID key. If using DatabaseObject descendants, the object model can be created upon an existing (and in case of ImmutableObject descendants even upon the read-only) database tables with arbitrary keys. In this case, however, no inheritance between classes is allowed.

Associations in the POLiTe library are not modelled primarily as references but as instances of the Relation class. There are five subclasses of this class - OneToOneRelation, OneToManyRelation, ManyToOneRelation, ManyToManyRelation and ChainedRelation according to cardinality of the association. Their names describe which kind of relation between the underlying tables they manage. ChainedRelation is built from other relations and it can be used to define relation for indirectly associated objects. Example 3.2, “Associations in the POLiTe library” demonstrates how a one-to-many relation between the Employee and Student classes can be created and used.


The relation can be queried for objects on both of its sides. So we may run queries like "Which students are supervised by Ola Nordmann?", "Who is the supervisor of Richard Doe?" or even more complex ones, but that would be out of the scope of this thesis.

The one-to-many relation can be replaced by a reference to s supervisor in the Student class definition:

...
dbPtr(supervisor);
dbString(studcardid);
MAP_BEGIN
mapPtr(supervisor,"SUPERVISOR");
...

Usage of the references is closer to the object-oriented approach in which we navigate using pointers or references to gain access to the related objects. The drawback is, that the navigation is usually one-way and in this case, the retrieval of all supervised students of an employee is not trivial.

Persistent objects can enter one of the following states (see the state diagram in Figure 3.2, “POLiTe persistent object states”):


  • Transient - Each new instance of persistent class enters this state. The instance data are stored only in the application memory and are not persisted.

  • Local copy - A persistent image of the transient instance can be created by calling the BePersistent() method. The method inserts attribute values of the instance to the database. The memory instance can be deallocated at any time as it is considered as a cached copy of the inserted database data. This state can be entered also at a later time when loading a persistent instance which has no local copy in the application memory.

  • Locked local copy - To prevent the local copy deallocation, the local copy can be locked in the application memory. Local copy is not deallocated until its lock is released. After unlocking, the locked local copy enters the local copy state.

  • Persistent instance - A persistent object can enter this state if its local copy is removed from the application memory. During the state transition, the changes in the local copy are usually propagated to the database. The object exists now only in the database; it can be loaded later and enter one of the local copy states.

All local copies and local locked copies are managed by the ObjectBuffer which acts as a trivial object cache. The buffer is implemented as an associative container between object identities and local object copies. If the buffer is full, all non-locked local copies are freed and dirty instances updated in the database.

Because a persistent object can exist in one of those states, library uses indirect references to access the object's attributes. Users do not have to know whether the object is loaded into the object cache or if it exists only in the database. Users can just access it via the Ref<T> reference type using the overloaded -> operator. The library looks for the requested instance in the object cache and if not found, it loads it from the database. C++ chains the operator -> calls until it gets to a type that does not overload the -> operator and there it accesses the requested attribute or calls the requested function. So if the variable e is of the Ref<Employee> type, the following expression:

e->salary(65000);

does not invoke the setter method on the Ref<Employee> instance, but it looks for the object in the object cache, loads it eventually, and invokes the setter method on it. The process is illustrated by the Figure 3.3, “POLiTe references”.


POLiTe allows the users to specify several concurrent data access strategies the ObjectBuffer will use. These strategies are used to influence the safety or speed of concurrent access and cached data coherence.

  • Updating strategy - determines whether changes done to local copies are propagated to the database immediately or they can be deferred.

  • Locking strategy - determines how the rows in the database are locked when they are loaded into local copies. Shared, exclusive or no locking can be requested.

  • Waiting strategy - if the application tries to access a locked database resource (by another session), this strategy specifies whether the application waits until the resource gets unlocked or an exception is thrown.

  • Reading strategy - determines behaviour of the persistence layer if a local copy is accessed using the indirect reference Ref<T>. Local copy can be either used right away or it can be refreshed with the data stored in the database. The refresh option can be speeded up by comparing timestamps of the local copy and of the stored image.

Object manipulation is illustrated by the Example 3.3, “Persistent object manipulation”. Two objects - an employee and a student which is supervised by that employee are created as transient instances and inserted into the database. The BePersistent() call returns references to unlocked local copies of created objects. Then the salary of the employee is modified and the change propagated to the database. In the end, the student object is deleted from the database and also from the memory.


Queries in the POLiTe library search for objects of a specified class. Search criteria restricting the result set can be specified. Queries are represented as instances of the Query class which contains only two data fields: The search criteria (in fact the WHERE clause of the final SELECT statement together with the ORDER BY clause specification) determines what object will be returned and how the result will be ordered. The search criteria can be written using SQL (referencing physical table and column names) or using a C++-like syntax. The C++-like syntax hides the O/R mapping complexity and allows the users to use more convenient class and attribute names. The query objects can be then combined using the C++ !, && and || logical operators. Results of the query execution are accessed using instances of the Result<T> template class. The template is used similarly to the Ref<T> template. Example 3.4, “Queries in the POLiTe library” illustrates how the queries are created, combined and executed.