[Flutter] SQLite 사용하기

건전한건전지·2022년 9월 21일

회사 프로젝트를 진행하면서 오프라인 모드 지원에 대한 요구사항이 있었다.
대부분 데이터는 서버에서 받아오기 때문에 오프라인 지원을 위해서 받아온 데이터를 디바이스에 저장할 필요가 있었다. 단순 데이터라면 앱 내부 저장소를 사용해도 되지만 구조체를 저장하기에는 무리가 있다.

이번 포스트에서는 sqflite를 사용해서 플러터에서 임베디드 DBMS를 어떻게 사용하는지 알아보려한다.

패키지 설치

dependencies:
  sqflite: ^2.1.0

본 포스트에서는 2.1.0로 작성 시점 최신버전을 사용했다.


사용법

sqlite를 사용하려면 쿼리문에 대한 사진지식이 필요하다. 본 포스트에서는 설명하지 않고 사용법만 알아볼 것이다.

DB 파일 열기

var db = await openDatabase('my_db.db');
await db.close();

'my_db.db' 파일을 불러온다. Database 클래스를 리턴한다.
수동으로 db파일을 닫으려면 db.close() 매소드를 사용한다.

안드로이드는 default database directory, IOS는 document directory를 기본 경로로 한다.
경로는 getDatabasesPath() 함수를 통해 조회 할 수 있다.


쿼리 작성

쿼리 작성에는 두 가지 방법이 있다.
쿼리를 직접 작성하는 rawQueryhelper를 사용하는 방법이 있다.
공통적인 부분을 알아본 후 간단한 예제와 함께 알아보자.

공통

transaction

await db.transaction((txn) async {
  await txn.insert('my_table', {'name': 'my_name'});
  await txn.delete('my_table', where: 'name = ?', whereArgs: ['cat']);
});

트랜젝션은 'all or nothing'으로 동작한다. 트랜젝션 안의 하나의 쿼리라도 실패한다면 에러를 던지고 모두 롤백된다.
트랜젝션 안에서는 db 대신 매개변수 txn을 사용해서 쿼리를 실행한다. rawQuery, helper 모두 사용 가능하다.

batch

batch = db.batch();
batch.insert('Test', {'name': 'item'});
batch.update('Test', {'name': 'new_item'}, where: 'name = ?', whereArgs: ['item']);
batch.delete('Test', where: 'name = ?', whereArgs: ['item']);
results = await batch.commit();

batch는 여러 sql 구문을 순차적으로 저장해뒀다가 실행할 때 사용한다.
기본적으로 batch는 하나의 쿼리가 실패하면 일괄 종료된다. 이를 막으려면 continueOnError를 true로 설정한다.

batchtransaction 안에서 사용할 경우 transaction이 커밋될 때까지 batch는 커밋되지 않는다!


rawQuery

rawQuery를 작성할 때는 가능하면, argument 파라미터를 사용하자.

// good
int recordId = await db.rawInsert('INSERT INTO my_table(name, year) VALUES (?, ?)', ['my_name', 2019]);
// bad
int recordId = await db.rawInsert("INSERT INTO my_table(name, year) VALUES ('my_name', 2019)");

execute

// Create a table
await db.execute('CREATE TABLE my_table (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, type TEXT)');

리턴값을 받지 않는 쿼리를 실행한다.

rawQuery

List<Map> list = await database.rawQuery('SELECT * FROM Test');

테이블의 데이터를 조회한다. 조회한 데이터는 List<Map>의 형태로 반환된다.

조회한 데이터 리스트와 리스트 아이템은 read only다.
데이터를 추가, 삭제, 수정해야한다면

list = List.from(list)
list.add(<String, Object?>{'name': 'some data'});
//
var map = list.first;
map = Map.from(map);
map['name'] = 'other';

위 처럼 새로운 객체를 생성해서 사용하자.

rawInsert

int id1 = await database.rawInsert(
  'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');

레코드를 테이블에 추가한다. 추가한 후 int 값으로 레코드 id를 리턴한다.

rawUpdate

int count = await database.rawUpdate(
  'UPDATE Test SET name = ?, value = ? WHERE name = ?',
  ['updated name', '9876', 'some name']);

데이터를 변경하고 변경된 데이터의 갯수를 리턴한다.

rawDelete

int count = await database
  .rawDelete('DELETE FROM Test WHERE name = ?', ['another name']);

데이터를 삭제하고 삭제된 데이터의 갯수를 리턴한다.


SQL helper

query

var list = await db.query('my_table', columns: ['name', 'type']);

rawQuery와 같다.

insert

int recordId = await db.insert('my_table', {'name': 'my_name', 'type': 'my_type'});

rawInsert와 똑같이 동작하나 conflict 핸들링을 위한 conflictAlgorithm 파라미터를 제공한다.
자세한 내용은 여기를 참고하자.

update

var count = await db.update('my_table', {'name': 'new cat name'}, where: 'name = ?', whereArgs: ['cat']);

rawUpdate와 같다.

delete

var count = await db.delete('my_table', where: 'name = ?', whereArgs: ['cat']);

rawDelete와 같다.


SQL 구문을 많이 다뤄본 개발자라면 매우 쉬운 내용일 것이다. 매소드 및 클래스명이 직관적이라 익히는데 오랜 시간이 걸리지 않을 것이다.
디바이스에 로컬 DB를 사용해야한다면 sqflite를 사용해보자!

자세한 내용은 sqflite, Database API에서 확인 할 수 있다.

profile
Flutter 공부를 위한 블로그입니다.

0개의 댓글