Modern application programs are mostly written in object-oriented languages like Java and the business objects (also known as domain model objects) used in those applications are typically stored (persisted) in an SQL relational database. However, integrating such applications with a relational database is a big programming challenge because that entails bridging the gap between the object-oriented world and the relational world which are conceptually different; mixing Java programming with SQL relational language is hard; manually writing the verbose mapping logic between the object-oriented and relational artifacts is tedious and time-consuming. Using Object Relational Mapping (ORM) products has become a preferred way to simplify the development efforts of integrating object-oriented applications with relational databases.
Software Tree has developed innovative and flexible ORM products – JDX for Java, NJDX for .NET, and JDXA for Android. These products are based on some well though-out KISS Principles for ORM detailed here.
JDXA is a simple, non-intrusive, and comprehensive ORM product for the Android platform. JDXA can easily handle complex object models and lets you map them flexibly to a (SQLite) relational database. In this blog, we will discuss the INLINE mapping feature of the JDXA product, which provides the flexibility of optionally storing the attribute values of a contained (referenced) object in the same database table as the one used for storing the attribute values of the containing (referencing) object. This INLINE mapping capability also has the welcome side effect of increasing the efficiency of data access operations, as explained below. Further, JDXA provides the facility of filtering top-level containing objects based on the attribute values of the corresponding INLINEd objects.
N.b. The INLINE feature is also available in the NJDX and JDX products. We may use the product names JDXA and JDX interchangeably in the rest of the article.
For the purpose of explaining the INLINE mapping feature of JDXA ORM, let’s consider the following two object model classes – Employee and Address – where an Address object is referenced by an Employee object in a one-to-one relationship. Segregating an Address class from an Employee class may be desirable from a program design perspective, since these classes represent two different kinds of business entities. Let’s assume that an Address object cannot exist without a referencing Employee object.
The above model classes can be defined in Java as follows. Here, the code for accessor (getters/setters) methods and other constructors has been omitted for brevity.
public class Employee {
private String id;
private String name;
private String title;
private Address address;
/**
* Default no-arg constructor needed for JDX
*/
public Employee () {
}
}
public class Address {
private String addr1;
private String addr2;
private String city;
private String state;
private String zip;
private String country;
/**
* Default no-arg constructor needed for JDX
*/
public Address() {
}
}
As per the above class definitions, an Address object from an Employee object is referenced by a non-primitive (complex) attribute named address. Normally, the attribute values of an Employee object will be stored in a row of a different database table than the attribute values of the corresponding Address object and these table rows would need to be linked through a “foreign key” column (attribute) value. But, since we know the semantics of their relationship, that is, an Address object cannot have an existence independent of an Employee object, we may, for the reason of efficiency, want to store the attribute values of both an Employee and the corresponding Address object together in a single record of one database table. Essentially, as shown below, we want to INLINE the storage of Address attribute values with the storage of the corresponding Employee attribute values.
With such an aggregated table layout, the number of database trips to store/retrieve/delete an Employee object along with its related Address object will be reduced in half, as only one table would need to be accessed to get/set/remove the attribute values of the objects of both classes.
As explained below, JDXA ORM provides a simple and flexible mechanism for declaratively defining the INLINE mapping of a contained class. Here is an example of the JDXA mapping specification for the Employee and Address model classes defined above.
CLASS Employee
PRIMARY_KEY id
RELATIONSHIP address REFERENCES Address INLINE AUTO_INSTANTIATE
SQLMAP FOR address.addr2 COLUMN_NAME county NULLABLE
;
CLASS Address
PRIMARY_KEY addr1
;
JDXA ORM Specification
As you can see, the declarative ORM specification is very simple and succinct. Most of the mapping information is automatically derived by JDX based on some reasonable conventions. For example, a table name is assumed to be the same as the class name and a column name is assumed to be the same as an attribute name unless it is explicitly overridden.
With respect to INLINE mapping of a complex attribute, it is very easy to specify this in the context of the mapping specification for the containing class. As shown above, just mention the keyword INLINE while defining the RELATIONSHIP specification for the address attribute in the mapping specification for the class Employee.
The INLINE keyword means that the attributes of a contained object (e.g., an Address object referenced by the complex attribute address) can be stored in the columns of the same table to which the containing class (e.g., Employee) is mapped. This does not require the use of foreign key attributes. The default column name of an INLINEd attribute is logically patterned like (<complexAttribName>_<attribName>). For example, the default column name of the state attribute in the Employee table would be address_state.
You can also specify SQL column mapping for an INLINEd attribute by using its name with dot notation. For example, the SQLMAP specification for address.addr2 above within the mapping specification of the class Employee means that the addr2 attribute value of the contained Address object may be null. As a result, the corresponding column should be declared NULLable in the database table. The SQLMAP also specifies a non-default column name of “county” for the addr2 attribute.
Based on the above mapping specification, a database table Employee will be created by JDXA with the following column names:
id, name, title, address_addr1, county, address_city, address_state, address_zip, address_country.
Even though the corresponding column name may be different than the dot notation attribute name, the application logic just needs to use the object-oriented pattern of accessing/specifying an attribute, and the JDX ORM layer performs the appropriate translation for accessing the underlying column name. In other words, an application program is totally shielded from the underlying relational schema for the object model.
If AUTO_INSTANTIATE is specified, then during query, JDX instantiates an object of the class Address with its default constructor and assigns it to the INLINE complex attribute address of the containing (Employee) object. If AUTO_INSTANTIATE is not specified, the INLINE complex attribute should be instantiated and initialized in the default constructor of the containing class.
Note that you may not always want to use the INLINE feature because storing attribute values of a contained object in a separate table gives you the ability to query and analyze just the contained objects without the overhead of involving the attribute values of the containing objects.
Here are some code snippets, with embedded comments, exemplifying the power of JDX APIs in terms of handling INLINE relationship mapping described above:
String employeeClassName = Employee.class.getName();
// First delete all existing employees from the database.
// This will also delete the corresponding Address objects.
jdxHelper.delete2(employeeClassName, null);
// Create and insert Employee objects along with the associated
// Address objects.
// Because the address attribute of the Employee class is
// declared as an INLINE attribute in the mapping specification,
// the values of the attributes of an Address object will be
// stored in the same row of the same table where the attributes
// of the containing Employee object are stored.
Address addr1 = new Address(
“111 Main Street”, null, “New York”, “NY”, “10001”, “USA”);
Employee emp1 = new Employee (“E1”, “John Smith”, “Manager”, addr1);
jdxHelper.insert(emp1, true);
// Now create and insert multiple Employee objects along with their
// associated Address objects in one call to JDX.
ArrayList <Employee> employees = new ArrayList<Employee>();
Address addr2 = new Address(
“1-13 St Giles High”, null, “London”, “WC-2H”, “8AG”, “UK”);
Address emp2 = new Address (“E2”, “Mark Clinton”, “Engineer”, addr2);
employees.add(emp2);
Address addr3 = new Address(
“333 City Blvd”, “Apt 33”, “San Francisco”, “CA”, “94103”, “USA”);
Employee emp3 = new Employee (“E3”, “Bob Brown”, “Consultant”, addr3);
employees.add(emp3);
jdxHelper.insert(employees, true);
// Retrieve all the Employee objects
List queryResults = jdxHelper.getObjects(employeeClassName, null);
// Retrieve an Employee (E2) object using its id
Employee emp = (Employee) jdxHelper.getObjectById(
employeeClassName, “empId=E2”, false, null);
// Retrieve all the Employee objects whose country is USA.
// Notice the use of a path expression (address.country='USA')
// in the predicate below.
queryResults = jdxHelper.getObjects(
employeeClassName, "address.country='USA'");
In summary, JDXA ORM supports flexible and optimized storage of INLINE attributes in the same table as the attributes of the containing class, resulting in improved database operation performance in many situations.
JDXA also supports a notion of EMBEDDED complex attributes, whereby all the attributes of the contained object are serialized and put into a blob column of the table to which the containing class is mapped. This may provide an optimized way to store a referenced object if the attributes of the referenced object do not need to be used in a query predicate.
Learn more about the KISS ORMs, JDX and JDXA, including how to get free trial versions, by visiting Software Tree’s website at https://www.softwaretree.com.