Flutter

[Flutter] SQFLite DB Migration 진행하는 방법

디벨로펄 2024. 12. 26.
반응형

안녕하세요 디벨로펄입니다.

 

flutter에서 DB 마이그레이션....

처음부터 구성을 잘 못해서, 새로 만드는 것보다 더 힘드네요

 

Migration의 전반적인 순서는 다음과 같습니다.

1. 변경되는 테이블 생성

2. 변경되는 테이블에 현재 데이터 옮기기

3. 현재 db 테이블 삭제

4. 변경되는 테이블명을 본래 테이블 명으로 수정

5. 관련 method 문제 없도록 수정

 

단순한 테이블 사용하려다가, 엮으니 되게 복잡하네요...

차근차근 따라가보겠습니다.

 

여기서 가장 큰 문제는

각 경우에 맞는 쿼리를 짜는게 가장 큰 문제입니다.

gpt의 도움을 받아서 하나씩 차근차근 변경합니다.

 

1. 변경되는 테이블 생성

다음과 같이 변경하는 Table을 생성해줍니다.

저는 table명도 변경해서 다음과 같이 설정했습니다.

version이 1에서 2로 업데이트 될 때 수행되는 코드 내부에 넣어둡니다.

if (oldVersion == 1 && newVersion == 2) {
      print("version 1-> version 2 migration executed");
      // 버전 1에서 2로 업그레이드 할 때 필요한 작업만 수행

      // 1. 변경되는 테이블 생성
      await db.execute('''
      CREATE TABLE $groupTableName (
        ${Columns.ContactGroup.groupId} INTEGER PRIMARY KEY AUTOINCREMENT,
        ${Columns.ContactGroup.groupName} TEXT NOT NULL UNIQUE
      )
    ''');

      await db.execute('''
      CREATE TABLE $contactTableName (
        ${Columns.EventDiaryContact.contactId} INTEGER PRIMARY KEY AUTOINCREMENT,
        ${Columns.EventDiaryContact.name} TEXT NOT NULL,
        ${Columns.EventDiaryContact.groupId} INTEGER,
        ${Columns.EventDiaryContact.phoneNumber} TEXT,
        ${Columns.EventDiaryContact.memo} TEXT,
        FOREIGN KEY (${Columns.EventDiaryContact.groupId}) REFERENCES groups (${Columns.ContactGroup.groupId}) ON DELETE SET NULL
      )
    ''');

      await db.execute('''
    CREATE TABLE $eventDiaryTableName (
      ${Columns.EventDiary.id} INTEGER PRIMARY KEY AUTOINCREMENT,
      ${Columns.EventDiary.contactId} INTEGER,
      ${Columns.EventDiary.eventName} TEXT NOT NULL,
      ${Columns.EventDiary.eventDate} TEXT,
      ${Columns.EventDiary.amount} INTEGER,
      ${Columns.EventDiary.memo} TEXT,
      ${Columns.EventDiary.direction} TEXT,
      ${Columns.EventDiary.location} TEXT,
      ${Columns.EventDiary.eventType} TEXT,
      FOREIGN KEY (${Columns.EventDiary.contactId}) REFERENCES contacts (${Columns.EventDiaryContact.contactId}) ON DELETE CASCADE
    )
  ''');

2. 변경되는 테이블에 현재 데이터 옮기기

기존 데이터를 가져와서 새로운 테이블에 써주도록 합니다.

// 2. 기존 테이블에서 새 테이블로 데이터 복사
      // 현재 연락처 리스트 가져오기
      var contacts =
          await EventDiaryContactDatabaseHelper.instance.fetchContacts();
      // group 데이터 넣기
      Set<String> uniqueGroups =
          contacts.map((contact) => contact.groupName).toSet();
      Map<String, int> groupIdMap = {}; // Map to store groupName -> groupId
      for (String groupName in uniqueGroups) {
        int groupId = await db.insert(groupTableName, {
          'groupName': groupName,
        });
        groupIdMap[groupName] = groupId;
      }
      // 연락처 정보 연락처 테이블에 넣기
      Map<int, int> oldContactNewContactMap =
          {}; // Map to store oldContactId -> newContactId

      for (EventDiaryContactViewModel contact in contacts) {
        int newContactId = await db.insert(contactTableName, {
          'name': contact.name,
          'groupId': groupIdMap[contact.groupName],
          'phoneNumber': contact.phoneNumber,
          'memo': contact.memo,
        });
        oldContactNewContactMap[contact.contactId!] = newContactId;
      }

      List<Map<String, dynamic>> eventDiaries =
          await db.query('event_diaries'); // 기존 테이블 명 사용해야 합니다.
      for (var diary in eventDiaries) {
        int? oldContactId = diary['contactId'];
        int? newContactId = oldContactNewContactMap[oldContactId];

        if (newContactId != null) {
          await db.insert(eventDiaryTableName, {
            'id': diary['id'],
            'contactId': newContactId,
            'eventName': diary['eventName'],
            'eventDate': diary['eventDateTime'],
            'date': diary['date'],
            'amount': diary['amount'],
            'memo': diary['memo'],
            'direction': diary['direction'],
            'location': diary['location'],
          });
        }
      }
    }
  }

3. 현재 db 테이블 삭제

기존에 사용하던 테이블은 과감하게 삭제합니다.

      // 3. 기존 테이블 제거 
      await db.execute('DROP TABLE event_diaries');

4. 변경되는 테이블명을 본래 테이블 명으로 수정

필요한 경우 기존 테이블 명을 본래의 테이블 명으로 수정합니다.

변화가 많지 않은경우라면 ALTER 등 SQL문으로 해결할 수 있을 거예요

 

5. 관련 method 문제 없도록 수정

기존 Select 문들을 새로운 ERD에 맞춰서 작성해주도록 합니다.

상당히 번거로운 작업이지만, gpt의 도움을 받아서 진행합니다.

도움을 받더라도 결과에 대한 테스트는 꼭 진행해주세요~

 

 

 

최종 Migration 코드

Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion == 1 && newVersion == 2) {
      print("version 1-> version 2 migration executed");
      // 버전 1에서 2로 업그레이드 할 때 필요한 작업만 수행

      // 1. 변경되는 테이블 생성
      await db.execute('''
      CREATE TABLE $groupTableName (
        ${Columns.ContactGroup.groupId} INTEGER PRIMARY KEY AUTOINCREMENT,
        ${Columns.ContactGroup.groupName} TEXT NOT NULL UNIQUE
      )
    ''');

      await db.execute('''
      CREATE TABLE $contactTableName (
        ${Columns.EventDiaryContact.contactId} INTEGER PRIMARY KEY AUTOINCREMENT,
        ${Columns.EventDiaryContact.name} TEXT NOT NULL,
        ${Columns.EventDiaryContact.groupId} INTEGER,
        ${Columns.EventDiaryContact.phoneNumber} TEXT,
        ${Columns.EventDiaryContact.memo} TEXT,
        FOREIGN KEY (${Columns.EventDiaryContact.groupId}) REFERENCES groups (${Columns.ContactGroup.groupId}) ON DELETE SET NULL
      )
    ''');

      await db.execute('''
    CREATE TABLE $eventDiaryTableName (
      ${Columns.EventDiary.id} INTEGER PRIMARY KEY AUTOINCREMENT,
      ${Columns.EventDiary.contactId} INTEGER,
      ${Columns.EventDiary.eventName} TEXT NOT NULL,
      ${Columns.EventDiary.eventDate} TEXT,
      ${Columns.EventDiary.amount} INTEGER,
      ${Columns.EventDiary.memo} TEXT,
      ${Columns.EventDiary.direction} TEXT,
      ${Columns.EventDiary.location} TEXT,
      ${Columns.EventDiary.eventType} TEXT,
      FOREIGN KEY (${Columns.EventDiary.contactId}) REFERENCES contacts (${Columns.EventDiaryContact.contactId}) ON DELETE CASCADE
    )
  ''');

      // 2. 기존 테이블에서 새 테이블로 데이터 복사
      // 현재 연락처 리스트 가져오기
      var contacts =
          await EventDiaryContactDatabaseHelper.instance.fetchContacts();
      // group 데이터 넣기
      Set<String> uniqueGroups =
          contacts.map((contact) => contact.groupName).toSet();
      Map<String, int> groupIdMap = {}; // Map to store groupName -> groupId
      for (String groupName in uniqueGroups) {
        int groupId = await db.insert(groupTableName, {
          'groupName': groupName,
        });
        groupIdMap[groupName] = groupId;
      }
      // 연락처 정보 연락처 테이블에 넣기
      Map<int, int> oldContactNewContactMap =
          {}; // Map to store oldContactId -> newContactId

      for (EventDiaryContactViewModel contact in contacts) {
        int newContactId = await db.insert(contactTableName, {
          'name': contact.name,
          'groupId': groupIdMap[contact.groupName],
          'phoneNumber': contact.phoneNumber,
          'memo': contact.memo,
        });
        oldContactNewContactMap[contact.contactId!] = newContactId;
      }

      List<Map<String, dynamic>> eventDiaries =
          await db.query('event_diaries'); // 기존 테이블 명 사용해야 합니다.
      for (var diary in eventDiaries) {
        int? oldContactId = diary['contactId'];
        int? newContactId = oldContactNewContactMap[oldContactId];

        if (newContactId != null) {
          await db.insert(eventDiaryTableName, {
            'id': diary['id'],
            'contactId': newContactId,
            'eventName': diary['eventName'],
            'eventDate': diary['eventDateTime'],
            'date': diary['date'],
            'amount': diary['amount'],
            'memo': diary['memo'],
            'direction': diary['direction'],
            'location': diary['location'],
          });
        }
      }
    }
  }

 

반응형

댓글