ヘッダーをスキップ

Oracle® Fusion Middleware Oracle Mobile Application Frameworkでのモバイル・アプリケーションの開発 2.0.1 E57592-01 |
| 目次

前 | 次 |

# 10 ローカル・データベースの使用方法

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

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

- 第10.1項「ローカルSQLiteデータベースの使用方法の概要」

- 第10.2項「ローカルSQLiteデータベースの使用方法」

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

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

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

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

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

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

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

ローカルのSQLiteデータベースの使用例は、開発コンピュータの`jdev_install``/jdeveloper/jdev/extensions/oracle.maf/Samples`ディレクトリ内の`PublicSamples.zip`ファイルにあるStockTrackerというMAFサンプル・アプリケーションを参照してください。詳細は、第10.2.8項「StockTrackerサンプル・アプリケーションに関する必知事項」を参照してください。

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

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

- 同時実行性

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

- データ型

- 外部キー

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

- 認証

詳細は、次を参照してください。

- SQLite Webサイト(`http://www.sqlite.org/docs.html`)のドキュメント・セクション

- SQLite Webサイトのドキュメント・セクションで入手可能な『Limits In SQLite』(`http://www.sqlite.org/limits.html`)

#### 10.1.1.1 同時実行性

SQLiteデータベースの単一インスタンスは、常に、単一の読取り/書込み接続または複数の読取り専用接続のいずれかを持つことができます。

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

#### 10.1.1.2 SQLのサポートおよび解釈

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

- `RIGHT OUTER JOIN`

- `FULL OUTER JOIN`

- `GRANT`

- `REVOKE`

詳細は、SQLite Webサイトのドキュメント・セクションで入手可能な『SQL Features That SQLite Does Not Implement』(`http://www.sqlite.org/omitted.html`)を参照してください。

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

#### 10.1.1.3 データ型

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

#### 10.1.1.4 外部キー

SQLiteでは、外部キーをサポートしています。外部キー制約を解析および適用します。詳細は、`http://www.sqlite.org/foreignkeys.html`にあるSQLiteサイトのドキュメント・セクションで入手可能な『SQLite Foreign Key Support』を参照してください。

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

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

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

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

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

詳細は、SQLite Webサイトのドキュメント・セクションで入手可能な『Distinctive Features of SQLite』(`http://www.sqlite.org/different.html`)を参照してください。

#### 10.1.1.6 認証

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

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

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

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

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

- SQLスクリプトを使用してデータベースを初期化する方法またはデスクトップ上でデータベースを初期化する方法

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

- VACUUMコマンドの使用方法

### 10.2.1 データベースへの接続方法

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

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

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

例10-1 暗号化されていないデータベースとの接続

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

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

例10-2 暗号化されたデータベースとの接続

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

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

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

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

通常、アプリケーションの起動時にSQLスクリプトを使用してデータベースを初期化できます。例10-3は、サポートされたSQL構文(第10.1.1.2項「SQLのサポートおよび解釈」で説明)の一部を示すSQL初期化スクリプトを示しています。この構文では、`DROP TABLE`、`CREATE TABLE`および`INSERT`の各コマンドと`NUMBER`および`VARCHAR2`の各データ型が使用されています。

例10-3 SQL初期化スクリプト

    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`として保存されているものとします。例10-4は、SQLスクリプトを解析し、その文を実行するために追加する必要のあるコードが示されています。

例10-4 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();
       }
    }

注意: 例10-4では、わかりやすくするためにエラー処理が省略されました。

例10-5で示すとおり、`LifeCycleListenerImpl`の`start`メソッドからデータベース初期化コード(例10-4を参照)を起動します。

例10-5 データベース初期化コードの起動

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

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

SQLiteデータベースは自己完結型でプラットフォーム間のバイナリ互換性があるため、同じデータベース・ファイルをiOS、Android、Windows、LinuxおよびMac OSの各プラットフォームで使用できます。複雑なケースでは、サード・パーティのツール(MesaSQLite、SQLiteManager、SQLite Database Browserなど)を使用してデスクトップ上でデータベースを初期化し、結果ファイルをアプリケーション内のリソースとしてパッケージ化できます。

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

例10-6 デスクトップ上のデータベースの初期化

    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();
       }
    }

注意: 例10-6では、わかりやすくするためにエラー処理が省略されました。

例10-7で示すとおり、`LifeCycleListenerImpl`の`start`メソッドからデータベース初期化コード(例10-6を参照)を起動します。

例10-7 データベース初期化コードの起動

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

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

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

### 10.2.5 MAFでのSQLite JDBCドライバの制限

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

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

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

### 10.2.6 VACUUMコマンドの使用方法

レコードがSQLiteデータベースから削除された場合、そのサイズは変更されません。このことは断片化の原因となり、最終的には、パフォーマンスが低下します。`VACUUM`コマンドを定期的に実行することによって、このことを回避できます。

注意: `VACUUM`は、大規模データベースに対して実行される場合、かなりの時間がかかる場合があります(SQLiteがデプロイされているLinuxコンピュータ上ではMB当たり約0.5秒)。また、実行中は、元のファイルの最大2倍の一時ディスク領域を使用する可能性があります。

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

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

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

独自のパスワードでデータベースを暗号化するには:

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

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

       AdfmfJavaUtilities.encryptDatabase(connection, "newPassword");

独自のパスワードで暗号化されたデータベースを永続的に復号化するには:

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

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

       AdfmfJavaUtilities.decryptDatabase(connection);

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

MAFによって生成されたパスワードを使用してデータベースを暗号化するには:

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

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

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

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

       char[] password = GeneratedPassword.getPassword("databasePasswordID");

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

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

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

データベースを復号化し、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");

### 10.2.8 StockTrackerサンプル・アプリケーションに関する必知事項

StockTrackerサンプル・アプリケーションは、カスタムSQLiteデータベース・ファイル(このアプリケーション内にパッケージ化されています)を使用します。データベース・ファイルには、4つの株に関する情報が含まれる4つのレコードのある表が含まれています。アプリケーションは、アクティブ化されると、この表からデータを読み取り、4つの株を表示します。株に関する情報は、CRUD操作の対象にできます。株は、ユーザー・インタフェースを介して、作成、並替え、更新および削除できます。株の並替えを含むすべてのCRUD操作が、SQLiteデータベースで更新されます。