Flutter를 이용하여 Desktop 어플리케이션을 개발할 때 데이터베이스 관리 도구로써 가장 강력하게 사용할 수 있는 라이브러리 중에 Drift가 있습니다. 제가 애용하는 라이브러리 중 하나인데요. 앱이 복잡해지면서 효율성을 위해서 관리하는 데이터베이스 테이블의 수도 많아지게 되고 테이블 간 복잡한 쿼리문을 작성하여 데이터를 보여줘야 하는 상황이 발생합니다. 이 글에서는 flutter drift로 join 구문 사용하는 방법에 대해서 알아보고 합니다.
Drift에 대해서 잘 모르시는 분이라면 아래의 글을 읽어보시길 추천드립니다.
flutter drift로 sqlite 데이터베이스 생성부터 관리까지(클릭)
공식문서 참고하기
Drift 고급 문법에 대해서 정식문서를 찾기 원하시는 분은 아래의 링크를 통해 확인 가능합니다.
Drift Advanced Features(공식문서 바로가기)
공식문서에서 보여주는 예제는 두 개의 테이블을 이용하여 join 활용법에 대해서 설명하고 있습니다.
flutter drift로 join 구문 사용
테이블 정의하기
Drfit에서 Join을 사용하기 위해서는 새롭게 테이블을 정의해 주어야 합니다. 다음에 보시는 예제는 학생관리 테이터베이스를 만들기 입니다. student_infos, student_class_info 테이블을 정의하였습니다. 두 개의 테이블을 별도로 관리하는 이유는 연도별로 학생의 반, 번호 정보가 변동되기 때문입니다. 물론 하나의 테이블로 만들어서 업데이트를 하여 관리할 수도 있지만 업데이트를 하여 데이터를 변경하게되면 과거의 반, 번호 정보를 추적할 수 없는 문제가 발생합니다. 따라서 학생 고유정보와 연도별 반, 번호 부여 상태를 별도의 테이블로 관리하는 것이 좋습니다.
//example/lib/local/entity/student_infos.dart import 'package:drift/drift.dart'; class StudentInfos extends Table { @override String get tableName => 'student_infos'; IntColumn get id => integer().autoIncrement()(); TextColumn get studentId => text().named('student_id')(); TextColumn get name => text()(); TextColumn get gender => text()(); }
//example/lib/local/entity/student_class_infos.dart import 'package:drift/drift.dart'; class StudentClassInfos extends Table { @override String get tableName => 'student_class_infos'; IntColumn get id => integer().autoIncrement()(); IntColumn get year => integer()(); TextColumn get studentNum => text()(); TextColumn get studentId => text()(); TextColumn get classId => text()(); TextColumn get validYn => text()(); }
쿼리 작성하기
당해 년도에 반 별로 학생정보를 조회하기 위한 쿼리를 작성한다고 가정해 봅시다. student_class_infos 테이블에는 반(classId), 번호(studentNum) 정보가 학생Id(studentId)정보와 연결되어 있습니다. 따라서 학생의 이름이나 성별 등을 확인하기 위해서는 student_infos 테이블의 정보를 추가로 확인해야 합니다. 즉 student_class_infos 테이블과 student_infos 테이블이 연결된 새로운 테이블이 필요합니다.
// example/lib/local/db/app_db.dart // 1 import '../entity/student_class_infos.dart'; import '../entity/student_infos.dart'; // 2 class StudentClassInfoDetailed { StudentClassInfoDetailed( this.studentInfo, this.studentClassInfo, ); final StudentInfo studentInfo; final StudentClassInfo studentClassInfo; } class AppDb extends _$AppDb { AppDb() : super(_openConnection()); // 3 Stream<List<StudentClassInfoDetailed>> getStudentClassInfoDetailed( String classCode) { final query = select(studentClassInfos).join([ innerJoin(studentInfos, studentInfos.studentId.equalsExp(studentClassInfos.studentId)), ]) ..where(studentClassInfos.classId.equals(classCode)) // 4 return query.watch().map((rows) { return rows.map((row) { return StudentClassInfoDetailed( row.readTable(studentClassInfos), row.readTable(studentInfos), ); }).toList(); }); } // 5 Future<List<StudentClassInfoDetailed>> getStudentClassInfoDetailedFuture( String classCode) { final query = select(studentClassInfos).join([ innerJoin(studentInfos, studentInfos.studentId.equalsExp(studentClassInfos.studentId)), ]) ..where(studentClassInfos.classId.equals(classCode)) // 6 return query.map((row) { return StudentClassInfoDetailed( row.readTable(studentClassInfos), row.readTable(studentInfos), ); }).get(); } }
- entity 폴더에 정의한 테이블을 임포트 합니다.
- 임포트한 두 개의 테이블을 기반으로 한 새로운 클래스를 정의합니다. StudentClassInfoDetailed라고 이름을 지정하겠습니다. 해당 클래스는 StudentInfo와 StudentClassInfo를 함께 가지고 있는 새로운 테이블이라고 보시면 됩니다. Drift에서는 각각의 테이블 이름에 해당하는 클래스로 커스텀 타입을 생성하고 이를 기반으로 데이터를 관리합니다. StudentClassInfoDetailed 역시 두 개의 테이블이 함께 연결된 새로운 타입이 됩니다.
- Future나 Stream 중 원하는 용도에 맞춰 함수를 지정합니다. getStudentClassInfoDetailed라는 함수는 리턴 타입으로 List<StudentClassInfoDetailed> 타입을 스트림으로 전달합니다.
- 함수에 입력 파라메터를 전달해서 조건을 부여할 수 있습니다. 해당 쿼리의 경우 classCode 변수를 받은 뒤 해당 classCode에 해당하는 학생정보만을 출력합니다.
- join이나 where 구문에서 값의 비교는 ==를 이용하여 하지 않고 equals나 equalsExp을 이용해서 합니다. equals는 값을 비교할 때 사용하고 equalsExp는 다른 테이블의 열과 비교할 때 사용합니다.
- 리턴을 설정합니다. 스트림의 리턴의 경우 watch() 를 이용합니다. 새롭게 정의된 StudentClassInfoDetailed 클래스 형태로 데이터를 리턴하기 때문에 map 함수를 이용하여 studentClassInfo와 studentInfos 테이블의 데이터를 매칭시켜주는 작업을 수행합니다.
- 동일한 결과를 Future 타입으로 반환하는 경우입니다. 이 경우에는 리턴타입을 기존의 Stream<List<StudentClassInfoDetailed>> 였던 것을 Future<List<StudentClassInfoDetailed>>로 바꿔줍니다. 그 외 쿼리작성해주는 부분은 동일합니다.
- 리턴타입의 경우 Stream과 동일한 맥락에서 작성되지만 약간의 차이가 있습니다. 기존의 watch()를 이용하던 것이 Future에서는 get() 함수를 이용해서 값을 리턴합니다.
마무리
이렇게 Drift를 이용해서 복수의 테이블로부터 join 쿼리를 이용하는 방법에 대해서 알아보았습니다. 가장 기본적인 CRUD(Create, Read, Update, Delete) 기능과 더불어 join 기능을 사용하실 수 있게 되면 Drift의 활용 폭이 확 넓어집니다. 데이터베이스를 분산하여 더욱 효과적으로 관리할 수 있기 때문에 필요 이상으로 테이블을 수정하지 않아도 되며, 전체 데이터베이스의 용량도 획기적으로 줄일 수 있습니다. 이는 곳 앱의 성능 향상의 결과로 나타나게됩니다.
다음시간에는 앞서서 만든 쿼리를 이용하여 실제 화면에 FutureBuilder와 StreamBuilder를 이용해서 어떻게 정보를 구현할 수 있을지에 대해서 정리해 보도록 하겠습니다.