Wednesday, April 25, 2007

Simple Auditing using TopLink JPA

In a forum post on the Oracle TopLink forum on OTN there was a request for an approach to tracking entity history in a separate table. While Oracle TopLink includes support for automatic historical versioning this support does not exist in TopLink Essentials.

The following is a simple example for how you might do historical versioning of your persistent entities using TopLink Essentials and JPA. There are many approaches to how you would design your schema for managing this additional state. I kept my approach simple where the primary data table uses an integer version optimistic locking column and the history table stores a row for each version of the primary table.

The sample model is simply an Employee entity and its history is stored in an EmployeeHistory entity.

@Entity
@NamedQuery(name = "Employee.findAll",
query = "select e from Employee e ORDER BY e.lastName ASC, e.firstName ASC")
@TableGenerator(name = "emp-seq-table", table = "SEQUENCE",
pkColumnName = "SEQ_NAME", valueColumnName = "SEQ_COUNT",
pkColumnValue = "EMP_SEQ", allocationSize = 26)
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "emp-seq-table")
@Column(name = "EMP_ID", nullable = false)
private int id;

@Column(name = "F_NAME")
private String firstName;

@Column(name = "L_NAME")
private String lastName;

private String gender;

@Version
private long version;

...

@Entity
@IdClass(EmployeeHistory.ID.class)
@Table(name = "EMPLOYEE_HISTORY")
@NamedQuery(name="EmployeeHistory.findAllForEmployee", query="SELECT eh FROM EmployeeHistory eh WHERE eh.employee = :EMP")
public class EmployeeHistory {
@Id
@Column(name = "EMP_ID")
private int id;

@Id
private long version;

@ManyToOne
@JoinColumn(name = "EMP_ID", referencedColumnName = "EMP_ID", insertable=false, updatable=false)
private Employee employee;

@Column(name = "F_NAME")
private String firstName;

@Column(name = "L_NAME")
private String lastName;

private String gender;

/**
*
* @param emp
*/
protected EmployeeHistory(Employee emp) {
this.id = emp.getId();
this.version = emp.getVersion();
this.firstName = emp.getFirstName();
this.lastName = emp.getLastName();
this.gender = emp.getGender();

this.employee = emp;
}

...

Now to have the creation and modification of all Employee instances cause the creation of an EmployeeHistory instance I will use a TopLink Essentials specific event listener.

The descriptor event listener used looks like:

/**
* This is an example of how a TopLink Essentials DescriptorEventListener can be
* used to populate an audit record whenever an update occurs. This class must be
* registered as a persistence unit property in the creation of the
* EntityManagerFactory.
*
* property name="toplink.descriptor.customizer.Employee" value="model.jpa.auditing.EmployeeAuditingHandler"
*
* @since TopLink Essentials v2-Build-41
*/
public class EmployeeAuditingHandler extends DescriptorEventAdapter implements DescriptorCustomizer {

/**
* This is where the real work of this class happens. For any INSERT or UPDATE
* operation an INSERT of a new history object is forced.
*/
private void insertEmployeeHistory(DescriptorEvent event) {
EmployeeHistory empHist = new EmployeeHistory((Employee)event.getSource());
InsertObjectQuery insertQuery = new InsertObjectQuery(empHist);
event.getSession().executeQuery(insertQuery);
}

public void aboutToUpdate(DescriptorEvent event) {
insertEmployeeHistory(event);
}

public void aboutToInsert(DescriptorEvent event) {
insertEmployeeHistory(event);
}

/**
* This method is invoked through this class's registration as a descriptor
* customizer. All it does is register this same object as an event listener
*/
public void customize(ClassDescriptor descriptor) {
descriptor.getEventManager().addListener(this);
}
}
Now with this event listener in place I can proceed in my application using standard JPA coding. Every time I create or modify an Employee entity I will see the associated EmployeeHistory INSERT during the transaction commit.

I hope you find this helpful.

Doug











Sunday, April 22, 2007

TopLink Essentials using Oracle Spatial ... a sample application

Andrejus Baranovskis has posted a sample application using Oracle Spatial with TopLink Essentials JPA. The steps are based upon my previous post.

andrejusb.blogspot.com

Doug

Thursday, April 12, 2007

Oracle Spatial using TopLink Essentials JPA


As discussed in my previous post the usage of Oracle Database Spatial support is definitely growing in popularity. This post describes how TopLink Essentials, the reference implementation of JPA within GlassFish, can be extended to support mapping to MDSYS.SDO_GEOMETRY columns and leveraging the Oracle Spatial custom SQL operators within your queries.

Extending TopLink Essentials (TLE) for Spatial support involves a couple of key steps:

  1. A custom database platform is required to handle the STRUCT <-> JGeometry (oracle.spatial.geometry) conversions and statement binding.

  2. Customize the mappings to ensure the default serialized mapping for the unknown JGeometry type is not used.

  3. Usage of custom TLE expression operators for defining the queries.

1. Custom Database Platform

TLE uses a target database configuration to control custom SQL generation leveraging the different dialects supported by each of the database vendors. The platform is automatically detected by default but can be specified. For our purposes we will write a custom database platform extending the shipped OraclePlatform. To configure the use of our custom platform a persistence unit property (toplink.target-database) will need to be set.


<property name="toplink.target-database" value ="oracle.toplink.essentials.extension.spatial.Oracle10SpatialPlatform"/>


The custom platform must be written and packaged where the class-loader can access it.

Here is an example of what that platform might look like. Oracle10SpatialPlatform.java

2. Customizing the Mappings

The default mapping with JPA is to consider unknown types to be serialized basic (direct to field) mappings. In the case of JGeometry attribute types it is up to the developer to remove the converter that will be configured by default. The following descriptor customizer illustrates how the converter can be easily removed.

          
/**
* Descriptor customizer. This must be configured as a persistence unit property
* for each entity type containing a JGeometry attribute.
*
* property name="toplink.descriptor.customizer.SimpleSpatial" value="model.jpa.spatial_simple.toplink.SimpleSpatialCustomizer"
*
*/
public class SimpleSpatialCustomizer implements DescriptorCustomizer {

public void customize(ClassDescriptor descriptor) {
DirectToFieldMapping mapping =
(DirectToFieldMapping)descriptor.getMappingForAttributeName("geometry");
mapping.setConverter(null);


}
}



3. Using Custom Expression Operators

While the above two steps are pretty straight forward with one time configurations the more involved process is using spatial operators across dynamic and named queries. Based on a set of custom expression operators the following examples illustrate how a named query definition and usage might look.

EXAMPLE: Defining a Named Query with Custom Spatial Operators


/**
* Session customizer. This must be configured as a persistence unit property
* for each entity type containing a JGeometry attribute.
*
* <property name="toplink.session.customizer" value="model.jpa.spatial_simple.toplink.SS_SessionCustomizer/>"
*
*/
public class SS_SessionCustomizer implements SessionCustomizer {

public void customize(Session session) {
SpatialParameters params = new SpatialParameters();
params.setMask(SpatialParameters.Mask.ANYINTERACT);
params.setQueryType(SpatialParameters.QueryType.WINDOW);

ReadAllQuery raq = new ReadAllQuery(SimpleSpatial.class);
ExpressionBuilder eb = new ExpressionBuilder(SimpleSpatial.class);
Expression relateExp =
SpatialOperator.RELATE.buildExpression(eb.get("geometry"),
eb.getParameter("GEOMETRY"),
params);
Expression idExp = eb.get("id").greaterThan(0);

raq.setSelectionCriteria(idExp.and(relateExp));

raq.addArgument("GEOMETRY");

raq.addAscendingOrdering("id");

session.addQuery("SimpleSpatial.sampleQuery", raq);

}
}


Now to execute the above query within the application code standard JPA usage is followed.

EXAMPLE: Invoking the Named Query

double[] points = new double[] { 1, 1, 1, 20, 10, 20, 20, 1, 1, 1 };
JGeometry rectangle = JGeometry.createLinearPolygon(points, 2, 0);


Query query = getEntityManager().createNamedQuery("SimpleSpatial.sampleQuery");
query.setParameter("GEOMETRY", rectangle);

List<SimpleSpatial> results = query.getResultList();



In order to make all of this work a couple of helper classes are required to define the spatial operators and parameters.

SpatialOperator: Captures the definition of expression operators and acts as a factory for function expressions for use in queries.

SpatialParameters: Simplifies configurations of parameters for the MDSYS spatial operators

These classes must be compiled and made available on the application's classpath or packaged within a JAR with TopLink Essentials.


This support will be available in the next release of Oracle TopLink and will also be included in the initial contribution to EclipseLink. The final API in these version may vary from what I am providing now as a sample of how to extend TopLink.

Doug

Wednesday, April 4, 2007

Using Oracle Spatial with TopLink

I have been working a growing number of customers assisting them with their usage of Oracle Database Spatial support through TopLink. Actually my first exposure with Spatial and TopLink dates back to 1999, long before I joined (through acquisition) Oracle. It was an extended consulting engagement assisting Oracle Consulting in their usage of TopLink on a customer's application. Those Oracle consultants are now my co-workers managing the Oracle Consulting teams I deal with and I am now product manager for TopLink. Our recent work assisting customers in their extensions of TopLink for Spatial data mapping and querying has already produced results (GE Energy Press Release).

TopLink does not offer formal support for Oracle Spatial in the current 10.1.3.X versions or earlier. TopLink can however, be extended to support custom data types and query operators/functions. This post will walk through the basics of extended the expression framework and show some examples using Spatial data types.

In order to have TopLink both read and write SDO_GEOMETRY columns as well as generate the necessary SQL to query these columns two extensions to TopLink need to be made.

  1. Provide a custom database platform enabling the SDO_GEOMETRY <-> JGeometry (oracle.spatial.geometry) conversion

  2. Define and use custom expression operators to allow the generation of these operators into SQL

I will leave it to you to read about the Oracle DB's Spatial support in the docs, on OTN or download a sample.


Struct Conversions - Custom Database Platform

The SDO_GEOMETRY object type which is used to store geometries in the database passes through JDBC as a Struct. The spatial Java library provides an implementation of JGeometry (oracle.spatial.geometry) that simplifies conversion and usage within a Java object model. In order to get TopLink to work with JGeometry types in the object model and properly handle the Struct with JDBC a custom database platform is required.

Note: TopLink's existing support for mapping custom object types (Structs) is not an option with some Oracle Advanced Data Types such as Spatial. An active JDBC connection is required for conversion and the conversions at the mapping level do not necessarily have the connection available.

Example 1: JGeometry - Struct Conversion Code

public Object convertToObject(Struct struct) throws SQLException {
if (struct == null) {
return null;
}
return JGeometry.load((STRUCT)struct);
}

public Struct convertToStruct(Object geometry,
Connection connection) throws SQLException {
if (geometry == null) {
return null;
}
return JGeometry.store((JGeometry)geometry, connection);
}


To use these conversions within TopLink you must create a custom DatabasePlatform (most likely a subclass of Oracle10Platform) and incorporate these conversions into the platform.

Custom Expression Operators


TopLink's query framework makes use of expressions for defining the selection criteria as well as for specifying many query configuration options relative to the mapped object model. The expressions are basically a tree node object structure defining navigation through the mapped domain model as well as common query operators. For those of you more familiar with RedHatTM/JBossTM HibernateTM the expression framework provides a super-set of these capabilities to their criteria API.

Using the TopLink expressions developers are able to build very complex queries and have the platform specific SQL generated for them. Expressions also allow the queries to be processed in-memory and avoid unnecessary database trips.

ExpressionOperator

All operations used in expressions are defined by an ExpressionOperator. This capability can be extended to define additional operations.

Example 2: SDO_RELATE ExpressionOperator


public static ExpressionOperator RELATE_OP = relateOperator();

public static ExpressionOperator relateOperator() {
ExpressionOperator exOperator = new ExpressionOperator();
exOperator.setType(ExpressionOperator.FunctionOperator);
Vector v = NonSynchronizedVector.newInstance(4);
v.addElement("MDSYS.SDO_RELATE(");
v.addElement(", ");
v.addElement(", ");
v.addElement(")");
exOperator.printsAs(v);
exOperator.bePrefix();
exOperator.setNodeClass(ClassConstants.FunctionExpression_Class);
return exOperator;
}


These operators should be defined only once and re-used as needed when defining specific named or dynamic queries. In the above example a singleton operator (RELATE_OP) is used to store the custom operator.

FunctionExpression

To use an ExpressionOperator with specific parameter values a FunctionExpression must be built and incorporated within the selection criteria expression of a TopLink query.

Example 3: Using a FunctionExpression in a Query

        ReadAllQuery raq = new ReadAllQuery(MyEntity.class);
ExpressionBuilder eb = raq.getExpressionBuilder();

FunctionExpression relateOpExp = new FunctionExpression();
relateOpExp.setOperator(RELATE_OP);
// RELATE_OP is a static holding a singleton instance of the operator defined in Example 1.
relateOpExp.setBaseExpression(eb);

relateOpExp.addChild(Expression.from(eb.get("geometry"), eb)); // References a geometry attribute/column
relateOpExp.addChild(Expression.from(someJGeometry, eb)); // Passes in a JGeometry instance
relateOpExp.addChild(Expression.from("mask=ANYINTERACT", eb)); // Provides the parameters string for the operator

Expression relateExpression = relateOpExp.equal(Boolean.TRUE.toString());

raq.setSelectionCriteria(relateExpression);

session.executeQuery(raq);

Preview of Future TopLink Spatial Query API

In the next major release of TopLink we will offer out of the box support for Spatial mapping and querying. The following example illustrates how the same query as above would be written.

Example 4: Spatial Queries in the future TopLink release

ReadAllQuery raq = new ReadAllQuery(SimpleSpatial.class);
ExpressionBuilder eb = raq.getExpressionBuilder();

SpatialParameters params = new SpatialParameters();
params.setMask(Mask.ANYINTERACT);

Expression relateExp = SpatialExpressionFactory.relate(eb.get("geometry"), someJGeometry, params);
raq.setSelectionCriteria(relateExp);
raq.addAscendingOrdering("id");

List<SimpleSpatial> results = (List<SimpleSpatial>)session.executeQuery(raq);


This new API incorporates the same expression operator functionality within the SpatialExpressionFactory class. The SpatialParameters provides some easy to use enums for providing the parameter values instead of building the string value yourself. If you do want to provide the string value directly this is also available.


The goal of this post is to provide a simple example of how you can extend the expression framework for custom operators. When we preview the next release I am sure those of you using these extensions will find the provided functionality and simplified API a great asset.

Doug



Sunday, April 1, 2007

A new home

With the launching of the Java Persistence Platform Project (aka EclipseLink) I decided to increase the scope of my blog so that it is not focussed just on TopLink. I also wanted to move to a different host.

After working on TopLink in many different roles for over a decade I am looking forward to this new challenge. Delivering a high quality open source solution in an open and transparent manor is our goal. I plan to make this blog my way to express my opinion on persistence related subjects and also deliver technical content to assist in the project's adoption.

Doug