| Oracle® Mobile Application Framework Oracle Mobile Application Frameworkでのモバイル・アプリケーションの開発 2.3.2 E79300-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 Webサイトのドキュメント・セクションで入手可能な『File Locking And Concurrency In SQLite Version 3』(http://www.sqlite.org/lockingv3.html)を参照してください。
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)を参照してください。
大部分のデータベース・システムは強く型指定をしているのに対して、SQLiteは動的な型指定のため、宣言された型とは無関係にどの列にどの値を格納することもできます。たとえば、数値列に文字列値が間違って格納された場合でも、SQLiteではエラーは戻されません。詳細は、SQLite Webサイトのドキュメント・セクションで入手可能な『Datatypes In SQLite Version 3』(http://www.sqlite.org/datatype3.html)を参照してください。
SQLiteは外部キーをサポートします。これは、外部キー制約を解析および強制します。詳細は、http://www.sqlite.org/foreignkeys.htmlにあるSQLiteサイトのドキュメント・セクションで入手可能な『SQLite Foreign Key Support』を参照してください。
SQLiteはACID準拠であるためトランザクションをサポートしていますが、SQLiteとOracleのトランザクション・サポートの間には基本的な相違点がいくつか存在します。
ネストされたトランザクション: SQLiteでは、ネストされたトランザクションをサポートしません。単一のトランザクションのみを常時アクティブにできます。
コミット: SQLiteは、任意の指定されたデータベースに対し、複数の読取り専用接続または単一の読取り/書込み接続のいずれかを許容します。したがって、同一のデータベースに対して複数の接続がある場合は、データベースの変更を試みる最初の接続のみが成功します。
ロールバック: SQLiteでは、開いているすべてのResultSetsを閉じるまではトランザクションのロールバックができません。
詳細は、SQLite Webサイトのドキュメント・セクションで入手可能な『Distinctive Features of SQLite』(http://www.sqlite.org/different.html)を参照してください。
SQLiteでは、ロールベースまたはユーザーベースの認証はどの形式もサポートしていません。デフォルトで、どのユーザーもファイル内のすべてのデータにアクセスできます。ただし、MAFでは、使用可能な暗号化ルーチンを提供することで、データを保護し、有効な資格証明セットを持たないユーザーによるアクセスを防ぎます。詳細は、「データベースを暗号化および復号化する方法」を参照してください。
MAFには、暗号化されたSQLite 3.8.5データベースが含まれています。
通常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スクリプトを使用して、アプリケーションの起動時にデータベースを初期化します。次の例は、サポートされている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);
}
}
SQLiteデータベースは自己完結型でプラットフォーム間のバイナリ互換性があるため、同じデータベース・ファイルをiOS、Android、Windows、LinuxおよびMac OSの各プラットフォームで使用できます。複雑なケースでは、サード・パーティのツール(MesaSQLite、SQLiteManager、SQLite Database Browserなど)を使用してデスクトップ上でデータベースを初期化し、結果ファイルをアプリケーション内のリソースとしてパッケージ化できます。
データベースを使用するには、そのデータベースを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);
}
}
Commit文があってもこれは無視されます。各文は、SQLスクリプトから読み込まれる際にコミットされます。この自動コミット機能は、SQLiteデータベースによってデフォルトで提供されます。アプリケーションのパフォーマンスを向上させるには、自動コミットを無効にして、ConnectionのsetAutoCommit(false)メソッドを使用することによるcommit文の通常の実行を可能にします。
java.sqlパッケージからの次のメソッドは、MAF内では制限があるか、サポートされていません。
ResultSetのgetByteメソッドはサポートされていません。使用した場合は、実行時にこのメソッドからSQLExceptionがスローされます。
(ResultSetを戻す文に対してのみtrueを戻すのとは対照的に)Statementのexecuteメソッドは常にtrueを戻します。
SQLiteデータベースからレコードが削除される場合、そのサイズは変更されません。この一定のサイズにより断片化が発生し、最終的にはパフォーマンスが低下します。パフォーマンスの低下を防ぐには、VACUUMコマンドを定期的に実行します。
注意:
大規模なデータベースに対してVACUUMコマンドを実行すると、長い時間がかかる場合があります(SQLiteが開発されたLinuxコンピュータで1MB当たり約0.5秒)。さらに、実行中は、元のファイルの2倍の一時ディスク領域が使用されます。
VACUUMコマンドは、適切に登録されているLifeCycleListenerの実装から実行する必要があります(「MAFアプリケーションでのライフサイクル・リスナーの使用方法」を参照)。
MAFでは、初期またはそれ以降に各種APIを使用して暗号化したSQLiteデータベースを提供できます。一部のAPIでは、データベースの暗号化に独自のパスワードを指定できます。MAFにパスワードを生成させる場合、または(オプションで)管理させる場合は、他のAPIを使用します。
独自のパスワードでデータベースを暗号化するには、次の手順を実行します。
データベース接続を確立します(「データベースへの接続方法」を参照)。
次のユーティリティ・メソッドを使用して、新しいキーでデータベースを暗号化します。
AdfmfJavaUtilities.encryptDatabase(connection, "newPassword");
独自のパスワードで暗号化されたデータベースを永久的に復号化するには、次の手順を実行します。
正しいパスワードを使用して暗号化されたデータベースを開きます。
次のユーティリティ・メソッドを使用します。
AdfmfJavaUtilities.decryptDatabase(connection);
警告:
データベースを不正に開いて(不正なパスワードを使用して暗号化されたデータベースを開くなど)、再び暗号化した場合は、以前の正しいパスワード、不正なパスワード、新しいパスワードのいずれでもデータベースのロックを解除できなくなり、取り返しのつかないデータの損失につながります。
MAF生成のパスワードでデータベースを暗号化するには、次の手順を実行します。
次のメソッドを使用して、パスワードを生成します。
GeneratedPassword.setPassword("databasePasswordID", "initialSeedValue");
このメソッドには、暗号化機能が強力なパスワードを生成できるように、一意の識別子および初期シード値の両方が必要です。
次のように事前に指定したIDを使用して、作成されたパスワードを取得します。
char[] password = GeneratedPassword.getPassword("databasePasswordID");
データベース接続を確立します(「データベースへの接続方法」を参照)。
データベースを次のように暗号化します。
AdfmfJavaUtilities.encryptDatabase(connection, new String(password));
データベースを復号化して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");