본문 바로가기

APP/Flutter

[플러터] SQLite 기본 사용법 - CRUD, sqflite, Flutter, 패키지

반응형

  서버와 통신해서 DB에 데이터를 저장하고 가져올 수도 있지만 앱 내부 DB를 이용하는게 프로젝트 진행에 더 유리한 경우가 있다. 그런 경우에 내부 DB(SQLite DB)에 쉽게 접근하기 위해 사용하는 패키지가 'sqflite'다.


sqflite 패키지 설치

  pub.dev에서 'sqflite' 검색 후 해당 버전의 패키지를 설치하면 된다. 나 같은 경우에는 2021년 12월 10일 기준 최신버전인 2.0.1 버전을 설치했다.

 

path 패키지 설치

  내부 데이터베이스의 위치를 정확히 정의해주는 path 패키지도 설치해준다. 이 역시 2021년 12월 10일 기준 최신 버전인 1.8.0 버전으로 설치했다.

 

DB 연결

class SqliteTestModel {
  Database? _database;
  
  Future<Database> get database async {
    if (_database != null) return _database!;

    return await initDB();
  }
  
  initDB() async {
    String path = join(await getDatabasesPath(), 'test_database.db');
    
    return await openDatabase(
      path,
      version: 1,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade
    );
  }
}
  • 매번 쿼리문을 실행할 때마다 DB 경로에 접근해서 DB를 열게되면 자원낭비이기 때문에 Singleton 패턴을 이용하여 Database 변수를 만들어 주었다.
  • 'test_database'라는 이름의 DB를 만들어주었다.
  • openDatabase 함수
    • path - DB 경로
    • version - DB의 업그레이드와 다운그레이드를 수행하기 위해 사용된다.
    • onCreate - DB에 해당 테이블이 없으면 생성해준다.
    • onUpgrade - 이전 version과 업데이트 된 version을 비교하여 다를 경우 새로 업데이트 된 내용이 적용된다.

 

테이블 생성

FutureOr<void> _onCreate(Database db, int version) {
  String sql = '''
  CREATE TABLE testTable(
    No INTEGER PRIMARY KEY AUTOINCREMENT,
    UserID TEXT,
    Content TEXT,
    RecordDate DATETIME)
  ''';

  db.execute(sql);
}

FutureOr<void> _onUpgrade(Database db, int oldVersion, int newVersion) {}
  • No는 int 타입이며, SQLite에서는INTEGER 타입으로 저장된다. 또한 PRIMARY KEY로 지정을 해주었다.
  • No를 AUTOINCREMENT로 설정을 해줌으로써 자동으로 값이 1씩 증가하도록 만들어주었다.
  • UserID는 String 타입이며, SQLite에서는 TEXT 타입으로 저장된다.
  • RecordDate는 데이터가 INSERT되는 시간을 저장하기 위한 컬럼으로 날짜를 입력하기 위해 DATETIME 타입으로 지정해주었다.
  • openDatabase에 들어갈 _onUpgrade 함수는 첫 테이블을 생성하는데 필요없기 때문에 빈 칸으로 냅둔다.

onUpgrade에 대해서는 여기에서!!

2021.12.23 - [플러터] SQLite DB 내용 수정 - Flutter, onUpgrade, sqflite, 데이터베이스, db, database

 

[플러터] SQLite DB 내용 수정 - Flutter, onUpgrade, sqflite, 데이터베이스, db, database

다음 링크에서 기본적인 SQLite 사용법이 정리되어있다. 2021.12.13 - 플러터 SQLite 기본 사용법 - CRUD, sqflite, Flutter, 패키지 플러터 SQLite 기본 사용법 - CRUD, sqflite, Flutter, 패키지 서버와 통신해..

zzangwoo.tistory.com

 

테이블 생성 확인

Widget _buttonView() {
  return SizedBox(
    height: 100,
    child: Row(
      children: [
        OutlinedButton(
          onPressed: () {
            var db = _model.database;
          },
          child: Text('DB 생성')
        )
      ],
    ),
  );
}

  • SQLite 테스트를 하기 위해서 테스트 페이지를 만들었다.
  • DB 생성 버튼을 클릭하면 자동으로 테이블이 생성되게 SqliteTestModel의 database 변수를 가져오도록 하였다.

 

Database Inspector 기능으로 테이블 생성 확인

 

만약 Database Inspector 기능이 활성화 되어 있지 않다면??

2021.12.12 - Database Inspector 안 보이는 경우 해결방법 - Flutter, Android Studio, 안드로이드 스튜디오

 

Database Inspector 안 보이는 경우 해결방법 - Flutter, Android Studio, 안드로이드 스튜디오

내부 데이터베이스를 만들고 개발하면서 안의 내용물을 확인하기 위해서 테스트 코드를 만들고 로그를 찍어보는건 여간 귀찮은 일이 아니다. 이를 쉽게 하기위해서 안드로이드 스튜디오 내에 '

zzangwoo.tistory.com

 

CRUD 기능 만들기

  CRUD 기반으로 예제를 만들어보았다.

 

데이터 모델 정의

class SqliteTest {
  final int? No;
  final String? UserID;
  final String? Content;
  final String? RecordDate;

  SqliteTest({
    this.No,
    this.UserID,
    this.Content,
    this.RecordDate
  });

  Map<String, dynamic> toMap() => {
    'UserID': this.UserID,
    'Content': this.Content,
    'RecordDate': this.RecordDate
  };
}
  • 테이블을 만들 때 컬럼 이름과 동일하게 데이터 모델을 정의했다.
  • INSERT를 할 때 No 컬럼을 AUTOINCREMENT로 설정해서 값을 넣을 필요는 없지만 추후에 SELECT로 데이터를 가져온 후에 KEY 값으로 접근해야 하는 일이 있을 수 있기 때문에 추가해두었다.
  • Database 객체에서 제공하는 insert 함수에 넣을 파라미터 값을 위해서 Map으로 변환해주는 toMap() 함수를 추가해준다.

 

INSERT 함수 예제

  • 위와 같이 '아이디(Test)'와 '내용(테스트 내용1)'을 입력하고 INSERT 버튼을 누르면 테이블에 저장되게 INSERT문을 만들어놓았다.

 

final _model = SqliteTestModel();

... 생략 ...

OutlinedButton(
    onPressed: () async {
      String userID = _userID.text;
      String content = _content.text;
      String recordDate = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());

      await _model.testInsert(SqliteTest(
        UserID: userID,
        Content: content,
        RecordDate: recordDate
      ));
    },
    child: Text('INSERT')
),
  • 위의 OutlinedButton이 눌렸을 때 이벤트에서는 캡쳐화면과 같이 TextField에 입력된 값이 테이블에 INSERT 하는 로직으로 넘어가게 해준다.

 

Future<void> testInsert(SqliteTest item) async {
  var db = await database;

  await db.insert(
    'testTable',
    item.toMap()
  );
}
  • 위의 함수는 미리 만들어 두었던 데이터 모델을 기반으로 테이블에 INSERT 하게 해주는 로직이다.

 

INSERT 함수 결과

 

SELECT 함수 예제 및 결과

  • SELECT 버튼을 클릭하면 테이블에 저장된 내용이 출력되게 만들었다.

 

final _model = SqliteTestModel();

... 생략 ...

OutlinedButton(
    onPressed: () async {
      var list = await _model.testSelect();

      setState(() {
        _result = '';

        for (var item in list) {
          _result += '${item.No} - ${item.UserID} / ${item.Content} / ${item.RecordDate}\n';
        }
      });
    },
    child: Text('SELECT')
)
  • SELECT 버튼 클릭 이벤트가 발생하면 SELECT 하는 로직을 통해서 List를 리턴받아온다.

 

Future<List<SqliteTest>> testSelect() async {
    var db = await database;

    // testTable 테이블에 있는 모든 field 값을 maps에 저장한다.
    final List<Map<String, dynamic>> maps = await db.query('testTable');

    return List.generate(maps.length, (index) {
      return SqliteTest(
        No: maps[index]['No'] as int,
        UserID: maps[index]['UserID'] as String,
        Content: maps[index]['Content'] as String,
        RecordDate: maps[index]['RecordDate'] as String
      );
    });
  }
  • db.query 함수의 파라미터에 테이블 명을 입력한 후에 List<Map<String, dynamic>> 형태로 테이블에 있는 값들을 모두 리턴받는다.
  • List 형태로 변환시켜주고 리턴시킨다.

 

UPDATE 함수 예제

  • UPDATE 버튼을 클릭하면 No 값이 2인 Field 값의 Content 컬럼을 '테스트 내용222'로 변경해보려 한다.

 

final _model = SqliteTestModel();

... 생략 ...

OutlinedButton(
    onPressed: () async {
      int no = int.parse( _no.text);
      String userID = _userID.text;
      String content = _content.text;
      String recordDate = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());

      await _model.testUpdate(SqliteTest(
        No: no,
        UserID: userID,
        Content: content,
        RecordDate: recordDate
      ));
    },
    child: Text('UPDATE')
)
  • UPDATE 버튼 클릭 이벤트가 발생하게 되면 TextField의 값들을 모두 SqliteTest 객체에 저장해서 UPDATE 로직으로 넘겨준다.

 

Future<void> testUpdate(SqliteTest item) async {
  var db = await database;

  await db.update(
    'testTable',
    item.toMap(),
    where: 'No = ?',
    whereArgs: [item.No]
  );
}
  • UPDATE하기 위해서 Primary Key인 No 컬럼으로 조건을 넣어주었다.
  • SqliteTest의 No를 whereArg로 넘겨 SQL injection을 방지한다.

 

UPDATE 함수 결과

  • Primary Key인 No 컬럼으로 접근해서 Content 내용이 '테스트 내용222'로 변경된 것을 확인할 수 있다.

 

DELETE 함수 예제

  • DELETE 버튼을 클릭하면 No의 값이 2인 Field를 삭제해보려 한다.

 

final _model = SqliteTestModel();

... 생략 ...

OutlinedButton(
    onPressed: () async {
      int no = int.parse( _no.text);

      await _model.testDelete(no);
    },
    child: Text('DELETE')
)
  • DELETE 버튼 클릭 이벤트가 발생하게 되면 No 값을 DELETE 로직으로 넘겨준다.

 

Future<void> testDelete(int no) async {
  var db = await database;

  await db.delete(
    'testTable',
    where: 'No = ?',
    whereArgs: [no]
  );
}
  • delete 역시 SQL injection을 방지하기 위해 whereArgs에 No 값을 입력해주었다.

 

DELETE 함수 결과

  • DELETE 버튼 클릭 후 No를 입력받는 TextField의 값(2)을 기준으로 테이블에서 해당 Field를 삭제했다.
반응형