プライマリ・コンテンツに移動
Oracle® Mobile Application Framework Oracle Mobile Application Frameworkでのモバイル・アプリケーションの開発
2.5.0.0.0
E94553-01
目次へ移動
目次

前
次

18 MAF AMXでのローカル・データベースの使用方法

この章では、MAF AMXアプリケーション機能でローカルSQLiteデータベースを使用する方法について説明します。

この章の内容は次のとおりです。

18.1 ローカルSQLiteデータベースの使用方法の概要

SQLiteは、特に埋込みアプリケーション用に設計されたリレーショナル・データベース管理システム(RDBMS)です。

SQLiteには次の特性があります。

  • ACID準拠: 他の従来のデータベース・システムと同様に、原子性、一貫性、独立性および永続性という特性を備えています。

  • 軽量: アプリケーション内に直接埋め込むように設計された小さなCライブラリでデータベース全体が構成されています。

  • 移植性: 様々な範囲のコンピュータ・アーキテクチャおよびオペレーティング・システム間でバイナリ互換性がある単一ファイルでデータベースは自己完結型となります。

詳細は、SQLite Webサイト(http://www.sqlite.org)を参照してください。

ローカルのSQLiteデータベースの使用例は、開発コンピュータのjdev_install/jdeveloper/jdev/extensions/oracle.maf/Samplesディレクトリ内のPublicSamples.zipファイルにあるCRUDDemoというMAFサンプル・アプリケーションを参照してください。CRUDDemoサンプル・アプリケーションは、カスタムSQLiteデータベース・ファイル(このアプリケーション内にパッケージ化されています)を使用します。このデータベース・ファイルには、従業員に関する情報が含まれるレコードのある表が含まれています。アプリケーションは、アクティブ化されると、この表からデータを読み取り、従業員のリストを表示します。従業員に関する情報はCRUD操作の対象となり、ユーザー・インタフェースから、従業員を作成、並替え、更新および削除できます。すべてのCRUD操作は、SQLiteデータベース内で更新されます。

SQLiteデータベースを使用して、MAFアプリケーションでRESTデータ・サービスとのオフライン・アクセスおよび同期を提供する場合は、MAFクライアント・データ・モデルを使用することをお薦めします。これは、RESTサービスからのデータの取得を容易にするウイザードと、MAFアプリケーションがオフラインのときにSQLiteデータベースに残すデータを選択するウィザードを提供し、オフライン・トランザクションとMAFアプリケーションがオンラインに復帰したときの同期のサポートを可能にします。また、SQLiteデータベースとやり取りするための様々なメソッドを公開するAPI (DBPersistenceManager)も提供されます。「MAFアプリケーションでのクライアント・データ・モデルの作成」「MAFクライアント・データ・モデルDBPersistenceManagerの使用によるSQLiteデータベースへのアクセス」を参照してください。

18.1.1 SQLiteと他のリレーショナル・データベースとの相違点

SQLiteは、埋込みデータベース・システムとして使用する設計になっており、通常は1人のユーザーによって使用され、アプリケーションに直接リンクされることも多くあります。他方、エンタープライズ・データベースは、クライアントとサーバーの分散された環境における高い同時実行性を求めて設計されています。

このような違いがあるため、Oracleデータベースと比較して複数の制約が存在します。最も重要な相違点を次に示します。

次の項も参照してください。

18.1.1.1 同時実行性

SQLiteデータベースの単一インスタンスは、常に、単一の読取り/書込み接続または複数の読取り専用接続のいずれかを持つことができます。ロック・メカニズムが粗いため、SQLiteでは、同一のデータベース・インスタンスに対する複数の読取り/書込み接続のサポートが不可能です。

ロック・メカニズムが粗いため、SQLiteでは、同一のデータベース・インスタンスに対する複数の読取り/書込み接続がサポートされていません。『File Locking And Concurrency In SQLite Version 3』を参照してください(SQLite Webサイトhttp://www.sqlite.org/lockingv3.htmlの「Documentation」セクションから入手できます)。

18.1.1.2 SQLのサポートおよび解釈

SQLiteはSQL92標準に完全に準拠していますが、いくつかのサポート対象外の構成があります。

SQLiteはSQL92標準でコンパイルされますが、サポートされていない構成が次を含めていくつか存在します。

  • RIGHT OUTER JOIN

  • FULL OUTER JOIN

  • GRANT

  • REVOKE

『SQL Features That SQLite Does Not Implement』を参照してください(SQLite Webサイトhttp://www.sqlite.org/omitted.htmlの「Documentation」セクションから入手できます)。

SQLiteによるSQLの解釈の方法については、SQLite Webサイトのドキュメント・セクションで入手可能な『SQL As Understood by SQLite』(http://www.sqlite.org/lang_createtable.html)を参照してください。

18.1.1.3 データ型

SQLiteは動的に型指定され、宣言された型とは無関係に、任意の列に任意の値を格納できます。

大部分のデータベース・システムは強く型指定をしているのに対して、SQLiteは動的な型指定のため、宣言された型とは無関係にどの列にどの値を格納することもできます。たとえば、数値列に文字列値が間違って格納された場合でも、SQLiteではエラーは戻されません。『Datatypes In SQLite Version 3』を参照してください(SQLite Webサイトhttp://www.sqlite.org/datatype3.htmlの「Documentation」セクションから入手できます)。

18.1.1.4 外部キー

SQLiteは外部キーをサポートします。これは、外部キー制約を解析および強制します。『SQLite Foreign Key Support』を参照してください(SQLiteサイトhttp://www.sqlite.org/foreignkeys.htmlの「Documentation」セクションから入手できます)。

18.1.1.5 データベース・トランザクション

SQLiteはACID準拠であるためトランザクションをサポートしていますが、ネストされたトランザクションをサポートしていません。また、オープンしているすべてのResultSetsがクローズされるまでトランザクションをロールバックできません。SQLiteは、任意のデータベースに対し、複数の読取り専用接続または単一の読取り/書込み接続のどちらかを許容します。

SQLiteはACID準拠であるためトランザクションをサポートしていますが、SQLiteとOracleのトランザクション・サポートの間には基本的な相違点がいくつか存在します。

  • ネストされたトランザクション: SQLiteでは、ネストされたトランザクションをサポートしません。単一のトランザクションのみを常時アクティブにできます。

  • コミット: SQLiteは、任意の指定されたデータベースに対し、複数の読取り専用接続または単一の読取り/書込み接続のいずれかを許容します。したがって、同一のデータベースに対して複数の接続がある場合は、データベースの変更を試みる最初の接続のみが成功します。

  • ロールバック: SQLiteでは、開いているすべてのResultSetsを閉じるまではトランザクションのロールバックができません。

『Distinctive Features of SQLite』を参照してください(SQLite Webサイトhttp://www.sqlite.org/different.htmlの「Documentation」セクションから入手できます)。

18.1.1.6 認証

SQLiteは、ロールベースの認証とユーザーベースの認証のどちらもサポートしていません。ユーザーはファイル内のすべてのデータにアクセス可能です。データの保護にはMAF暗号化を使用できます。

SQLiteでは、ロールベースまたはユーザーベースの認証はどの形式もサポートしていません。デフォルトで、どのユーザーもファイル内のすべてのデータにアクセスできます。ただし、MAFでは、使用可能な暗号化ルーチンを提供することで、データを保護し、有効な資格証明セットを持たないユーザーによるアクセスを防ぎます。「データベースを暗号化および復号化する方法」を参照してください。

18.2 ローカルSQLiteデータベースの使用方法

MAFには、暗号化されたSQLite 3.8.5データベースが含まれています。

通常SQLiteを使用する場合は、次のことを知っておく必要があります。

18.2.1 データベースへの接続方法

SQLiteデータベースに接続するには、アプリケーションに関連付けられたjava.sql.Connectionオブジェクトを使用します。SQLite JDBC URLは、テキストjdbc:sqliteで始める必要があります。

SQLiteデータベースへの接続は、Oracleデータベースへの接続を開く場合とは異なります。最初の接続を取得すると、同じJDBC APIおよびSQL構文の大部分が使用可能になり、データベースの問合せと変更が可能になります。

ユーザーのアプリケーションに関連付けられたjava.sql.Connectionオブジェクトを使用して、SQLiteデータベースに接続します。接続を作成する場合は、どのSQLite JDBC URLもjdbc:sqlite:というテキストで始まるようにします。

次の例は、暗号化されていないデータベースと接続を開く方法を示しています。接続を取得する前に、JDBCドライバをロードします。

public static Connection getConnection() throws Exception {
   if (conn == null) {
      try {
         // create a database connection
         String Dir = AdfmfJavaUtilities.getDirectoryPathRoot(
                              AdfmfJavaUtilities.ApplicationDirectory);
         String connStr = "jdbc:sqlite:" + Dir + "/portfolio.db";
         // Load the driver
         Class.forName("SQLite.JDBCDriver");
         conn = DriverManager.getConnection(connStr);
      }
      catch (SQLException e) {
         // If the error message is "out of memory", it probably
         // means that no database file is found
         System.err.println(e.getClass().getName() + ": " + e.getMessage() );
         e.printStackTrace();
      }
   }
   return conn;
}

次の例は、暗号化されたデータベースと接続を開く方法を示しています。

java.sql.Connection connection = new SQLite.JDBCDataSource(
             "jdbc:sqlite:/path/to/database").getConnection(null,"password");

前述の例では、getConnectionメソッドの最初のパラメータがユーザー名となっていますが、SQLiteではユーザーベースのセキュリティがサポートされないため、この値は無視されます。

注意:

SQLiteでは、不正なパスワードで暗号化されたデータベースを開いた場合でも、エラー・メッセージが表示されません。同様に、暗号化されていないデータベースをパスワードで誤って開いた場合でも警告は表示されません。そのかわりに、データの読取りまたは変更を試みた場合は、「エラー: ファイルが暗号化されているか、ファイルがデータベースではありません。」というメッセージとともにSQLExceptionがスローされます。

18.2.2 SQLスクリプトを使用してデータベースを初期化する方法

アプリケーションの起動時に、SQLスクリプトによってデータベースが初期化されます。このスクリプトは、MAFアプリケーションのApplicationControllerプロジェクトに、リソースとして追加する必要があります。

SQLスクリプトを使用して、アプリケーションの起動時にデータベースを初期化します。次の例は、サポートされているSQL構文(「SQLのサポートおよび解釈」で説明)の一部を示すSQL初期化スクリプトを示しています。この例では、DROP TABLECREATE TABLEおよびINSERTコマンド、NUMBERおよびVARCHAR2データ型が使用されています。

DROP TABLE IF EXISTS PERSONS;

CREATE TABLE PERSONS
(
PERSON_ID NUMBER(15) NOT NULL,
FIRST_NAME VARCHAR2(30),
LAST_NAME VARCHAR2(30),
EMAIL VARCHAR2(25) NOT NULL
);

INSERT INTO PERSONS (PERSON_ID, FIRST_NAME, LAST_NAME, EMAIL) VALUES ( 100, 'David', 'King', 'steven@king.net');
INSERT INTO PERSONS (PERSON_ID, FIRST_NAME, LAST_NAME, EMAIL) VALUES ( 101, 'Neena', 'Kochhar', 'neena@kochhar.net');
INSERT INTO PERSONS (PERSON_ID, FIRST_NAME, LAST_NAME, EMAIL) VALUES ( 102, 'Lex', 'De Haan', 'lex@dehaan.net');
INSERT INTO PERSONS (PERSON_ID, FIRST_NAME, LAST_NAME, EMAIL) VALUES ( 103, 'Alexander', 'Hunold', 'alexander@hunold.net');
INSERT INTO PERSONS (PERSON_ID, FIRST_NAME, LAST_NAME, EMAIL) VALUES ( 104, 'Bruce', 'Ernst', 'bruce@ernst.net');

SQLスクリプトを使用するには、そのスクリプトをMAFアプリケーションのApplicationControllerプロジェクトにリソースとして追加します。サンプル・スクリプトは、META-INFディレクトリでinitialize.sqlとして保存されているものとします。次の例は、SQLスクリプトを解析し、文を実行するために追加する必要のあるコードを示しています。

private static void initializeDatabaseFromScript() throws Exception {
   InputStream scriptStream = null;
   Connection conn = null;
   try {
      // ApplicationDirectory returns the private read-write sandbox area
      // of the mobile device's file system that this application can access.
      // This is where the database is created
      String docRoot = AdfmfJavaUtilities.getDirectoryPathRoot
                                (AdfmfJavaUtilities.ApplicationDirectory);
      String dbName = docRoot + "/sample.db";

      // Verify whether or not the database exists.
      // If it does, then it has already been initialized 
      // and no furher actions are required
      File dbFile = new File(dbName);
      if (dbFile.exists())
         return;

      // If the database does not exist, a new database is automatically 
      // created when the SQLite JDBC connection is created
      conn = new SQLite.JDBCDataSource("jdbc:sqlite:" + docRoot +
                                       "/sample.db").getConnection();

      // To improve performance, the statements are executed 
      // one at a time in the context of a single transaction
      conn.setAutoCommit(false);

      // Since the SQL script has been packaged as a resource within
      // the application, the getResourceAsStream method is used 
      scriptStream = Thread.currentThread().getContextClassLoader().
                            getResourceAsStream("META-INF/initialize.sql");
      BufferedReader scriptReader = new BufferedReader
                                        (new InputStreamReader(scriptStream));
      String nextLine;
      StringBuffer nextStatement = new StringBuffer();

      // The while loop iterates over all the lines in the SQL script,
      // assembling them into valid SQL statements and executing them as
      // a terminating semicolon is encountered
      Statement stmt = conn.createStatement();
      while ((nextLine = scriptReader.readLine()) != null) {
         // Skipping blank lines, comments, and COMMIT statements
         if (nextLine.startsWith("REM") ||
             nextLine.startsWith("COMMIT") || 
             nextLine.length() < 1)
            continue;
         nextStatement.append(nextLine);
         if (nextLine.endsWith(";")) {
            stmt.execute(nextStatement.toString());
            nextStatement = new StringBuffer();
         }
      }
      conn.commit();
   }
   finally {
      if (conn != null)
         conn.close();
   }
}

注意:

例を簡単にするため、前の例ではエラー処理を省略しています。

次の例に示すように、データベース初期化コード(前の例を参照)をLifeCycleListenerImplstartメソッドから起動します。

public void start() {
   try {
      initializeDatabaseFromScript();
   }
   catch (Exception e) {
      Trace.log(Utility.FrameworkLogger,
                Level.SEVERE, 
                LifeCycleListenerImpl.class, 
                "start", 
                e);
   }
}

18.2.3 デスクトップ上でデータベースを初期化する方法

データベースは、同じデータベース・ファイルを使用してiOS、Android、Windows、LinuxおよびMacの各プラットフォームで初期化できます。複雑になる場合は、サード・パーティ製ツールを使用してデータベースをデスクトップで初期化して、アプリケーションのリソースとして応答ファイルをパッケージ化します。

データベースを使用するには、そのデータベースをMAFアプリケーションのApplicationControllerプロジェクトにリソースとして追加します。データベースは、META-INFディレクトリにsample.dbとして保存されているものとします。次の例は、アプリケーションからモバイル・デバイスのファイル・システムにデータベースをコピーしてデータベースへのアクセスを可能にするために追加する必要のあるコードを示しています。

private static void initializeDatabase() throws Exception {
   InputStream sourceStream = null;
   FileOutputStream destinationStream = null;
   try {
      // ApplicationDirectory returns the private read-write sandbox area
      // of the mobile device's file system that this application can access.
      // This is where the database is created
      String docRoot = AdfmfJavaUtilities.getDirectoryPathRoot
                                (AdfmfJavaUtilities.ApplicationDirectory);
      String dbName = docRoot + "/sample.db";

      // Verify whether or not the database exists.
      // If it does, then it has already been initialized 
      // and no furher actions are required
      File dbFile = new File(dbName);
      if (dbFile.exists())
         return;

      // Since the database has been packaged as a resource within
      // the application, the getResourceAsStream method is used 
      sourceStream = Thread.currentThread().getContextClassLoader().
                            getResourceAsStream("META-INF/sample.db");
      destinationStream = new FileOutputStream(dbName);
      byte[] buffer = new byte[1000];
      int bytesRead;
      while ((bytesRead = sourceStream.read(buffer)) != -1) {
         destinationStream.write(buffer, 0, bytesRead);
      }
   }
   finally {
      if (sourceStream != null)
         sourceStream.close();
      if (destinationStream != null)
         destinationStream.close();
   }
}

注意:

例を簡単にするため、前の例ではエラー処理を省略しています。

次の例に示すように、データベース初期化コード(前の例を参照)をLifeCycleListenerImplstartメソッドから起動します。

public void start() {
   try {
      initializeDatabase();
   }
   catch (Exception e) {
      Trace.log(Utility.FrameworkLogger,
                Level.SEVERE, 
                LifeCycleListenerImpl.class, 
                "start", 
                e);
   }
}

18.2.4 コミット処理に関する必知事項

SQLiteデータベースには、SQLスクリプトからの読取り時に文をコミットする、自動コミット機能があります。

Commit文があってもこれは無視されます。各文は、SQLスクリプトから読み込まれる際にコミットされます。この自動コミット機能は、SQLiteデータベースによってデフォルトで提供されます。アプリケーションのパフォーマンスを向上させるには、自動コミットを無効にして、ConnectionsetAutoCommit(false)メソッドを使用することによるcommit文の通常の実行を可能にします。

18.2.5 MAFでのSQLite JDBCドライバの制限

java.sqlパッケージに含まれるResultSetのgetByteメソッドとStatementのexecuteメソッドには制限があり、MAFでのサポートがありません。

java.sqlパッケージからの次のメソッドは、MAF内では制限があるか、サポートされていません。

  • ResultSetgetByteメソッドはサポートされていません。使用した場合は、実行時にこのメソッドからSQLExceptionがスローされます。

  • (ResultSetを戻す文に対してのみtrueを戻すのとは対照的に)Statementexecuteメソッドは常にtrueを戻します。

18.2.6 VACUUMコマンドの使用方法

SQLiteデータベースのサイズは、レコードの削除後も一定のままで、断片化とパフォーマンスの低下の原因になります。VACUUMコマンドを使用して、パフォーマンスの低下を防止してください。

SQLiteデータベースからレコードが削除される場合、そのサイズは変更されません。この一定のサイズにより断片化が発生し、最終的にはパフォーマンスが低下します。パフォーマンスの低下を防ぐには、VACUUMコマンドを定期的に実行します。

注意:

大規模なデータベースに対してVACUUMコマンドを実行すると、長い時間がかかる場合があります(SQLiteが開発されたLinuxコンピュータで1MB当たり約0.5秒)。さらに、実行中は、元のファイルの2倍の一時ディスク領域が使用されます。

VACUUMコマンドは、適切に登録されているLifeCycleListenerの実装から実行する必要があります(「MAFアプリケーションでのライフサイクル・リスナーの使用方法」を参照)。

18.2.7 データベースを暗号化および復号化する方法

SQLiteデータベースはAPIを使用して暗号化できるだけでなく、暗号化のパスワードを指定することもできます。独自のパスワードでデータベースを暗号化する手順、独自のパスワードで暗号化したデータベースを復号化する手順、MAFで生成されたパスワードを使用してデータベースを暗号化する手順、およびデータベースを復号化してMAFで生成されたパスワードを削除する手順を使用してください。

MAFでは、初期またはそれ以降に各種APIを使用して暗号化したSQLiteデータベースを提供できます。一部のAPIでは、データベースの暗号化に独自のパスワードを指定できます。MAFにパスワードを生成させる場合、または(オプションで)管理させる場合は、他のAPIを使用します。

18.2.7.1 独自のパスワードでのデータベースの暗号化

独自のパスワードでデータベースを暗号化する手順を使用してください。

独自のパスワードでデータベースを暗号化するには、次の手順を実行します。

  1. データベース接続を確立します(「データベースへの接続方法」を参照)。

  2. 次のユーティリティ・メソッドを使用して、新しいキーでデータベースを暗号化します。

    AdfmfJavaUtilities.encryptDatabase(connection, "newPassword");

18.2.7.2 独自のパスワードで暗号化されたデータベースの永久的な復号化

独自のパスワードで暗号化したデータベースを復号化する手順を使用してください。

独自のパスワードで暗号化されたデータベースを永久的に復号化するには、次の手順を実行します。

  1. 正しいパスワードを使用して暗号化されたデータベースを開きます。

  2. 次のユーティリティ・メソッドを使用します。

    AdfmfJavaUtilities.decryptDatabase(connection);

注意:

データベースを不正に開いて(不正なパスワードを使用して暗号化されたデータベースを開くなど)、再び暗号化した場合は、以前の正しいパスワード、不正なパスワード、新しいパスワードのいずれでもデータベースのロックを解除できなくなり、取り返しのつかないデータの損失につながります。

18.2.7.3 MAFで生成されたパスワードでのデータベースの暗号化

MAFで生成されたパスワードを使用してデータベースを暗号化する手順を使用してください。

MAF生成のパスワードでデータベースを暗号化するには、次の手順を実行します。

  1. 次のメソッドを使用して、パスワードを生成します。

    GeneratedPassword.setPassword("databasePasswordID", "initialSeedValue");
    

    このメソッドには、暗号化機能が強力なパスワードを生成できるように、一意の識別子および初期シード値の両方が必要です。

  2. 次のように事前に指定したIDを使用して、作成されたパスワードを取得します。

    char[] password = GeneratedPassword.getPassword("databasePasswordID");
    
  3. データベース接続を確立します(「データベースへの接続方法」を参照)。

  4. データベースを次のように暗号化します。

    AdfmfJavaUtilities.encryptDatabase(connection, new String(password));

18.2.7.4 MAFで生成されたパスワードで暗号化されたデータベースの復号化

データベースを復号化してMAFで生成されたパスワードを削除する手順を使用してください。

データベースを復号化してMAF生成のパスワードを削除するには、次の手順を実行します。

  1. 正しいパスワードを次のように取得します。

    char[] password = GeneratedPassword.getPassword("databasePasswordID");
    
  2. データベース接続を確立し、次のようにデータベースを復号化します。

    java.sql.Connection connection =
                        SQLite.JDBCDataSource("jdbc:sqlite:/path/to/database").
                        getConnection(null, new String(password));
    
  3. オプションで、次のメソッドを使用して生成されたパスワードを削除します。

    GeneratedPassword.clearPassword("databasePasswordID");