Oracle® Mobile Application Framework Oracle Mobile Application Frameworkでのモバイル・アプリケーションの開発 2.5.0.0.0 E94553-01 |
|
前 |
次 |
この章の内容は次のとおりです。
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データベースへのアクセス」を参照してください。
SQLiteは、埋込みデータベース・システムとして使用する設計になっており、通常は1人のユーザーによって使用され、アプリケーションに直接リンクされることも多くあります。他方、エンタープライズ・データベースは、クライアントとサーバーの分散された環境における高い同時実行性を求めて設計されています。
このような違いがあるため、Oracleデータベースと比較して複数の制約が存在します。最も重要な相違点を次に示します。
次の項も参照してください。
SQLite Webサイト(http://www.sqlite.org/docs.html
)のドキュメント・セクション
SQLite Webサイトのドキュメント・セクションで入手可能な『Limits In SQLite』(http://www.sqlite.org/limits.html
)
SQLiteデータベースの単一インスタンスは、常に、単一の読取り/書込み接続または複数の読取り専用接続のいずれかを持つことができます。ロック・メカニズムが粗いため、SQLiteでは、同一のデータベース・インスタンスに対する複数の読取り/書込み接続のサポートが不可能です。
ロック・メカニズムが粗いため、SQLiteでは、同一のデータベース・インスタンスに対する複数の読取り/書込み接続がサポートされていません。『File Locking And Concurrency In SQLite Version 3』を参照してください(SQLite Webサイトhttp://www.sqlite.org/lockingv3.html
の「Documentation」セクションから入手できます)。
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
)を参照してください。
SQLiteは動的に型指定され、宣言された型とは無関係に、任意の列に任意の値を格納できます。
大部分のデータベース・システムは強く型指定をしているのに対して、SQLiteは動的な型指定のため、宣言された型とは無関係にどの列にどの値を格納することもできます。たとえば、数値列に文字列値が間違って格納された場合でも、SQLiteではエラーは戻されません。『Datatypes In SQLite Version 3』を参照してください(SQLite Webサイトhttp://www.sqlite.org/datatype3.html
の「Documentation」セクションから入手できます)。
SQLiteは外部キーをサポートします。これは、外部キー制約を解析および強制します。『SQLite Foreign Key Support』を参照してください(SQLiteサイトhttp://www.sqlite.org/foreignkeys.html
の「Documentation」セクションから入手できます)。
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」セクションから入手できます)。
SQLiteは、ロールベースの認証とユーザーベースの認証のどちらもサポートしていません。ユーザーはファイル内のすべてのデータにアクセス可能です。データの保護にはMAF暗号化を使用できます。
SQLiteでは、ロールベースまたはユーザーベースの認証はどの形式もサポートしていません。デフォルトで、どのユーザーもファイル内のすべてのデータにアクセスできます。ただし、MAFでは、使用可能な暗号化ルーチンを提供することで、データを保護し、有効な資格証明セットを持たないユーザーによるアクセスを防ぎます。「データベースを暗号化および復号化する方法」を参照してください。
MAFには、暗号化されたSQLite 3.8.5データベースが含まれています。
通常SQLiteを使用する場合は、次のことを知っておく必要があります。
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
がスローされます。
アプリケーションの起動時に、SQLスクリプトによってデータベースが初期化されます。このスクリプトは、MAFアプリケーションのApplicationControllerプロジェクトに、リソースとして追加する必要があります。
SQLスクリプトを使用して、アプリケーションの起動時にデータベースを初期化します。次の例は、サポートされているSQL構文(「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アプリケーションの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(); } }
注意:
例を簡単にするため、前の例ではエラー処理を省略しています。
次の例に示すように、データベース初期化コード(前の例を参照)をLifeCycleListenerImpl
のstart
メソッドから起動します。
public void start() { try { initializeDatabaseFromScript(); } catch (Exception e) { Trace.log(Utility.FrameworkLogger, Level.SEVERE, LifeCycleListenerImpl.class, "start", e); } }
データベースは、同じデータベース・ファイルを使用して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(); } }
注意:
例を簡単にするため、前の例ではエラー処理を省略しています。
次の例に示すように、データベース初期化コード(前の例を参照)をLifeCycleListenerImpl
のstart
メソッドから起動します。
public void start() { try { initializeDatabase(); } catch (Exception e) { Trace.log(Utility.FrameworkLogger, Level.SEVERE, LifeCycleListenerImpl.class, "start", e); } }
SQLiteデータベースには、SQLスクリプトからの読取り時に文をコミットする、自動コミット機能があります。
Commit
文があってもこれは無視されます。各文は、SQLスクリプトから読み込まれる際にコミットされます。この自動コミット機能は、SQLiteデータベースによってデフォルトで提供されます。アプリケーションのパフォーマンスを向上させるには、自動コミットを無効にして、Connection
のsetAutoCommit(false)
メソッドを使用することによるcommit
文の通常の実行を可能にします。
java.sql
パッケージに含まれるResultSetのgetByte
メソッドとStatementのexecute
メソッドには制限があり、MAFでのサポートがありません。
java.sql
パッケージからの次のメソッドは、MAF内では制限があるか、サポートされていません。
ResultSet
のgetByte
メソッドはサポートされていません。使用した場合は、実行時にこのメソッドからSQLException
がスローされます。
(ResultSet
を戻す文に対してのみtrue
を戻すのとは対照的に)Statement
のexecute
メソッドは常にtrue
を戻します。
SQLiteデータベースのサイズは、レコードの削除後も一定のままで、断片化とパフォーマンスの低下の原因になります。VACUUM
コマンドを使用して、パフォーマンスの低下を防止してください。
SQLiteデータベースからレコードが削除される場合、そのサイズは変更されません。この一定のサイズにより断片化が発生し、最終的にはパフォーマンスが低下します。パフォーマンスの低下を防ぐには、VACUUM
コマンドを定期的に実行します。
注意:
大規模なデータベースに対してVACUUM
コマンドを実行すると、長い時間がかかる場合があります(SQLiteが開発されたLinuxコンピュータで1MB当たり約0.5秒)。さらに、実行中は、元のファイルの2倍の一時ディスク領域が使用されます。
VACUUM
コマンドは、適切に登録されているLifeCycleListener
の実装から実行する必要があります(「MAFアプリケーションでのライフサイクル・リスナーの使用方法」を参照)。
SQLiteデータベースはAPIを使用して暗号化できるだけでなく、暗号化のパスワードを指定することもできます。独自のパスワードでデータベースを暗号化する手順、独自のパスワードで暗号化したデータベースを復号化する手順、MAFで生成されたパスワードを使用してデータベースを暗号化する手順、およびデータベースを復号化してMAFで生成されたパスワードを削除する手順を使用してください。
MAFでは、初期またはそれ以降に各種APIを使用して暗号化したSQLiteデータベースを提供できます。一部のAPIでは、データベースの暗号化に独自のパスワードを指定できます。MAFにパスワードを生成させる場合、または(オプションで)管理させる場合は、他のAPIを使用します。
独自のパスワードでデータベースを暗号化する手順を使用してください。
独自のパスワードでデータベースを暗号化するには、次の手順を実行します。
データベース接続を確立します(「データベースへの接続方法」を参照)。
次のユーティリティ・メソッドを使用して、新しいキーでデータベースを暗号化します。
AdfmfJavaUtilities.encryptDatabase(connection, "newPassword");
独自のパスワードで暗号化したデータベースを復号化する手順を使用してください。
独自のパスワードで暗号化されたデータベースを永久的に復号化するには、次の手順を実行します。
正しいパスワードを使用して暗号化されたデータベースを開きます。
次のユーティリティ・メソッドを使用します。
AdfmfJavaUtilities.decryptDatabase(connection);
注意:
データベースを不正に開いて(不正なパスワードを使用して暗号化されたデータベースを開くなど)、再び暗号化した場合は、以前の正しいパスワード、不正なパスワード、新しいパスワードのいずれでもデータベースのロックを解除できなくなり、取り返しのつかないデータの損失につながります。
MAFで生成されたパスワードを使用してデータベースを暗号化する手順を使用してください。
MAF生成のパスワードでデータベースを暗号化するには、次の手順を実行します。
次のメソッドを使用して、パスワードを生成します。
GeneratedPassword.setPassword("databasePasswordID", "initialSeedValue");
このメソッドには、暗号化機能が強力なパスワードを生成できるように、一意の識別子および初期シード値の両方が必要です。
次のように事前に指定したIDを使用して、作成されたパスワードを取得します。
char[] password = GeneratedPassword.getPassword("databasePasswordID");
データベース接続を確立します(「データベースへの接続方法」を参照)。
データベースを次のように暗号化します。
AdfmfJavaUtilities.encryptDatabase(connection, new String(password));
データベースを復号化してMAFで生成されたパスワードを削除する手順を使用してください。
データベースを復号化してMAF生成のパスワードを削除するには、次の手順を実行します。
正しいパスワードを次のように取得します。
char[] password = GeneratedPassword.getPassword("databasePasswordID");
データベース接続を確立し、次のようにデータベースを復号化します。
java.sql.Connection connection = SQLite.JDBCDataSource("jdbc:sqlite:/path/to/database"). getConnection(null, new String(password));
オプションで、次のメソッドを使用して生成されたパスワードを削除します。
GeneratedPassword.clearPassword("databasePasswordID");