This section starts with the basic steps that are required to access information in the database using the persistence layer. It begins by discussing PDL and Data Objects. It then discusses how to create the database schema and how it can be represented in PDL. Finally, it covers using the properties defined in the PDL to access the database in your Java code.
The persistence layer can be looked at as a way to access objects within the database. Examples of objects that can be stored in the database are Users, Groups, Articles, Images, and Email Addresses. In order to abstract out the information regarding object storage within the database, the persistence layer has implemented the concept of a single Data Object. Data Objects are used by Java classes to handle all interaction with the database. Because this class is provided, other classes do not need to know how to create, retrieve, update, or delete a given object. Data Objects are defined by their Attributes and Object Key.
Every Data Object is associated with a single Object Type. Every object type, in turn, is associated with events and operations that specify how information is stored in the database and how information in the database is mapped to Java variables. The role of the PDL file (see Section 9.2.3.1 The PDL File) is to provide developers with a mechanism to easily specify that object type.
When defining your persistence layer, you need to work through two aspects of your design:
A UML model of your Data Objects.
A database schema to handle the storage of the Data Objects.
Some designers may feel more comfortable starting with a database design and then designing the objects that use the schema, or vice versa. You may begin with either one, but both should be designed as part of the persistence layer. The UML model is typically designed for the Data Objects using a UML modeling tool.
This tutorial will use a hypothetical database schema composed of publications, magazines, articles, paragraphs, and authors. This goal of this tutorial is to help you better understand how this technology can be used with WAF. In practice, the entire data model below will be automatically generated by the persistence layer using the metadata defined in the PDL file. Therefore, none of the data model below will have to be created by the developer.
The tutorial will begin by using two simple objects: a Publication and a Magazine. The schema will be expanded throughout the tutorial. To see the full schema that is used throughout the tutorial, please see Appendix D PDL Syntax.
create table publications ( publication_id integer constraint publications_pub_id_nn not null constraint publications_pub_id_pk primary key, type varchar(400) constraint publications_pub_type_nn not null, name varchar(400) constraint publications_pub_name_nn not null, constraint publications_pub_name_un unique(type, name) ); create table magazines ( magazine_id integer constraint magazines_magazine_id_fk references publications constraint magazines_magazine_id_pk primary key, issue_number varchar(30) ); |
A PDL file is a text file that has a .pdl extension and can be parsed using the PDL grammar (Appendix D PDL Syntax). It is made up of a model declaration followed by block declarations. These block declarations can be object type definitions, association blocks, Dara Query blocks, and Data Operation blocks.
The only required item is the Model declaration, as this declaration informs the compiler of which namespace to use. It is common to have one for all DataQuery and DataOperation definitions and one file for each Object Type. However, it is possible to combine all Object Type definitions into a single file. It is also possible to include DataQuery and DataOperations in their own files or in-line with Object Type Definitions. Data Associations are normally found with one of the Object Type definitions used within the association.
You can find a list of PDL reserved words, as well as the PDL grammar, in Appendix D PDL Syntax.
When creating a PDL file, the first line of the file must be the name of the model that defines the namespace for the block definitions in the PDL file. This is similar to the name of the package in a Java class and is necessary to avoid name collisions of object types that have the same name and which exist in different PDL files. This example will use the tutorial model.
The second block of lines within a PDL file can either be a list of namespaces to import (similar to Java's import statement) or the declaration of the object type itself. See Section 9.2.6 Object Type Inheritance for more information about importing.
The object type definition follows the import statement. The definition of an object type may include attributes and event definitions. The Publication object type defined below has two attributes defined. One of these attributes, id, is also part of the object key, which means that a Publication is uniquely identified by the id attribute value.
The first block of code within the object type definition is a list of persistence attributes and mappings of those attributes to database columns for the given object. Note that the attribute names do not need to be the same as the column names. The attributes are a list of Java variables that a given Data Object class can access. Finally, notices that in addition to the java data type at the beginning of the attribute mapping the SQL data type is also present. This SQL data type allows for DDL generator to generate the correct SQL. For a complete list of supported Java types, see Section D.2.2 PDL Attribute Types
// declare the namespace as "tutorial" using the "model" keyword model tutorial; // there are not any import statements because Publication does not // extend anything. // next comes the "object type" keyword followed by the name of // the object type object type Publication { // the first block of code within the object type is a set of // mappings from the Attribute Java type and Attribute name to // the database column to which they correspond. BigDecimal id = publications.publication_id INTEGER; String name = publications.name VARCHAR(400); } |
For each object type, you must define an object identifier for the purposes of uniquely identifying object instances at run time. If the object type does not have a super type, the object key syntax is used. If the object has a super type, a reference key must be declared to indicate how this object joins with the supertype. (See Section 9.2.6 Object Type Inheritance for more details.) The following example indicates how to designate that the Publication's id attribute is the object identifier or key:
model tutorial; object type Publication { BigDecimal id = publications.publication_id INTEGER; String name = publications.name VARCHAR(400); String type = publications.name VARCHAR(400); object key (id); } |
This object type definition almost models the SQL that we have defined above but it is missing any mention of the unique constraint. To allow for this and to allow the DDL generator to correctly generate unique constraints the persistence layer allows you to specify either a single property or a set of properties as being unique. The syntax for this is as follows:
SINGLE UNIQUE PROPERTY: object type User { BigDecimal id = users.id INTEGER; unique String[0..1] screenName = users.screen_name VARCHAR(100); ... } SET OF UNIQUE PROPERTIES: object type Node { BigDecimal id = nodes.id INTEGER; Node[0..1] parent = join nodes.parent_id to nodes.id; String[1..1] name = nodes.name VARCHAR(100); object key (id); unique (parent, name); } |
Therefore, in order to correctly model the publications, we need to add the constraint. The correct, full publications object type defintion is below:
model tutorial; object type Publication { BigDecimal id = publications.publication_id INTEGER; String name = publications.name VARCHAR(400); String type = publications.name VARCHAR(400); object key (id); unique (name, type); } |
Note | |
---|---|
Attributes cannot have the same name as a pdl reserved word without special quoting. For a list of reserved words and escaping techniques, see Section D.2 PDL Reserved Words. |
Now that we know how to specify an Object Type using PDL, you can learn how to access the information from Java. The first step is to examine the public API that is provided by the persistence system.
The persistence layer in the Web Application Framework is implemented in the Java package (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/package-summary.html). This package contains a set of classes and interfaces for working with persistent objects that store themselves in a relational database. Some of the URLs listed contain a visual break with the "\" character for printing formatting.
SessionManager — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ SessionManager.html.
This class is responsible for initializing the Session class. Users of persistence will use it to get a pointer to the Session class through SessionManager.getSession() (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ SessionManager.html#getSession()).
Session — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/Session.html
All persistence operations take place within the context of a session. The Session object contains methods for creating and retrieving data objects, data queries, and data operations.
DataObject — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/DataObject.html
This interface defines the public methods for Data Objects; see Data Object. Data Objects are normally accessed through the use of DomainObjects (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/domain/DomainObject.html).
OID — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/OID.html
This class represents a unique identifier for a given Data Object. It typically holds the information in the object key (Object Key) of the object type. For the WAF Object type, the OID is the combination of the "acs object type" (e.g., com.arsdigita.User) and the "object id". This is typically used to retrieve specific objects from the database.
DataQuery — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/DataQuery.html
This class represents an interface for retrieving arbitrary information from the database. It is the Java equivalent of the PDL data query described in Section 9.4.1 Data Queries.
DataOperation — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataOperation.html
This class represents an interface for executing arbitrary DML within the database. It is the Java equivalent of the PDL data operation described in Section 9.4.2 Data Operations.
DataCollection — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataCollection.html
This class is used to represent a collection of data objects. DataCollections can be used to efficiently iterate over a large set of DataObjects and access the values of their properties. These can be filtered in a manner similar to DataQueries.
DataAssociation — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataAssociation.html
This class is similar to the Java Collection interface, in that it provides methods that act on the entire set of associations, such as add(DataObject object) (http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataAssociation.html#add(com.arsdigita.persistence.DataObject)), and methods that return other objects to access the associations, such as cursor()(http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataAssociation.html#cursor()).
DataAssociationCursor — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ DataAssociationCursor.html
Like a Java Iterator, this is used to loop over a set of associations. Developers can also add Filters to DataAssociationCursors, thereby restricting the objects returned to the desired subset.
Filter — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/Filter.html
This class is used to filter the results of associations and collections. For instance, if a developer only wants the associated objects with a name starting with "l" then a filter is used.
PersistenceException — http://rhea.redhat.com/doc/waf/6.0/api/com/arsdigita/persistence/\ PersistenceException.html
This is an unchecked exception that is thrown whenever there is an error within the persistence system. It typically contains an error message as well as some debugging information.
Now that you have written your PDL files and your database is loaded, the next step is to create Data Objects to access the information in the database. The main access point to Data Objects is through the Session class. Session objects allow you to instantiate data objects that are part of a logical client session. Specifically, there are three methods that allow you to create and retrieve objects as well as a method allowing you to delete an object.
Suppose you have defined a Publication and want to create a new DataObject so that you can store information about it. The first step is to use the Session.create(String typeName) method. You can pass in the fully qualified name of the Object Type and get a Data Object back that corresponds to the defined events. For instance, to create a new Publication, you can do the following (remove the "\" and make it all one line):
DataObject publication = SessionManager.getSession().create\ ("tutorial.Publication"); |
If you want to retrieve an object that already exists (and you have the OID), you can use the Session.retrieve(OID oid) method. This is similar to the creation method, except that you are accessing a row that already exists in the database instead of creating a new row (or rows).
If you have an OID and you want to delete the object from the database, Session provides a delete(OID oid) method that allows you to perform the deletion. For most cases, however, you will have the DataObject and can just call the local delete() method.
The above methods show how to retrieve or create a single object within the system. The system also provides the ability to retrieve all objects of a given object type. This capability is provided through the method retrieve(String typeName), located in the Session class.
Suppose you want to print out the names of all of the Publications in the system whose ID is less than 100. One way to do this would be to loop from 1 to 100, create the corresponding OID, and look up each Publication. Another way would be to use a Data Query. However, the simplest way is to take advantage of the retrieve all event that is generated for the Magazine object type. The following example demonstrates this (remove the "\" and make it all one line):
// get all of the Publications in the system. Calling "retrieve" triggers // the "retrieve all" event defined in the PDL file. Note that // the string passed in to the method is the model name (tutorial) // followed by the object type name (Publication) separated by a dot (.). DataCollection pub = SessionManager.getSession().retrieve\ ("tutorial.Publication"); // now we want to filter on the ID of the publication pub.addFilter(pub.getFilterFactory().addLessThan("id", new Integer(100), \ false)); // finally, we can loop through the publications and print out its name // // the query to get the information out of the database is executed // when next() is called for the first time while (pub.next()) { System.out.println(pub.get("name")); } |
DataCollections are also useful when it is necessary to perform certain tasks on a large number of Data Objects. Suppose you want to add the publications to a java.util.Collection that can then be passed around. The following code will create the java.util.Collection (remove the "\" and make it all one line):
DataCollection publication = SessionManager.getSession().retrieve\ ("tutorial.Publication"); Collection collection = new ArrayList(); pub.addFilter(pub.getFilterFactory().addLessThan("id", new Integer(100), \ false)); while (pub.next()) { collection.add(pub.getDataObject()); } |
Note | |
---|---|
Please note the following:
|
Now that you know how to create and access information for a specific object type, the next logical step in Object-Oriented programming is to create an object type that extends another object type. To address this problem, PDL allows object types to inherit attributes from other object types, similar to inheritance in most Object-Oriented programming languages.
The following example shows a "Magazine" object type extending the "Publication" object type. The definition for the "Magazine" is very similar to that of "Publication" in that it has a block of attributes. It is different, however, in that it does not have an object key definition. Rather, it has a reference key which indicates how the table containing the Magazine information can be joined to the table containing the Publication information. If the Magazine tries to define an object key, an error will be thrown.
model tutorial.magazine; // we do not have to import the tutorial model because both object types // are in the same model. However, we show the import statement here as // an example. import tutorial.*; object type Magazine extends Publication { // we need to specify the size of the String attribute so we know // whether it is actually a String or if it is really a Clob String issueNumber = magazines.issue_number VARCHAR(30); // notice that because it extends Publication, there is not an // explicitly "object key" declaration. Rather, there is // a "reference key" declaration and "id" is not defined // as one of the attributes reference key (magazines.magazine_id); } |