FirstSQL/J Native Object/Relational Wrappers 

 

{Lee Fesperman, FirstSQL,  Dec 2, 2002} Object/Relational wrappers are a tool for bridging the gap between object-oriented applications and database systems. O/R wrappers encapsulate database access in application-oriented wrapper objects. Applications use these special objects for all access to database entities.


There are a number of tools for generating O/R wrappers, including the Java Data Objects (JDO) initiative. In practice however, wrapper objects are often coded by the application developer. With the advent of Relational DBMSs supporting user-defined objects in the database, this task has become much easier. Creation of wrapper objects can take place in the DBMS before passing them to the client.


Why Use O/R Wrappers


Much has been made of the so-called impedance mismatch between object-oriented applications and traditional Database Management Systems (DBMSs). However, this conflict is quite natural. It is a reflection of the differing goals of the two sub-systems.


A DBMS is a resource shared among diverse client applications:

It is the goal of a DBMS to provide effective services to all of its clients. A DBMS is also concerned with the overall integrity and security of the database.


A client application is oriented to performing or participating in a specific business task/process.


Object/Relational wrappers provide an excellent solution to the apparent conflict between the needs of the application and the needs of the DBMS. They allow the application to utilize the database in an application-specific manner. On the other side, they allow the DBMS to maintain a shared data structure that can service a rich set of application clients. This is accomplished through building wrappers that are specific to each application.


Application-specific wrappers support a concept important to both object-oriented and relational systems – a sub-system should have access to data only if relevant to its needs. In object-oriented terminology, this is known as
Encapsulation.


The alternative of creating an O/R wrapper that services multiple applications is a daunting task. Such
universal wrapper classes, that attempt to provide services to multiple applications, have two seemingly conflicting problems:

O/R wrappers oriented to individual applications are lightweight objects. This makes them easy to change as the needs of their client application changes.


Native O/R Wrappers Using Objects in the Database

An Object/Relational DBMS (ORDBMS) is a relational DBMS that supports cataloging of user-defined object classes in the database. ORDBMSs can directly create and manipulate objects of the user-defined classes, and they can return them to client software. Class level methods can function as Stored Procedures.

With these capabilities, Object/Relational DBMSs allow creation of wrapper object within the DBMS. They return these objects as result values to the client.


Building O/R Wrappers Using Stored Procedures

It is feasible to build simple object wrappers directly in a query using an ORDBMS. This is described in the next section. For more complicated objects, a stored procedure is the best choice for building wrapper objects.


Object-oriented stored procedures have the right capabilities to create complex wrapper objects. They have direct access to the DBMS for data retrieval (they run in the DBMS itself.) Their native object-oriented features allow them to easily create a network of related objects.


For a simplified example, we will describe creation of a wrapper object for an individual sales order. There are two associated tables in the database:

Primary keys are underlined.


The primary wrapper class is SalesOrder. A SalesOrder object contains information from a row in the sales_orders table plus a collection of LineDetail objects. The secondary class – LineDetail, contains information from a row in the line_details table.


The stored procedure to create a complete SalesOrder object is named retrieveOrder(). For convenience, we will place it as a class-level method in the SalesOrder class. Such class-level methods are also called
Factory methods because they manufacture objects.


The retrieveOrder() stored procedure receives a single argument – the id of the order to be retrieved. It returns a SalesOrder object as its result.


A pseudo-code version of retrieveOrder():

For expository purposes, retrieveOrder() uses a separate query for each different type of object created. The stored procedure could have used a single query to accomplish its purpose. The general case would use separate queries.


The application code to retrieve a SalesOrder wrapper for an order and its detail lines is just 5 lines in Java. Listing 1 shows sample Java code for client retrieval of a wrapper.


Listing 2 contains the SalesOrder class declaration in Java code. It includes the retrieveOrder() factory method used as a stored procedure. Listing 3 contains the class declaration in Java for the LineDetail class.

 Building O/R Wrappers Using SQL Queries

It is also possible to create an O/R wrapper using a SQL query, eliminating the need to develop a stored procedure. It requires an ORDBMS supporting user-defined classes in its catalog. In most cases, the result of a query should be a single wrapper object, but a query can generate a set of interconnected objects. Wrapper objects that include lists of secondary objects, like orders and detail lines, customers and invoices, etc., would need to use a stored procedure, as described above.


As an example of a query producing wrapper objects, we will use a wrapper class – InvoiceWrapper, for wrapping customer invoices. InvoiceWrapper is a user-defined database class, cataloged in the ORDBMS.  It has a constructor that receives the invoice #, customer #, invoice balance and invoice date.


The ORDBMS provides a NEW operator for creating objects for database classes. Using NEW, a query can dynamically create a wrapper object and return it as a result to the client. For example,

 

SELECT NEW InvoiceWrapper(inv_id, cust_id, inv_bal, inv_date)

FROM invoices

WHERE inv_id = 1384;

 

The result of this query is an InvoiceWrapper object containing the details about invoice #1384.

 Changing the query to:

 

SELECT NEW InvoiceWrapper(inv_id, cust_id, inv_bal, inv_date)

FROM invoices

WHERE inv_date = CURRENT_DATE;

 

will return a set of InvoiceWrapper objects representing today’s invoices.


 
Wrapper Agents Using SQL Queries

 Most ORDBMSs also allow a database object instantiated on the client to be passed to the database using ? parameters in SQL statements. This capability is useful for creating database agents. A database agent is an object designed for a round-trip (from the client to the ORDBMS and back to the client).


 A database agent would be an object created on the client with application-specific intelligence (data and methods). The client passes the agent object to a SQL query and receives the same object back as the result of the query. The result object has updated itself based on application-specific information and on database contents.


 A round-trip object uses a specific type of method to accomplish this feat. The method must use the object itself as its return value. There are no other restrictions on the method.


Using the InvoiceWrapper class from the previous section, we could define a method with the following Java signature:

 

InvoiceWrapper retrieve(int invId, int custId, BigDecimal invBal, Date invDate)

 The retrieve method would be used in a SQL query as follows:

 

SELECT (CAST(? AS InvoiceWrapper)).retrieve(inv_id, cust_id, inv_bal, inv_date)

FROM invoices

WHERE inv_id = 1384

 

The client would create an InvoiceWrapper object and pass it for the ? parameter in the SQL statement. It would then retrieve the updated InvoiceWrapper object as the result of the query.


 
Conclusion

Application-specific O/R wrappers are an excellent solution for bridging the gap between applications and DBMSs. They allow the application to utilize the database in an object-oriented manner without changing the database structure to meet the needs of individual applications.

 


Modern Java ORDBMSs (Object/Relational Database Systems) make creation and use of O/R wrappers much easier. Java ORDBMSs allow the wrapper class to be cataloged in the database and wrapper objects to be created using SQL statements


 

Listing 1: Sample O/R Wrapper Retrieve for Client

 

// client code to retrieve O/R wrapper from ORDBMS

CallableStatement call = conn.prepareCall("{?=CALL SalesOrder.retrieveOrder(?)}");

call.setInt(2, 2451);

call.execute();

call.registerOutParameter(1, Types.OTHER, "SalesOrder");

SalesOrder order = (SalesOrder) call.getObject(1);

 

 

Listing 2: O/R Wrapper SalesOrder Class

 

// SalesOrder.java -- O/R wrapper class for Sales Order

 

public class SalesOrder implements Serializable

{

  private int ordId, custId;

  private Date ordDate;

  private SortedMap lines = new TreeMap();

  public SalesOrder(int ordId, int custId, Date ordDate)

  {

    this.ordId = ordId;

    this.custId = custId;

    this.ordDate = ordDate;

  }

  public int getOrdId()

  {

    return ordId;

  }

  public int getCustId()

  {

    return custId;

  }

  public Date getOrdDate()

  {

    return ordDate;

  }

  // retrieve line detail by line number

  public LineDetail getLineDetail(int lineNo)

  {

    return (LineDetail) lines.get(new Integer(lineNo));

  }

  // get an ordered list of detail lines (LineDetail objects)

  public Iterator lineDetails()

  {

    return lines.values().iterator();

  }

  // add a detail line to this order

  public void addLine(LineDetail line)

  {

    lines.put(new Integer(line.getLineNo()), line);

  }

 

  // Stored Procedure to create a SalesOrder O/R Wrapper

  public static SalesOrder retrieveOrder(int ordId)

  {

    SalesOrder order = null;

    try

    {

      // Database is builtin class for retrieving an internal DBMS connection

      Connection conn = Database.getConnection();

      PreparedStatement prep;

      ResultSet results;

      // Retrieve information for sales order

      prep = conn.prepareStatement("SELECT cust_id, ord_date " +

                                   "FROM sales_orders " +

                                   "WHERE ord_id = ?");

      prep.setInt(1, ordId);

      results = prep.executeQuery();

      if (results.next())

      {

        int custId = results.getInt(1);

        Date ordDate = results.getDate(2);

        results.close();

        prep.close();

        order = new SalesOrder(ordId, custId, ordDate);

        // Retrieve information for detail lines in sales order

        prep = conn.prepareStatement("SELECT line_no, product, qty, " +

                                     "       price, taxable " +

                                     "FROM line_details " +

                                     "WHERE ord_id = ?");

        prep.setInt(1, ordId);

        results = prep.executeQuery();

        while (results.next())

        {

          int lineNo = results.getInt(1);

          String product = results.getString(2);

          int qty = results.getInt(3);

          BigDecimal price = results.getBigDecimal(4);

          boolean taxable = results.getBoolean(5);

          order.addLine(new LineDetail(ordId, lineNo, product, qty, price,

                                       taxable));

        }

      }

      results.close();

      prep.close();

    }

    catch (SQLException ex)

    {

      order = null;

    }

    return order;

  }

}

 

 

Listing 3: O/R Wrapper LineDetail Class

 

// LineDetail.java -- O/R wrapper class for Sales Order Detail Line

 

public class LineDetail implements Serializable

{

  private int ordId, lineNo;

  private String product;

  private int qty;

  private BigDecimal price;

  private boolean taxable;

  public LineDetail(int ordId, int lineNo, String product, int qty,

                    BigDecimal price, boolean taxable)

  {

    this.ordId = ordId;

    this.lineNo = lineNo;

    this.product = product;

    this.qty = qty;

    this.price = price;

    this.taxable = taxable;

  }

  public int getOrdId()

  {

    return ordId;

  }

  public int getLineNo()

  {

    return lineNo;

  }

  public String getProduct()

  {

    return product;

  }

  public int getQty()

  {

    return qty;

  }

  public BigDecimal getPrice()

  {

    return price;

  }

  public boolean getTaxable()

  {

    return taxable;

  }

}

 

 

Copyright 2002 FirstSQL, Inc.