Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
05-16 20:20
관리 메뉴

nomad-programmer

[Programming/Flutter] SQLite를 이용한 데이터 처리 본문

Programming/Flutter

[Programming/Flutter] SQLite를 이용한 데이터 처리

scii 2020. 10. 24. 14:50

로컬 디바이스에 많은 데이터를 저장하고 쿼리를 요청해야 한다면, 로컬 파일이나 키-값 저장소 대신 데이터베이스를 사용하는 것이 좋다. 일반적으로 데이터베이스는 다른 로컬 솔루션보다 더 빠른 쓰기, 수정, 읽기 성능을 제공한다.

Flutter 앱은 sqflite 플러그인을 통해 SQLite 데이터베이스를 사용할 수 있다. 

sqflite dart
INTEGER int
REAL num
TEXT String
BLOB Uint8List
import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

void main() async {
  // runApp()이 없기때문에 에러 발생한다. 그래서 아래의 코드를 추가해야 한다.
  WidgetsFlutterBinding.ensureInitialized();

  // 데이터베이스 경로 지정. 참고로 path패키지의 join함수를 사용해야 각 플랫폼마다의
  // 경로가 제대로 생성됐는지 보장할 수 있는 가장 좋은 방법이다.
  String dbPath = join(await getDatabasesPath(), 'person_database.db');
  // 데이터베이스 파일이 존재할 경우, 삭제한다.
  if (await databaseExists(dbPath)) {
    await deleteDatabase(dbPath);
  }
  final Future<Database> database = openDatabase(
    dbPath,
    // 데이터베이스가 처음 생성될 때, person을 저장하기 위한 테이블을 생성한다.
    onCreate: (db, version) {
      return db.execute(
        'CREATE TABLE person(id INTEGER PRIMARY KEY AUTOINCREMENT, age INTEGER, name TEXT)',
      );
    },
    // 비전 설정. onCreate 함수에서 수행되며 데이터베이스 업그레이드와 다운그레이드를
    // 수행하기 위한 경로를 제공한다.
    version: 1,
  );

  Future<void> insertPerson(Person person) async {
    // 데이터베이스 reference를 얻는다.
    final Database db = await database;

    // person 테이블에 데이터를 추가한다. 만약 동일한 person이 존재한다면,
    // ConflictAlgorithm이 replace이므로 데이터가 덮어씌워진다.
    await db.insert('person', person.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  Future<List<Person>> persons() async {
    // 데이터베이스 reference를 얻는다.
    final Database db = await database;

    // 모든 person을 얻기 위해 DB에 질의한다.
    final List<Map<String, dynamic>> maps = await db.query('person');

    // List<Map<String, dynamic>>>를 List<Person>으로 변환한다.
    return List.generate(maps.length, (i) {
      return Person(
        id: maps[i]['id'],
        age: maps[i]['age'],
        name: maps[i]['name'],
      );
    });
  }

  Future<void> updatePerson(Person person) async {
    // 데이터베이스의 reference를 얻는다.
    final Database db = await database;

    // 주어진 Person을 수정한다.
    await db.update(
      'person',
      person.toMap(),
      // Person의 id가 일치하는 지 확인
      where: "id = ?",
      // Person의 id를 whereArg로 넘겨 SQL injection을 방지한다.
      whereArgs: [person.id],
    );
  }

  Future<void> deletePerson(int id) async {
    // 데이터베이스 reference를 얻는다.
    final Database db = await database;

    // 데이터베이스에서 Person을 삭제한다.
    await db.delete(
      'person',
      where: "id = ?",
      // Person의 id를 where의 인자로 넘겨 SQL injection을 방지한다.
      whereArgs: [id],
    );
  }

  Future<Person> getId(int age, String name) async {
    // 데이터베이스 reference를 얻는다.
    final Database db = await database;

    List<Map<String, dynamic>> result = await db.query(
      'person',
      where: "age = ? AND name = ?",
      whereArgs: [age, name],
    );

    // 위의 result와 같다.
    // final result = await db.rawQuery(
    //     'SELECT * FROM person WHERE age = ? AND name = ?', [age, name]);

    return Person.fromJson(result[0]);
  }

  Person john = Person(age: 25, name: 'John');

  // 데이터베이스에 person을 추가한다.
  await insertPerson(john);

  // person 목록을 출력한다. (현재 john만 존재한다)
  print(await persons());

  // john의 나이틑 수정한 뒤 데이터베이스에 저장한다.
  john = Person(
    id: (await getId(john.age, john.name)).id,
    age: john.age + 3,
    name: john.name,
  );
  await updatePerson(john);

  // john의 수정된 정보 출력
  print(await persons());

  // john을 데이터베이스에서 제거한다.
  await deletePerson(john.id);

  // person 목록을 출력 (현재 비어있다)
  print(await persons());
}

class Person {
  final int id;
  final int age;
  final String name;

  Person({this.id, this.age, this.name});

  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(
      id: json['id'],
      age: json['age'],
      name: json['name'],
    );
  }

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'id': id,
      'age': age,
      'name': name,
    };
  }

  // 각 person 정보를 보기 쉽도록 toString 메소드를 오버라이딩하였다.
  @override
  String toString() {
    return 'Person{id: $id, name: $name, age: $age}';
  }
}

/* 결과

I/flutter (15523): [Person{id: 1, name: John, age: 25}]
I/flutter (15523): [Person{id: 1, name: John, age: 28}]
I/flutter (15523): []

*/
WHERE 절에 인자를 전달할 때에는 항상 whereArgs를 사용하는 것이 안전하다. 그 이유는, SQL injection 공격으로부터 보호할 수 있기 때문이다.

where: "id = ${person.id}" 와 같은 String interpolation 문법은 사용하지 안된다.

SQL 문법만 알고 있다면 쉽게 사용할 수 있다. 더 많은 Flutter sqflite 정보를 알고 싶다면 아래의 링크로 가서 예제를 살펴보면 되겠다.

pub.dev/packages/sqflite

 

sqflite | Flutter Package

Flutter plugin for SQLite, a self-contained, high-reliability, embedded, SQL database engine.

pub.dev

flutter-ko.dev/docs/cookbook/persistence/sqlite

 

SQLite에 데이터 저장하기

로컬 디바이스에 많은 데이터를 저장하고 쿼리를 요청해야 한다면, 로컬 파일이나 키-값 저장소 대신 데이터베이스를사용해보세요. 일반적으로 데이터베이스는 다른 로컬 솔루션보다 더 빠른

flutter-ko.dev

Comments