5 「Nashorn JavaScriptエンジンの概要」

Nashorn JavaScriptエンジンを使用すると、データベースでJavaScriptを直接実行できます。既存のクライアント側JavaScriptをデータベースで再利用でき、それをJava SEライブラリと組み合せると、RDBMSセッションで直接実行できる、機能が豊富で強力な汎用性の高いデータバインドされたアプリケーションの設計が可能になります。ここでは、次の各項でNashorn JavaScriptエンジンについて説明します。

5.1 Nashorn JavaScriptエンジンの使用について

Java 8の重要な機能の1つがNashorn JavaScriptエンジンです。Oracle Database 12c リリース2 (12.2.0.1)以降、Oracle JVMではJava 8がサポートされているため、Java 8コードベースを実行するOracle JVMに組み込まれているNashorn JavaScriptエンジンを使用してデータベースでJavaScriptを実行できます。データベースでJavaScriptを直接実行すると、次の利点があります。

Oracle DatabaseでJavaScriptを実行することの利点

  • 既存のスキルおよびコードの再利用。

  • Oracle Databaseにあるデータの送信の回避。データベースでJavaScriptを実行すると、データベース内部でのJSONドキュメントのより速いインプレース処理が可能になります。これにより、外部インフラストラクチャへのデータの送信が回避でき、ネットワーク・オーバーヘッドが回避されるため、パフォーマンスを向上させることができます。

  • 新しいデータべース機能の実現。

JavaScriptコードの実行では、12.2 Oracle JVMのNashorn エンジンをセッションで使用して、スキーマにDBJAVASCRIPTロールを付与する必要があります。このロールには、データベースでNashornを実行するのに必要なパーミッションが含まれます。ロールはDBAが付与でき、ユーザーごとに1回のみ付与する必要があります。

JavaScriptソースをOracle Databaseで管理するには、それらをJavaリソースとしてスキーマにロードしてからデータベース内でスクリプトを実行するのが望ましい方法です。

注意:

  • Nashornクラスの直接起動は、Oracle JVMでは制限されています。

  • Oracle JVMでは、すべてのスクリプティング・モードの拡張機能が無効になっています。

  • スクリプトは文字列またはファイル・ストリームのいずれかから標準スクリプティングAPIを使用して実行できますが、これはお薦めしません。この方法で起動されたスクリプトは、デフォルトのパーミッションでサンドボックス・モードでのみ実行できます。

5.1.1 スキーマへのJavaScriptコードのロード

loadjavaコマンドを使用してJavaScriptコードをデータベース・スキーマにロードします。スクリプトはJavaリソースとしてロードされます。たとえば、次のようなJavaScriptファイルhello.jsがあるとします。

function hello()
{
/*
*This is a sample Javascript file that prints "Hello World".
*/
var hellow = "Hello World";
return hellow;
}

var output = hello();
print(output);

次のコマンドを使用して、hello.jsスクリプト・ファイルをyourschemaというスキーマにロードします。

loadjava -v -u yourschema hello.js

loadjavaコマンドにより、パスワードの入力が要求された後、次の出力とともにスキーマ・ローカル・リソースhello.jsが作成されます。

arguments: '-u' 'yourschema/***' '-v' 'hello.js'
creating : resource hello.js
loading  : resource hello.js
Classes Loaded: 0
Resources Loaded: 1
Sources Loaded: 0
Published Interfaces: 0
Classes generated: 0
Classes skipped: 0
Synonyms Created: 0
Errors: 0

また、次のコマンドを使用して、hello.jsファイルがデータベースに正常にロードされたるかどうかを確認することもできます。

select object_name, object_type from user_objects;

その後、このリソースは次の各項で説明する方法を使用してNashornエンジンに渡すことができます。

5.1.2 Oracle JVMにおけるJavaScriptの実行方法

データベースにロードしたスクリプトは、次の3つの方法で実行できます。

5.1.2.1 DBMS_JAVASCRIPT.RUN PL/SQLプロシージャの使用

この方法は、値を戻さないJavaScriptプロシージャに便利です。引数としてリソース名を指定するためにDBMS_JAVASCRIPT.RUNプロシージャを起動します。スキーマにDBJAVASCRIPTロールが付与されていることを確認します。DBMS_JAVASCRIPT.RUNプロシージャは、oracle.aurora.rdbms.DbmsJavaScript.run Javaメソッドのラッパーで、Nashorn機能を内部的に起動します。

SQLからの場合

call dbms_javascript.run('hello.js');

JavaScriptコードが出力値を戻す場合は、次の命令を使用して標準出力デバイスに値を表示する必要があります。

SQL>set serveroutput on
SQL>call dbms_java.set_output(20000);
SQL>call dbms_javascript.run("hello.js");

PL/SQLからの場合

dbms_javascript.run('hello.js');

JavaScriptコードが出力値を戻す場合は、次の命令を使用して標準出力デバイスに値を表示する必要があります。

SQL>set serveroutput on
SQL>call dbms_java.set_output(20000);
SQL>call dbms_javascript.run("hello.js");
5.1.2.2 DbmsJavaScript Javaクラスの使用

これは、スキーマのパーミッションをJavaScriptコードに適用できるようになるため、Oracle JVMでJavaコードからスクリプトを起動するのに好ましい方法です。データベースで実行されるJavaコードからリソースhello.jsとしてロードされるJavaScriptを起動します。次のコード・スニペットに示すように、リソース名を引数として渡してoracle.aurora.rdbms.DbmsJavaScriptクラスのメソッド実行を起動します。

注意:

スキーマにDBJAVASCRIPTロールが付与されていることを確認します。

import oracle.aurora.rdbms.DbmsJavaScript; 
…
DbmsJavaScript.run("hello.js");
5.1.2.3 標準javax.script Javaパッケージの使用

この方法は、値を戻すJavaScript関数に便利です。通常、次のタスクを実行します。

注意:

スキーマにDBJAVASCRIPTロールが付与されていることを確認する必要があります。

  1. loadjavaコマンドを使用して、JavaScriptをJavaリソースとしてデータベースにロードします。

  2. javax.scriptパッケージを使用してJavaScriptを実行します。

  3. スクリプト・マネージャをインスタンス化します

  4. エンジンを作成します

  5. エンジンのevalメソッドに対する引数としてリソース・ストリーム・リーダーを渡します。

次のコード・スニペットでは、javax.scriptパッケージを使用してリソースhello.jsからコードを実行する方法を示します。

import javax.script.*;
import java.net.*;
import java.io.*;
...

// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine =
    factory.getEngineByName("javascript");
// create schema resource URL
URL url = Thread.currentThread()
    .getContextClassLoader().getResource("hello.js");
engine.eval(new InputStreamReader(url.openStream()));
...

文字列からJavaScriptコードを読み取ることもできます。次のコード・スニペットでは、文字列からJavaScriptコードを読み取って評価する方法を示します。

// evaluate JavaScript code from String
engine.eval(new StringReader(<script>));

そうではなく、ファイルからJavaScriptコードを読み取ることもできます。ただし、これには追加の権限が必要となります。次のコード・スニペットでは、script_nameファイルからJavaScriptコードを読み取って評価する方法を示します。

import javax.script.*;
...
ScriptEngine engine = new ScriptEngineManager()
    .getEngineByName("JavaScript");
engine.eval(new FileReader("script_name"));

JacaScriptコードの評価後、通常、JavaScript関数を起動するために次のタスクを実行します。

  1. NashornエンジンをInvocableにキャストしてinvokeFunctionを使用します。

    たとえば、hello.jsスクリプトでhelloというJavaScript関数を定義すると、次のコード・スニペットに示すように、これらのタスクを実行できます。

    // create a JavaScript engine as above
    Invocable invocable = (Invocable) engine;
    Object myResult = invocable.invokeFunction("hello");
    ...

    注意:

    invocable.invokeFunctionを使用するかわりに、スクリプトの内部から起動されるJavaメソッドに戻り値を渡す方法があります。この代替方法は、必要となるボイラープレート・コードが少なくなるため好ましく、DBMSJAVASCRIPT.RUNプロシージャと互換性があります。

    たとえば、JavaScriptリソースhello.jsselectQuery関数を定義し、次のようにクラスQueryTestで定義したprint() Javaメソッドに結果を渡すとします。

       public static void print (String results) { 
       System.out.println("my results: \n" + results);
           }

    その場合、JavaScriptからこれを実現するには、次の行をhello.jsファイルに追加します。

       var queryTest = Java.type("QueryTest");
       queryTest.print(selectQuery("all")); 

    ここで、次の方法のいずれかでhellp.jsを起動します。

    DbmsJavaScript.run("hello.js");
    
    DBMS_JAVASCRIPT.RUN('hello.js');
  2. 次の例に示すように、JavaScriptアプリケーションを起動するためにPL/SQLプロシージャを作成します。

    -- Create a procedure for select
    CREATE OR REPLACE PROCEDURE selectproc(id IN varchar2)
    IS
     output varchar2(10000);
    BEGIN
     SELECT invokeScriptEval(id) INTO output from dual;
     htp.prn(output);
    END;
    /
    SHOW ERRORS;

5.2 JDBCを使用したJavaScriptデータ・アクセス

この項では、JavaScriptを使用してRDBMSのデータにアクセスする方法について説明します。現在、RDBMSのデータにアクセスするためのJavaScript標準はありません。Nashorn JavaScriptエンジンにより、JavaScript内で標準のJDBCを使用することができます。
JDBCをJavaScriptとともに使用してアプリケーションをサービスとして公開するには、次の手順を実行します。
  1. 次の例に示すように、表をデータベースに作成し、JSONを使用して移入します。
    DROP TABLE employees PURGE;
    
    CREATE TABLE employees (
      id    RAW(16) NOT NULL,
      data  CLOB,
      CONSTRAINT employees_pk PRIMARY KEY (id),
      CONSTRAINT employees_json_chk CHECK (data IS JSON)
    );
    
    TRUNCATE TABLE employees;
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "100",
              "FirstName" : "Kuassi",
              "LastName"  : "Mensah",
              "Job"       : "Manager",
              "Email"     : "kuassi@oracle.com",
              "Address"   : {
                              "City" : "Redwood",
                              "Country" : "US"
                            }
             }');
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "200",
              "FirstName" : "Nancy",
              "LastName"  : "Greenberg",
              "Job"       : "Manager",
              "Email"     : "Nancy@oracle.com",
              "Address"   : {
                              "City" : "Boston",
                              "Country" : "US"
                            }
             }');
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "300",
              "FirstName" : "Suresh",
              "LastName"  : "Mohan",
              "Job"       : "Developer",
              "Email"     : "Suresh@oracle.com",
              "Address"   : {
                              "City" : "Bangalore",
                              "Country" : "India"
                            }
             }');
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "400",
              "FirstName" : "Nirmala",
              "LastName"  : "Sundarappa",
              "Job"       : "Manager",
              "Email"     : "Nirmala@oracle.com",
              "Address"   : {
                              "City" : "Redwood",
                              "Country" : "US"
                            }
             }');
    
    INSERT INTO employees (id, data)
    VALUES (SYS_GUID(),
            '{
              "EmpId"     : "500",
              "FirstName" : "Amarnath",
              "LastName"  : "Chandana",
              "Job"       : "Test Devloper",
              "Email"     : "amarnath@oracle.com",
              "Address"   : {
                             "City" : "Bangalore", 
                             "Country" : "India"
                            }
             }');
  2. 次の例に示すように、JDBCを使用してJavaScriptアプリケーションを作成し、JavaScriptコードをスキーマにロードします。
    var selectQuery = function(id)
    {
       var Driver = Packages.oracle.jdbc.OracleDriver;
       var oracleDriver = new Driver();
       var url = "jdbc:default:connection:";
       var output = "";
       var connection = oracleDriver.defaultConnection();
       var prepStmt;
    
       // Prepare statement
        if(id == 'all') {
           prepStmt = connection.prepareStatement("SELECT a.data FROM employees a");
          } else {
           prepStmt = connection.prepareStatement("SELECT a.data FROM employees a WHERE a.data.EmpId = ?");
           prepStmt.setInt(1, id);
           }
    
       // execute Query
        var resultSet = prepStmt.executeQuery();
    
       // display results
        while(resultSet.next()) {
            output = output + resultSet.getString(1) + "<br>";
        }
    
       // cleanup
        resultSet.close();
        prepStmt.close();
        connection.close();
        return output;
    }
  3. 次の例を示すように、Javaリソースを作成します。
    create or replace and compile java source named "InvokeScript" as
    import javax.script.*;
    import java.net.*;
    import java.io.*;
    
    public class InvokeScript {
        public static String eval(String inputId) throws Exception {
    	String output = new String();
        try {
            // create a script engine manager
            ScriptEngineManager factory = new ScriptEngineManager();
    
            // create a JavaScript engine
            ScriptEngine engine = factory.getEngineByName("javascript");
    
            //read the script as a java resource
    				engine.eval(new InputStreamReader(InvokeScript.class.getResourceAsStream("select.js")));
    				// Alternative approach
    				//engine.eval(Thread.currentThread().getContextClassLoader().getResource("select.js"));
    
    
            Invocable invocable = (Invocable) engine;
            Object selectResult = invocable.invokeFunction("selectQuery", inputId);
            output = selectResult.toString();
        } catch(Exception e) {
    		output =e.getMessage();
    	}
            return output;
        }
    }
    /
  4. 次の例に示すように、エンジンのevalメソッドのラッパーを作成します。
    -- Create function
    CREATE OR REPLACE FUNCTION invokeScriptEval(inputId varchar2) return varchar2 as language java 
    name 'InvokeScript.eval(java.lang.String) return java.lang.String';
    /
  5. 次の例に示すように、SQLからinvokeScriptEval JavaScript関数を起動します。
    CREATE OR REPLACE PROCEDURE sqldemo(id IN varchar2)
    IS
     output varchar2(10000);
    BEGIN
     SELECT invokeScriptEval(id) INTO output from dual;
     dbms_output.put_line(output);
    END;
    /
    SHOW ERRORS;
  6. 次の例に示すように、SQLからsqldemoプロシージャを起動します。
    SQL> set serveroutput on
    SQL> call dbms_java.set_output(5000);
    SQL> call sqldemo('100');

5.3 JavaScriptアプリケーションのREST対応化

Oracle REST Data Services (ORDS)を使用して、JavaScriptアプリケーションをクラウド対応サービスにするには、次の手順を実行します。
  1. 次の場所からOracle REST Data Services (ORDS)インストーラ・ファイルをダウンロードします。

    http://www.oracle.com/technetwork/developer-tools/rest-data-services/downloads/index.html

    注意:

    • Oracle REST Data Services (ORDS)を使用して、データベース・アプリケーションでJavaをクラウド対応サービスにするために同様の手順を実行できます。

    • ORDSのインストールには、40 MB以上の領域を持つUSERS表領域が必要です。

  2. 次を除いて、デフォルトのオプションを選択してORDSをインストールします。
    • PL/SQLゲートウェイを使用するかどうか入力を要求された場合は、2を選択してステップをスキップします。

    • Application Express RESTfulサービスのデータベース・ユーザー(APEX_LISTENER、APEX_REST_PUBLIC_USER)のパスワードを指定するように要求された場合は、2を選択してステップをスキップします。

    • スタンドアロン・モードでサービスを起動するか、終了するかの入力を要求された場合は、1を選択してスタンドアロン・モードでサービスを起動します。

  3. リスニング・ポートをサービスに割り当てます。デフォルトではポート8080が使用されますが、.../paramsディレクトリ下にあるords_params.propertiesファイルでこの値を変更することもできます。次に、次のコマンドを使用して、Jerseyサーバーをバウンスします。
    java -jar ords.war
  4. invokeScriptEvalメソッドに対するラッパーとして、次のようなプロシージャを作成します。
    Rem Create a procedure for select
    CREATE OR REPLACE PROCEDURE selectproc(id IN varchar2)
    IS
     output varchar2(10000);
    BEGIN
     SELECT invokeScriptEval(id) INTO output from dual;
     htp.prn(output);
    END;
    /
    SHOW ERRORS;
  5. 外部JavaScript SELECT問合せを実行するために、次のようなプロシージャを作成します。
    begin
      ords.create_service(
        p_module_name => 'load.routes' ,
        p_base_path   => '/load/routes/',
        p_pattern     => 'nashorn/selectbyid/:id',
        p_source_type => 'plsql/block',
        p_source      => 'begin selectproc(:id); end;'
    );
       commit;
    end;
    /
  6. ブラウザからサービスを起動します。URLの形式は次のとおりです。
    http://<server>:<port>/ords/<workspace>/load/routes/nashorn/<your_JSON_query>/<input>

    次に例を示します。

    http://localhost:8090/ords/ordstest/load/routes/nashorn/selectbyid/100

    ここでは、100は、詳細をフェッチする必要がある従業員の従業員IDを指定しています。次のイメージは出力を示しています。

    図5-1 JavaScriptアプリケーションの出力

    図5-1の説明が続きます
    「図5-1 JavaScriptアプリケーションの出力」の説明
出力はJSON形式で表示されます。