하얀설표 블로그




dart/flutter drift upsert 사용 예제





( 수정됨)


코드

models.py

# models.py
import 'package:drift/drift.dart';

// https://drift.simonbinder.eu/docs/getting-started/advanced_dart_tables/

@DataClassName('TodoTable')
class Todo extends Table {
  // int
  IntColumn get id => integer()();
  // null 허용
  IntColumn get category => integer().nullable()();
  // bool
  BoolColumn get enable => boolean()();
  // dateTime
  DateTimeColumn get date => dateTime()();
  // https://white.seolpyo.com/
  // String
  // 최소/최대 길이 제한
  TextColumn get title => text().withLength(min: 6, max: 32)();
  // 기본키 설정, upsert 명령을 위해 필요

  @override
  Set<Column> get primaryKey => {id};
}

database.dart

# database.dart
import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:sqlite3/sqlite3.dart';
// dart에서 사용하기 위해 주석처리
// import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
import 'package:example/Drift/models.dart';

part 'database.g.dart';

@DriftDatabase(tables: [Todo])
class Handle extends _$Handle {
  Handle() : super(_openConnection());

  @override
  int get schemaVersion => 1;
  // 테이블 전체 데이터 가져오기(Furture)
  Future<List<TodoTable>> get allTodoItems => select(todo).get();
  // https://white.seolpyo.com/
  // 테이블 limit 갯수만큼 데이터 가져오기(Furture)
  Future limitTodos(int limit, {int? offset}) {
    return (select(todo)..limit(limit, offset: offset)).get();
  }
  // 정렬
  Future sortTitle() {
    return (select(todo)
      ..orderBy([(table) => OrderingTerm(expression: table.title)]))
        .get();
  }
  // 특정 데이터 가져오기(Future)
  Future<TodoTable> findById(int id) {
    return (select(todo)..where((table) => table.id.equals(id))).getSingle();
  }
  // 수정하거나 삽입
  Future<int> upsertTodo(TodoTable entry) {
    return into(todo).insertOnConflictUpdate(entry);
  }
  Future<int> upsertTodoCompanion(TodoCompanion entry) {
    return into(todo).insertOnConflictUpdate(entry);
  }
  Future delTodo(int id) {
    return (delete(todo)..where((table) => table.id.equals(id))).go();
  }
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final file = File('db.sqlite');
    return NativeDatabase.createInBackground(file);
    // return NativeDatabase.memory();
  });
}

main.dart

# main.dart
import 'package:example/Drift/database.dart';

void main() async {
  final db = await Handle();
  var WhiteSeolpyoCom = await db.allTodoItems;
  for (final i in a) {print(i);}
  print('='*20);
  db.upsertTodo(
      TodoTable(id: 1, num: 1, date: DateTime.now(), title: '1111111', content: '1', enable: false)
  );
  a = await db.allTodoItems;
  for (final i in a) {print(i);}
  print('='*20);
  db.upsertTodo(
      TodoTable(id: 2, num: 2, date: DateTime.now(), title: '2222222', content: '2', enable: false)
  );
  a = await db.allTodoItems;
  for (final i in a) {print(i);}
  print('='*20);
  // https://white.seolpyo.com/
  db.upsertTodo(
      TodoTable(id: 3, num: 3, date: DateTime.now(), title: '33333333', content: '3', enable: false)
  );
  a = await db.allTodoItems;
  for (final i in a) {print(i);}
  print('='*20);
  db.upsertTodo(
    TodoTable(id: 2, num: 4, date: DateTime.now(), title: '4444444', content: '4', category: null, enable: true)
  );
  // await db.delTodo(2);
  a = await db.allTodoItems;
  for (final i in a) {print(i);}
  print('='*20);
  // https://white.seolpyo.com/
  var i = a[1];
  print('i.id, ${i.id}, ${i.id.runtimeType}');
  print('i.category, ${i.category}, ${i.category.runtimeType}');
  print('i.date, ${i.date}, ${i.date.runtimeType}');
  print('i.enable, ${i.enable}, ${i.enable.runtimeType}');
}

설명

dart/flutter에서 drift를 포함해 sqlite를 조작하려면 사전 작업이 반드시 필요하다.
이 작업들을 모른다면 어떤 작업을 해야 하는지 확인하길 바란다.

dart/flutter에서 사용 가능한 패키지인 drift에서 사용가능한 기본적인 명령과 각 Column의 속성을 작성한 코드다.
개인적으로 sqlite에서 사용 가능한 값은 int, string, bool 3가지만 사용했는데, 이 패키지는 int, string, bool에 더해 datetime 속성도 지원한다.
그밖에도 다른 속성도 지원하지만 현재 내가 사용할 줄 아는 속성이 이 4가지뿐이니 생략한다.

또한 코드를 보면 데이터 추가(insert)와 데이터 수정(update) 명령이 없다.
그 이유는 이를 upsert 명령이 대신하기 때문이다.

@DataClassName

데이터 테이블은 보통 models.dart에 작성된 class 이름을 따라가지만, @DataClassName('{데이터 클래스 명칭}')을 통해 return하는 데이터의 Type 명칭을 정의할 수 있다.
database.dart를 보면 type과 class 명칭이 다른 것을 알 수 있다.

@DriftDatabase(tables: [])

@DriftDatabase(tables: [])을 통해 1개의 데이터베이스 핸들 class에서 조작할 table의 갯수를 변경할 수 있다.
즉, 1개의 핸들에서 2개 이상의 데이터 테이블을 조작할 수 있다는 것이다.

2개 이상의 데이터 테이블을 조작하는 경우 각 테이블을 조작하는 명령을 추가하거나, db에 존재하는 table을 선택해 직접 명령하는 것도 가능하다.
db를 호출하고, db에 존재하는 table을 선택한 다음, table에서 할 작업을 명령하는 것은 pub.dev의 dirft 페이지에서 제공하는 Example 페이지를 참고하면 된다.

아니면 각 테이블마다 사용할 핸들 class를 따로따로 만들어 사용하는 것도 가능하다.

실행 예시

기능 자체는 꽤 많지만, 당장 자주 사용하는 기능인 데이터 읽기/추가/삭제/수정 작업만 main.dart에서 실행하도록 예시 코드를 작성해두었다.
main.dart를 실행하면 다음과 같이 데이터가 추가되고, upsert 명령으로 데이터가 수정되는 것도 확인할 수 있다.
마지막에는 불러온 데이터에서 각 Column별 type을 출력하도록 해두었다.

====================
TodoTable(id: 1, num: 1, category: null, enable: false, date: 2023-12-06 17:29:16.000, title: 1111111, content: 1)
====================
TodoTable(id: 1, num: 1, category: null, enable: false, date: 2023-12-06 17:29:16.000, title: 1111111, content: 1)
TodoTable(id: 2, num: 2, category: null, enable: false, date: 2023-12-06 17:29:16.000, title: 2222222, content: 2)
====================
TodoTable(id: 1, num: 1, category: null, enable: false, date: 2023-12-06 17:29:16.000, title: 1111111, content: 1)
TodoTable(id: 2, num: 2, category: null, enable: false, date: 2023-12-06 17:29:16.000, title: 2222222, content: 2)
TodoTable(id: 3, num: 3, category: null, enable: false, date: 2023-12-06 17:29:16.000, title: 33333333, content: 3)
====================
TodoTable(id: 1, num: 1, category: null, enable: false, date: 2023-12-06 17:29:16.000, title: 1111111, content: 1)
TodoTable(id: 2, num: 4, category: null, enable: true, date: 2023-12-06 17:29:16.000, title: 4444444, content: 4)
====================
i.id, 2, int
i.category, null, Null
i.date, 2023-12-06 17:29:16.000, DateTime
i.enable, true, bool

insert, update

insert와 update 기능을 사용하길 원한다면 이 페이지를 확인하자.

Companion

database.dart를 보면 upsertTodo 명령이 2개 있다. 하나는 Table 객체를, 다른 하나는 Companion 객체를 전달받는 명령이다.
2개의 차이에 대해 알고 싶다면 이 페이지를 확인하자.

upsert 명령

upsert 명령은 update와 insert의 합성어로 보인다.
실제로 해당 명령 사용시 중복되는 데이터가 있으면 update 작업을 하고, 중복되는 데이터가 없으면 insert 작업을 하기 때문이다.

upsert 명령의 단점

upsert 명령을 사용하는 경우 흔히 고유값으로 사용하는 id를 unique로 설정하거나, autoIncrement 기능을 이용해 자동으로 값을 입력하는 기능을 사용할 수 없다는 것이다.
그 이유는 models.dart를 작성한 다음, build하면 upsert 명령을 사용하기 위해 고유 키(primaryKey)를 추가하는 명령이 앞서 말한 2가지 기능과 공존할 수 없다는 에러를 뱉기 때문이다.

upsert 명령의 장점

upsert 명령은 Map(dict) 객체와 같이 일정한 키값으로 정렬되어있는 데이터를 저장할 때 유용한 명령이다.
이 경우 각 데이터마다 연결되어있는 고유한 키값이 존재하기 때문이다.

키값만 unique하고  autoIncrement 는 사용할 수 없는 경우에는 존재하지 않는 키값을 찾아낸 다음, 사용해야하는 작업이 추가되어야 하는데, 이런 작업을 거칠 필요가 없어진다.
코드 몇 줄이면 해결되는 작업이긴 하지만, 이런 소소한 작업들이 은근히 전체 작업 시간을 잡아먹기 때문에 전체 작업시간 절약에 기여한다.

upsert 명령 사용시 주의점

upsert 명령을 사용할 것이라면 반드시 primaryKey를 선언해야한다.
만약 그렇지 않을 경우, update를 해야 하는 upsert 명령은 작동하지 않고, insert 작업만 작동하기 때문이다.


공감 : 0







white.seolpyo.com