Gitを翻訳したものです。
引用: https://github.com/tekartik/sqflite/blob/master/sqflite/doc/opening_db.md
目次
データベースのロケーションパスを探す
Sqfliteは、Androidではデータベースのパス、iOSではドキュメントフォルダを使用した基本的なロケーション戦略を提供しています。
両プラットフォームで推奨されています。この場所は getDatabasesPath
を使って取得することができる。
var databasesPath = await getDatabasesPath();
var path = join(databasesPath, dbName);
// ディレクトリが存在することを確認する
try {
await Directory(databasesPath).create(recursive: true);
} catch (_) {}
DB接続
SQLiteデータベースは、パスで記載できる形のファイルです。相対パスの場合、このパスは
getDatabasesPath()
によって得られる、Androidではデフォルトのデータベースディレクトリ、iOSではdocumentsディレクトリが使われます。
var db = await openDatabase('my_db.db');
読み取り/書き込み
データベースを読み書きモードで開くのはデフォルトです。バージョンを指定して実行することができます。
マイグレーション戦略、データベースとそのバージョンを設定することができます。
構成
onConfigure
は、最初に呼び出されるオプションのコールバックです。このコールバックは、データベースの初期化を行います。
カスケード削除のサポートなど
_onConfigure(Database db) async {
// カスケード削除のサポート追加
await db.execute("PRAGMA foreign_keys = ON");
}
var db = await openDatabase(path, onConfigure: _onConfigure);
プリロードデータについて
初回起動時にデータベースをプリロードしておくとよいでしょう。次のいずれかの方法があります。
- 既存のSQLiteファイルをインポートする データベースファイルが存在するかどうかを最初にチェックします。
OnCreate
時にデータを投入します。
_onCreate(Database db, int version) async {
// データベースが作成されたら、テーブルを作成する
await db.execute(
"CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)");
// データを入力する
await db.insert(...);
}
// データベースを開き、バージョンとonCreateコールバックを指定します。
var db = await openDatabase(path,
version: 1,
onCreate: _onCreate);
マイグレーション
データベースのアップグレード(スキーマの変更)を処理するために、Android APIと同様の基本的なバージョン管理機構があります。getVersion
とsetVersion
が公開されていますが、これは使用せず、データベースを開くときに移行を実行する必要があります。
onCreate
, onUpgrade
, onDowngrade
は、バージョンが指定されたときに呼び出される。データベースが存在しない場合、onCreate
が呼び出される。onCreate
が定義されていない場合、oldVersion
の値が0であるonUpgrade
が代わりに呼び出される。データベースが存在し、新しいバージョンが現在のバージョンより高い場合、onUpgrade
が呼び出される。逆に、新しいバージョンが現在のバージョンより低い場合、onDowngrade
が呼び出されます。データベースのバージョンを常にインクリメントすることで、これを回避するようにしてください。ダウングレードの場合、特別な onDatabaseDowngradeDelete
コールバックが存在し、単にデータベースを削除して onCreate
を呼び出し、データベースを作成します。
これら3つのコールバックは、データベースのバージョンが設定される直前に、トランザクション内で呼び出されます。
_onCreate(Database db, int version) async {
// データベースが作成されたら、テーブルを作成する
await db.execute(
"CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)");
}
_onUpgrade(Database db, int oldVersion, int newVersion) async {
// データベースのバージョンが更新されたら、テーブルを変更する
await db.execute("ALTER TABLE Test ADD name TEXT");
}
// onDowngradeに使用される特別なコールバックは、データベースを再作成するために使用されます。
var db = await openDatabase(path,
version: 1,
onCreate: _onCreate,
onUpgrade: _onUpgrade,
onDowngrade: onDatabaseDowngradeDelete);
完全移行例をご覧ください。
接続後のコールバック
便宜上、onOpen
はデータベースのバージョンを設定した後、 openDatabase
が戻る前に呼び出されます。
_onOpen(Database db) async {
// データベースが開かれている場合、そのバージョンを表示する
print('db version ${await db.getVersion()}');
}
var db = await openDatabase(
path,
onOpen: _onOpen,
);
読み取り専用
// データベースを読み取り専用で開く
var db = await openReadOnlyDatabase(path);
破損ファイルの処理
AndroidとiOSでは、破損の処理方法が異なります。
- iOSでは、データベースへの最初のアクセスで失敗します。
- Androidの場合、既存のファイルは削除されます。
既存の挙動を崩さずに一貫性を持たせる方法はまだわかりません。
あるファイルが有効なデータベースファイルであるかどうかを確認する1つの方法は、そのファイルを読み取り専用で開くことだと思われます。
とそのバージョンをチェックします(つまり、sqlite/iOSは非sqliteデータベースの最初のアクセスで非一貫して失敗します)。
これをトップレベルの関数にする前に、動作を検証するためにもっと多くのテストが必要でしょう。
/// ファイルが有効なデータベースファイルであるかどうかをチェックする
///
/// 空のファイルは、有効な空のsqliteファイルです
Future<bool> isDatabase(String path) async {
Database db;
bool isDatabase = false;
try {
db = await openReadOnlyDatabase(path);
int version = await db.getVersion();
if (version != null) {
isDatabase = true;
}
} catch (_) {} finally {
await db?.close();
}
return isDatabase;
}
データベースがロックされる問題の防止
データベースを開くのは一度だけにすることを強くお勧めします。デフォルトでは、データベースはシングルインスタンスとして開かれます。(singleInstance: true
)
singleInstance: false
を使って同じデータベースを何度も開くと、(少なくともAndroidでは)以下のような現象が発生する可能性があります。
android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
それでは、次のようなヘルパークラスについて考えてみましょう。
class Helper {
final String path;
Helper(this.path);
Database _db;
Future<Database> getDb() async {
if (_db == null) {
_db = await openDatabase(path);
}
return _db;
}
}
openDatabase
は非同期なので、openDatabase
が2回呼び出される可能性があり、レースコンディションのリスクがあります。これを修正するには、以下のようにすればよい。
class Helper {
final String path;
Helper(this.path);
Future<Database> _db;
Future<Database> getDb() {
if (_db == null) {
_db = openDatabase(path);
}
return _db;
}
}
もし、openDatabase
の後に、ユーザーが使用できるようにするまでに長い操作がある場合は、次のようにします。
この場合、コードを保護する必要があります (ここでは _initDb()
メソッドをプライベートにして、同時アクセスから保護します)。
class Helper {
final String path;
Helper(this.path);
Future<Database> _db;
Future<Database> getDb() {
_db ??= _initDb();
return _db;
}
// Guaranteed to be called only once.
Future<Database> _initDb() async {
final db = await openDatabase(this.path);
// do "tons of stuff in async mode"
return db;
}
}
例外を解決する
データベースを開くときに例外が発生する場合。
- トラブルシューティングの項を参照してください。
- データベースを作成するディレクトリが存在することを確認する。
- データベースのパスが、既存のデータベース(または何もない)を指していることを確認してください。
sqliteデータベースでないファイルでないこと。 - オープンコールバック(onCreate/onUpgrade/onConfigure/onOpen)で予想される例外を処理する。
