🐕
【Flutter】SQLiteの基本的な使用方法
使用するライブラリ
データベースの基本操作
SQLite では、データは「テーブル」という形で管理されます。テーブルは以下のような特徴があります:
- 表のような形式でデータを保存
- 列(カラム)に名前とデータ型を設定
- 行(レコード)としてデータを保存
例えば、果物を管理するテーブルは以下のようになります:
id | name(名前) | type(種類) | price(価格) |
---|---|---|---|
1 | りんご | 果物 | 100 |
2 | みかん | 果物 | 80 |
基本的なデータベース操作
1. テーブルの作成
テーブルを作成する時は、各列の名前とデータ型を指定します。
// テーブルを作成する例
// 以下の列を持つテーブルを作成:
// - id: 自動で増える数値のID(主キー)
// - name: 商品名を入れる列
// - type: 種類を入れる列
// - price: 価格を入れる列
await db.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, // NULLを許可しない
type TEXT,
price INTEGER DEFAULT 0 // デフォルト値を設定
)
''');
2. データの追加
データを追加する方法は 2 つあります:
insert メソッドを使う方法(推奨)
// データを追加する例
// Mapの形式でデータを指定します
int recordId = await db.insert('products', {
'name': 'りんご',
'type': '果物',
'price': 100
});
SQL 文を直接書く方法
// SQLを直接書いてデータを追加
await db.rawInsert(
'INSERT INTO products(name, type, price) VALUES (?, ?, ?)',
['りんご', '果物', 100]
);
3. データの検索
データを検索する方法も 2 つあります:
query メソッドを使う方法(推奨)
// 全てのデータを取得
var allProducts = await db.query('products');
// 特定の列だけを取得
var nameAndPrice = await db.query(
'products',
columns: ['name', 'price']
);
// 条件を指定して取得(100円以上の商品)
var expensiveProducts = await db.query(
'products',
where: 'price >= ?',
whereArgs: [100],
orderBy: 'price DESC' // 価格の高い順に並べ替え
);
SQL 文を直接書く方法
// SQLを直接書いて検索
var products = await db.rawQuery(
'SELECT * FROM products WHERE price >= ?',
[100]
);
4. データの更新
// 特定の商品の価格を更新
var count = await db.update(
'products',
{'price': 150}, // 新しい値
where: 'name = ?', // 更新する条件
whereArgs: ['りんご']
);
print('$count件のデータを更新しました'); // 更新された件数が返ります
5. データの削除
// 条件を指定して削除
var count = await db.delete(
'products',
where: 'price < ?',
whereArgs: [50] // 50円未満の商品を削除
);
// テーブルの全データを削除
await db.delete('products');
トランザクション処理
複数の処理をまとめて行う時に使います。以下のような特徴があります:
- 全ての処理が成功するか、全て失敗するか(途中半端な状態にならない)
- エラーが発生すると自動的に処理が巻き戻される
- データの整合性を保つのに重要
// トランザクションの例
await db.transaction((txn) async {
try {
// 新商品を追加
await txn.insert('products', {
'name': 'パイナップル',
'type': '果物',
'price': 200
});
// 在庫切れ商品を削除
await txn.delete(
'products',
where: 'price = ?',
whereArgs: [0]
);
} catch (e) {
print('エラーが発生しました: $e');
// ここでエラーが発生すると、
// 上記で行った追加と削除が取り消されます
rethrow;
}
});
安全な SQL の書き方
SQL インジェクション攻撃を防ぐために、以下のルールを守りましょう:
SQL インジェクションの一例
「taro」に続いて「’ or ‘1’=‘1」が注入(Injection)されたとします。
生成された SQL 文が下記となります。
SELECT * FROM user WHERE id = ‘taro’ or ‘1’=‘1’
上記の SQL 文にはデータを抽出する条件が 2 つあります。
-
1 つ目は ID が「taro」と合致した場合です。この場合は「taro」の情報が出力されます。ここまでは前述した基本の動きと同じです。
-
2 つ目は‘1’=‘1’が成立した場合です。ただし、‘1’=‘1’は当然なことなのですべての場合においても成立してしまいます。そのため、この部分によってデータベースに格納されているすべてのデータが出力されます。
すなわち情報の漏えいです。
参照:https://www.shadan-kun.com/waf_websecurity/sql_injection/
良い例
// 値は必ず「?」を使ってプレースホルダーで設定
var products = await db.rawQuery(
'SELECT * FROM products WHERE price <= ? AND type = ?',
[100, '果物']
);
悪い例
// 文字列結合で値を直接埋め込むのは危険!
var price = '100';
var type = '果物';
// 絶対にこうしないでください!
var products = await db.rawQuery(
'SELECT * FROM products WHERE price <= $price AND type = "$type"'
);
データベース管理用の便利な機能
- テーブルの存在確認
- 名前の一覧で確認
上記を行いたい場合に重要です。
テーブルの存在確認
// テーブルが存在するか確認する関数
Future<bool> tableExists(DatabaseExecutor db, String table) async {
var count = firstIntValue(await db.query(
'sqlite_master',
columns: ['COUNT(*)'],
where: 'type = ? AND name = ?',
whereArgs: ['table', table]
));
return count > 0;
}
// 使用例
if (await tableExists(db, 'products')) {
print('productsテーブルは存在します');
} else {
print('productsテーブルは存在しません');
}
テーブル一覧の取得
// データベース内の全テーブル名を取得する関数
Future<List<String>> getTableNames(DatabaseExecutor db) async {
var tableNames = (await db.query(
'sqlite_master',
where: 'type = ?',
whereArgs: ['table']
))
.map((row) => row['name'] as String)
.toList(growable: false)
..sort();
return tableNames;
}
// 使用例
var tables = await getTableNames(db);
print('データベース内のテーブル: $tables');
まとめ
SQLite の基本的な使い方について説明しました:
- テーブルの作成:データの構造を定義
- データの追加:新しいレコードを追加
- データの検索:条件に合うデータを取得
- データの更新:既存のデータを変更
- データの削除:不要なデータを削除
- トランザクション:複数の処理を安全に実行
重要なポイント:
- データ型を適切に選択する
- SQL インジェクション対策を忘れない
- エラー処理を適切に行う
- 大量データの場合はトランザクションを使用する
これらの基本を押さえることで、安全で効率的なデータベース操作が可能になります。
参照
Discussion