プライマリ・コンテンツに移動

Oracle Enterprise Pack for Eclipse Oracle Mobile Application Framework (OEPE Edition)でのモバイル・アプリケーションの開発 リリース2.1.3 E67371-01 |
| 目次

前 | 次 |

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

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

この章には次の項が含まれます:

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

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

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

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

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

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

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

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

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

ローカルSQLiteデータベースの使用例は、StockTrackerと呼ばれるMAFのサンプル・アプリケーションを参照してください(「ファイル」→「新」→「MAFサンプル」にあります)。詳細は、第20.2.8項「StockTrackerサンプル・アプリケーションに関する必知事項」を参照してください。

### 20.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`)

#### 20.1.1.1 同時実行性

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

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

#### 20.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`)を参照してください。

#### 20.1.1.3 データ型

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

#### 20.1.1.4 外部キー

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

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

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

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

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

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

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

#### 20.1.1.6 認証

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

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

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

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

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

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

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

- VACUUMコマンドの使用方法

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

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

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

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

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

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

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

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

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

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

通常、アプリケーションの起動時にSQLスクリプトを使用してデータベースを初期化できます。次の例は、サポートされたSQL構文(第20.1.1.2項「SQLのサポートおよび解釈」で説明)の一部を示すSQL初期化スクリプトを示しています。この構文では、`DROP TABLE`、`CREATE 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アプリケーションのアプリケーション・プロジェクトにリソースとして追加します。サンプル・スクリプトは、`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();
       }
    }

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

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

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

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

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

データベースを使用するには、そのデータベースをMAFアプリケーションのアプリケーション・プロジェクトにリソースとして追加します。データベースは、`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();
       }
    }

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

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

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

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

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

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

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

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

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

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

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

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

通常、`VACUUM`コマンドは、適切に登録されている`LifeCycleListener`実装から実行されます。

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

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

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

1. データベース接続を確立します(第20.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. データベース接続を確立します(第20.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");

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

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