No JDBC based data retrieval in Java applications – RESTstyle, JSON formatted HTTP based interaction from Java to Database

This article demonstrates how Java application can query data from relational databases (well, in this case more specifically the Oracle RDBMS) without the use of plain old JDBC but instead through a simple HTTP interaction with a RESTful service that returns the data in JSON format.

In this way, issues with JDBC drivers, database connections, data sources and firewall and other network complications are avoided and the Java application does not contain a single bit of SQL.

Image

This article will demonstrate how easy it is to set up the RESTful API from the database – using Oracle’s dbms_epg package – and how this API can be accessed from Java using JAX-RS (Jersey) and the json-lib utility library.

This topic is one of many to be discussed in Friday’s (4th February) Masterclass on Leveraging the Oracle Database in Java Application (some seats are available – go to http://www.amis.nl/trainingen/oracle-database-in-java-applications for registration and details).

Creating the RESTful PL/SQL API

The Oracle Database can be accessed over HTTP. The most common way in over HTTP is through URLs registered through DADs (Database Access Descriptions) with the dbms_epg package. EPG stands for Embedded PL/SQL Gateway and has been around since Oracle 10g. Prior to EPG, Oracle shipped with the MOD_PLSQL plugin for Apache (Oracle HTTP Server) that also supports handling of HTTP requests by PL/SQL packages.

Before a package can be accessed via HTTP, a DAD has to be created and authorized for the database schema that contains the package. This has to be done by a user with the XDBADMIN role – for example SYS:

begin
  DBMS_EPG.create_dad
  ( dad_name => 'hrmrestapi'
  , path => '/hrmapi/*'
  );
  dbms_epg.authorize_dad('hrmrestapi','SCOTT');
end;

The HTTP port of the database needs to be enabled as well. Port 2100 is ocasionally used, but other port-numers can be used as well.

EXECUTE dbms_xdb.SETHTTPPORT(2100);

Some special configuration is required when the URL should not include the name of package directly, but instead a more intricate URL pattern is to be used – like in this example. In such situations, a handler package for handling special URLs is registered:

BEGIN
  dbms_epg.set_dad_attribute
  ( dad_name => 'hrmrestapi'
  , attr_name => 'path-alias'
  , attr_value => 'rest'
  );
  dbms_epg.set_dad_attribute
  ( dad_name => 'hrmrestapi'
  , attr_name => 'path-alias-procedure'
  , attr_value => 'scott.hrm_rest_api.handle_request'
  );
END;

This code specifies that URLs to the hrmrestapi DAD that use the path-alias rest (for example: host:port/hrmrestapi/rest/something) should be handled by the package hrm_rest_api, and more specifically by its procedure handle_request.

The package specification for our RESTful API package is quite simple:

create or replace
package hrm_rest_api
is

  procedure handle_request
  ( p_path in varchar2
  );

end hrm_rest_api;

The package body contains the central dispatcher procedure handle_request:

procedure handle_request (p_path in varchar2)
is
 l_path_elements t_array := split(p_path,'/');
begin
  if lower(l_path_elements(1))='departments'
  then
    process_departments(sub_array(l_path_elements, 2));
  elsif  lower(l_path_elements(1))='employees'
  then
    process_employees(sub_array(l_path_elements, 2), null);
  end if;
end;

This procedure is handed the URL path (the part that follows after http://server:port/hrmrestapi/rest). It checks whether the path starts with departments or with employees – the two currently supported paths. It invokes the processing procedures accordingly.
The process_departments procedure is shown here, the other procedures are similar and can be found in the source code associated with this article.

procedure process_departments(l_path_elements in t_array)
is
  l_first boolean;
begin
  if l_path_elements.count = 0 or l_path_elements(1)='all'
  then
    htp.p('{"departments": [');
    l_first:= true;
    for l_rec in (select * from dept ) loop
      if not l_first
      then
        htp.p(',');
      else
        l_first:= false;
      end if;
      htp.p('{"department": {');
      htp.p('"departmentIdentifier":'||l_rec.deptno);
      htp.p(', "departmentName":"'||l_rec.dname||'"');
      htp.p(', "departmentLocation":"'||l_rec.loc||'"');
      htp.p('}}');
    end loop;
    htp.p(']}');
  else
     process_department(p_deptno => l_path_elements(1), p_path_elements => sub_array(l_path_elements,2));
  end if;
end;

Note: using the PL/JSON library for PL/SQL, this code can be written even more elegantly and with less room for errors. See: http://database-geek.com/2009/03/25/json-in-and-out-of-oracle-json-data-type/.

The data published by the database package can be accessed from any browser, using a REST-style URL – naming resource type and resource identifiers:

Image

When you check the Page Source in the browser, the JSON structure is revealed. JSON can be parsed directly in JavaScript – in rich web clients – and also in many other languages including PL/SQL itself (using PL/JSON for example) as an alternative to database links and in Java, for example using json-lib as demonstrated below.

Code the Java to access the RESTful HRM service

Using the JAX-RS implementation provides by Jersey and the json-lib library for parsing JSON objects, it is fairly straightforward to retrieve the data from the database via HTTP and the HRM api described above.

This simple Java application makes calls to the RESTful service using a generic method – in go the resource path that should be accessed and out comes the JSON response:

    private static String BASE_URI = "http://xp-vm:2100/hrmapi/rest";

    public static String restfulCall(String path) {
        Client client = Client.create();
        client.addFilter(new HTTPBasicAuthFilter("SCOTT", "tiger"));
        WebResource webResource = client.resource(BASE_URI);
        String response =
            webResource.path(path).accept(MediaType.TEXT_PLAIN).get(String.class);
        return response;
    }

Note how the username and password for Basic Authentication are set on the requests sent to the RESTful API exposed by the Oracle RDBMS.

The main method in the Java Class traverses all Department resources across the RESTful service and drills down into the employees and even their subordinates. It uses helper classes from the json-lib library to help parse the JSON data received from the service. Two converter methods – departmentConstructor and employeeConstructor – turn a JSONObject into a POJO.

    public static void main(String[] args) {

        String response = Consumer.restfulCall("departments");
        JSONObject json = (JSONObject)JSONSerializer.toJSON(response);
        JSONArray departments = json.getJSONArray("departments");

        for (int i = 0; i < departments.size(); ++i) {
            JSONObject department =
                (JSONObject)departments.getJSONObject(i).get("department");
            Department d = Consumer.departmentConstructor(department);
            System.out.println("Department " + d.getIdentifier() +
                               " is called " + d.getName() +
                               " and located in " + d.getLocation());
            json = (JSONObject)JSONSerializer.toJSON(Consumer.restfulCall
                         ("departments/" + d.getIdentifier() + "/employees"));
            JSONArray employees = json.getJSONArray("employees");
            for (int j = 0; j < employees.size(); ++j) {
                JSONObject employee =
                    (JSONObject)employees.getJSONObject(j).get("employee");
                Employee e = Consumer.employeeConstructor(employee);
                System.out.println("  * Employee " + e.getIdentifier() +
                                   " is called " + e.getName() +
                                   " and works as " + e.getJob());
                json =
(JSONObject)JSONSerializer.toJSON(Consumer.restfulCall("departments/" +
                                                       d.getIdentifier() +
                                                       "/employees/" +
                                                       e.getIdentifier() +
                                                       "/subordinates"));
                JSONArray subordinates = json.getJSONArray("subordinates");
                for (int k = 0; k < subordinates.size(); ++k) {
                    JSONObject subordinate =
                        (JSONObject)subordinates.getJSONObject(k).get("employee");
                    Employee s = Consumer.employeeConstructor(subordinate);
                    System.out.println("    - Staff member " +
                                       s.getIdentifier() + " is called " +
                                       s.getName() + " and works as " +
                                       s.getJob());
                }
            }
        }
    }

One of the converter methods:

    public static Department departmentConstructor(JSONObject json) {
        Department d = new Department();
        d.setIdentifier(json.getString("departmentIdentifier"));
        d.setName(json.getString("departmentName"));
        d.setLocation(json.getString("departmentLocation"));
        return d;
    }

The outcome of the calls to the RESTful service is used to produce the following output:

Image

The following URLs have been accessed to produce this output:

  • http://server:2100/hrmapi/rest/departments
  • http://server:2100/hrmapi/rest/departments/#/employees
  • http://server:2100/hrmapi/rest/departments/#/employees/#/subordinates

other supported URLs are:

  • http://server:2100/hrmapi/rest/departments/#
  • http://server:2100/hrmapi/rest/employees
  • http://server:2100/hrmapi/rest/employees/#
  • http://server:2100/hrmapi/rest/departments/#/employees/#

In these URLs, the # represents the identifier of one of the resources.

It is not exceedingly efficient – though very REST-stylish – to retrieve the data for each resources individually. When you need to retrieve more complex data structures from the database in your Java application, JSON can easily be used to create fairly complex, deeply nested data graphs that are returned in a single call. It’s bit like XML – but much more compact (and error prone).

Resources

The source code for this article. In order to compile and run the Java project, you have to add the jersey-bundle-1.5.jar and the Apache Commons Language library (for example commons-lang-2.6.jar) as libraries. RESTfulJavaDBInteraction_EncapsulationOfTheDataModel.zip

http://ora-00001.blogspot.com/2009/07/creating-rest-web-service-with-plsql.html

http://www.oracle-base.com/articles/10g/dbms_epg_10gR2.php

http://answers.oreilly.com/topic/257-how-to-parse-json-in-java/

http://blogs.sun.com/naresh/entry/jersey_client_api_in_action

One Response

  1. Gerton ten Ham January 31, 2011