👻
FlutterのSQLiteでインメモリDBを使う
これはなに?
FlutterでSQLiteを使う際に、パフォーマンス向上を目的にDBデータをすべてインメモリにしてしまおうという試みです。
SQLiteをインメモリDBで使うとは
Flutterで開発するにあたり、SQLiteは非常に使いやすいですよね。
assets以下にdbファイルを配置すればマスターデータをアプリに組み込んだりできますし、気軽にユーザーデータを保存できるのでおすすめです。
SQLiteは標準でもパフォーマンスは高いですが、MBレベルの軽量なマスターデータかつ、頻繁に検索が走りパフォーマンスに影響があるような状況でしたら、DBのデータをすべてメモリ上に乗せた状態でselect等を行うことでさらに高速に処理を行えます。
具体的にどうするか?
やり方は非常にかんたんで、起動時にassets以下のDBを読み込んで、インメモリDBを作成してコピーすればあとは通常のDBと同じように扱えます。
import 'package:sqflite/sqflite.dart';
class InMemoryDBProvider {
// コンストラクタを呼んでシングルトンで構成
InMemoryDBProvider._();
static final InMemoryDBProvider instance = InMemoryDBProvider._();
Database _database;
bool initializeStarted = false;
Future<Database> get database async {
if (_database != null) return _database;
if (initializeStarted) {
while (_database == null) {
await Future.delayed(Duration(milliseconds: 200));
}
} else {
initializeStarted = true;
_database = await _initDatabase();
}
return _database;
}
// 初期化
_initDatabase() async {
// assets以下のDBを読み込み
final String databasesPath = await getDatabasesPath();
final String path = join(databasesPath, "on_memory_db.sqlite3");
var exists = await databaseExists(path);
if (!exists) {
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
ByteData data = await rootBundle.load(join("assets", "on_memory_db.sqlite3"));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(path).writeAsBytes(bytes, flush: true);
}
// assets以下のテーブルの内容をインメモリDBに乗せる
var _db;
try {
_db = await openDatabase(inMemoryDatabasePath);
await _db.rawQuery("ATTACH DATABASE ? as tmpDb", [path]);
await _db.rawQuery("CREATE TABLE my_items AS SELECT * FROM tmpDb.my_items");
await _db.rawQuery("DETACH DATABASE tmpDb");
} catch (e) {
print("Error creating database");
_db.close();
_db = null;
throw (e);
}
return _db;
}
Future close() async => (await instance.database).close();
}
// モデル
class MyItem {
num id;
String name;
MyItem({
this.id,
this.name,
});
Map<String, dynamic> toMap() => {
"id": id,
"name": name,
};
factory MyItem.fromMap(Map<String, dynamic> json) => MyItem(
id: json["id"],
name: json["name"],
);
static Future<MyItem?> findById(num id) async {
final db = await InMemoryDBProvider.instance.database;
List<Map> maps = await db.query("my_items", where: 'id = ?', whereArgs: [id]);
if (maps.length > 0) {
return MyItem.fromMap(maps.first);
}
return null;
}
}
注意点
起動時にassets以下のファイルDBから読み込んで使うようになっているので、create/update/deleteされた内容は永続化されません。
追加の対応を行えばそれらを永続化できますが、マスターデータなど検索をヘビーに行う状況が多いと思うのでそういった場合に活用ください。
自分はこのInMemoryDBProviderと(通常のファイルDBとしての)UserDBProviderをアプリ内で使い分けるようにしています。
誰かのお役に立てば幸いです :bow:
Discussion