The concept of O/R mapping requires three basic building blocks:
There are very different approaches around how to express the descriptor. JPA uses annotations on entity classes, MyBatis uses XML files, and PriDE follows a different approach as you may have seen already from the quick start tutorial. The descriptor is an instance of class pm.pride.RecordDescriptor, i.e. it is code itself. This concept has a few advantages over other approaches.
A coded descriptor needs to go somewhere in your code, of course. PriDE provides two default patterns for the descriptor placement which are obvious when you think of the building blocks mentioned above: descriptors within adapter classes or descriptors within entity classes.
Descriptors in entities is what you know already from the quick start tutorial. It cases the entities to become their own adapters having their own persistence methods. This is a compact pattern which is suitable for small applications. Therefore you will find it spread over most examples provided with PriDE. The disadvantage is the same one mentioned above with JPA: the entity classes spread knowledge about database mapping information all over the code. Combined with persistence capabilities directly incorporated in entity classes, this is a questionable concept in bigger architectures.
Let’s have a look on the more sophisticated pattern of separate adapter classes. You can have a look on the general structure by generating separate classes for the quick start example table. The pure entity class can be generated by the following command:
java
-D... see quick start tutorial
pm.pride.util.generator.EntityGenerator CUSTOMER adapter.CustomerEntity -b > CustomerEntity.java
The parameter -b tells the generator to create only an entity class without descriptor. The result is an ordinary Java bean or POJO class:
package adapter;
public class CustomerEntity implements Cloneable, java.io.Serializable {
private long id;
private String name;
private String firstName;
public long getId() { return id; }
public String getName() { return name; }
public String getFirstName() { return firstName; }
public void setId(long id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setFirstName(String firstName) { this.firstName = firstName; }
// re-constructor
public CustomerEntity(long id) {
setId(id);
}
public CustomerEntity() {}
}
The “re-constructor” is an additional constructor getting passed a value for all the attributes making up the entity’s primary key. This of interest for find operations.
Generating the corresponding adapter class looks like this:
java
-D... see quick start tutorial
pm.pride.util.generator.EntityGenerator CUSTOMER adapter.CustomerAdapter adapter.CustomerEntity > CustomerAdapter.java
The first parameter after the table name specifies the class to generate - in this case a class called CustomerAdapter in package adapter. The second parameter is the name of a entity class the adapter should refer to. The result looks like this:
package adapter;
public class CustomerAdapter extends ObjectAdapter {
public static final String TABLE = "CUSTOMER";
public static final String COL_ID = "id";
public static final String COL_NAME = "name";
public static final String COL_FIRST_NAME = "first_name";
protected static final RecordDescriptor red =
new RecordDescriptor(CustomerEntity.class, TABLE, null)
.row(COL_ID, "getId", "setId")
.row(COL_NAME, "getName", "setName")
.row(COL_FIRST_NAME, "getFirstName", "setFirstName")
.key(COL_ID);
public RecordDescriptor getDescriptor() { return red; }
CustomerAdapter(CustomerEntity entity) { super(entity); }
}
All what the adapter class has to provide is a RecordDescriptor and an optional list of column names making up the entity’s primary key. Based on that, the class inherits all entity-related persistence capabilities from class pm.pride.ObjectAdapter. Adapters always operate on an instance of the entity class which must be passed in the adapter’s constructor. Finding a customer by its primary ID looks like this when using separate adapter classes:
// Create a customer entity, initialized with a primary key value of 1
CustomerEntity customer = new CustomerEntity(1);
// Create an adapter based on the entity
CustomerAdapter adapter = new CustomerAdapter(customer);
// Call the adapter's find method to find a customer by primary key 1.
// The primary key value is read from the entity passed in the adapter's constructor
// The result (if any) is written to the same entity
adapter.find();
As you see, every persistence operation now requires one additional line of code to create the adapter. Especially when you design a multi-threaded application, it is important to know that adapter and entity instances are not supposed to be shared among multiple threads. So creating new instances in every operation is the prefered technique and is usually not a considerable code complication.
If you want to minimize the amount of code, you are free to invent your own adapter concept. Have a look on the base classes pm.pride.ObjectAdapter for the adapter above and pm.pride.MappedObject for the hybrid variant from the quick start tutorial. Both are minimalistic implementations of the mix-in pm.pride.DatabaseAdapterMixin which is the actual provider for all entity-related persistence operations. It is in turn based on the static methods of the class pm.pride.DatabaseAdapter. Using this class or the mix-in you could easily produce a generic adapter being responsible for multiple entity types similar to JPA’s EntityManager interface.
One note concerning packages: When you actually use the pattern of separate adapters in a sophisticated architecture, you should consider generating entity and adapter classes in different packages. Only the entity classes should be part of the interface for dependent code while the adapter classes should completely be hidden behind facade components as proposed in the wide-spread repository pattern.
The examples for descriptors you have seen so far should already clarify most of the descriptor structure. You will see more complicated examples in following chapters of this manual. A descriptor is assembled from the following information:
The name of the entity class and the name of the database table which the entity class is mapped to. Preferably the table name is not specified as a string-literal but as a reference to a constant representing the table name. If you have a look on the outcome of PriDE’s code generator, there are appropriate constants generated and used.
A reference to the descriptor of a base class. This is of interest when you build up an inheritance hierarchy between entity classes as explained in chapter Entity Inheritance.
A table-row to attribute mapping by adding calls of the row() method for every row of interest. The row() method returns the descriptor object, making up a fluent API. Every row/attribute mapping consists of
The methods are the ones which the adapter is supposed to use for transporting entity attributes to the database via JDBC and vice versa. The getter methods’ return type implies which methods the adapter uses to access JDBC statements and result sets and how to translate the values to SQL syntax. Getters are mandatory whereas setters may be null in case of entity types that are never supposed to be written to the database. A typical example for this case are entity classes representing the result of SQL joins (see chapter Joins).
An optional primary key definition by adding a call of the key() method with a list of column names making up the primary key. Alternatively you can use rowPK() instead of row() for the row mappings.
The RecordDescriptor class has a few more constructors concerned with joins and accessing multiple databases, but that’s not important for now. The basic structure described above is what you work with most of the time.
The following table illustrates the mapping of Java object attribute types to SQL database field types as they are supported by PriDE. The row ‘JDBC type’ determines the type being used for the specified attribute type to access results from a JDBC ResultSet or to pass inputs to a JDBC PreparedStatement. The ‘SQL type’ specifies the actual SQL row types, the attributes can usually be mapped to. Not all SQL databases support all the mentioned type identifiers and it may also depend on the JDBC driver’s capabilities which mappings are supported. Primitive attribute types should of course only be used, if the corresponding row must not be NULL. Otherwise an exception will be thrown at runtime when attempting to process NULL values.
Java attribute type | JDBC type | SQL type |
---|---|---|
String | String | VARCHAR, VARCHAR2, NVARCHAR2, CHAR |
java.util.Date | java.sql.Date | DATE, DATETIME, TIMESTAMP, TIME |
java.sql.Date | java.sql.Date | DATE, DATETIME, TIMESTAMP, TIME |
java.sql.Timestamp | java.sql.Timestamp | DATETIME, TIMESTAMP, TIME |
int / Integer | Integer | INTEGER |
float / Float | Float | DECIMAL, REAL |
double / Double | Double | DECIMAL, REAL |
Any enum | String | VARCHAR, VARCHAR2, NVARCHAR2, CHAR |
boolean / Boolean | Boolean | BOOLEAN, INTEGER, SMALLINT, TINYINT, CHAR |
BigDecimal | BigDecimal | DECIMAL, NUMBER |
long / Long | Long | INTEGER, DECIMAL, NUMBER, BIGINT |
short / Short | Short | INTEGER, SMALLINT, TINYINT, DECIMAL |
byte / Byte | Byte | TINYINT |
byte[] | byte[] | BLOB, LONGVARBINARY, VARBINARY |
java.sql.Blob | java.sql.Blob | BLOB, LONGVARBINARY, VARBINARY |
java.sql.Clob | java.sql.Clob | CLOB, LONGVARCHAR |
java.sql.SQLXML / String | java.sql.SQLXML | java.sql.SQLXML |
Clobs and Blobs can only be used through PreparedStatements, i.e. you either have to access the entities with Clob / Blob attributes with PriDE’s prepared operations or you configure PriDE to use bind variables by default (see quick start tutorial).
The precision of dates and time stamps in the database vary significantly between different database vendors. E.g. although date rows were originally intended to represent dates without time portions in SQL databases, Oracle allows seconds precision instead and so does PriDE for Oracle. When using PriDE with plain SQL, dates and timestamps are rendered by appropriate database-specific formating functions like to_date
or to_timestamp
in Oracle, preserving the same precision as it applies to prepared statements.
The interface pm.pride.ResourceAccessor
provides the constant SYSTIME_DEFAULT. In update and insert operations this values will be translated to an expression which addresses the current database server time like CURRENT_TIMESTAMP in MySQL or SYSDATE in Oracle. This translation is only applied in plain SQL.
You can tell PriDE to map a java.util.Date attribute to an SQL time stamp by providing the JDBC type in the row definition of the record descriptor as an additional parameter like that:
.row(<columnname>, <getter>, <setter>, java.sql.Timestamp.class)
There are a few more type conversions which can be expressed that way. E.g. Enums can be represented by their ordinals in the database by providing java.lang.Integer.class
as additional parameter for the mapping of the corresponding attributes.
The general pattern for arbitrary type conversion is to provide appropriate additional getter/setter pairs which encapsulate the conversion. To make clear, that these getters / setters are for internal use only, it is common practice to give the method names a leading underscore. Assume you have an enumeration type for coins with their value in cent like that:
public enum Coin {
FIVE_CENT(5), FIFTY_CENT(50), ONE_EURO(100);
private int valueInCent;
Coin(int valueInCent) { this.valueInCent = valueInCent; }
int value() { return valueInCent; }
}
If you map an attribute of this type to the database, PriDE expects an SQL row of type VARCHAR or a similar type to store values like ‘FIVE_CENT’ etc. If you want to represent the coins by their value in the database, you provide a type-converting getter-setter-pair for the corresponding attribute in the entity class:
class MyEntity {
private Coin myAttr;
public int _getMyAttr() {
return myAttr.value();
}
public void _setMyAttr(int v) {
for (Coin coin: Coin.values()) {
if (coin.value() == v) {
myAttr = coin;
return;
}
}
throw new IllegalArgumentException();
}
}
Now you can use this getter setter pair to map the myAttr attribute to a DECIMAL table row, holding the coins’ values:
.row("MYATTR", "_getMyAttr", "_setMyCoin")