참고 문헌
https://flutter-ko.dev/docs/cookbook/persistence/sqlite
마침내 오랜만에 글을 쓰게 되었다.
오늘도 채팅 작업을 잘 마무리하고 그동안 유용하게 써왔던,
sqlite에 대한 사용법을 작성할 생각이다.
9시 50분에 차를 타고 퇴근을 해야 하기 때문에
다소 간략하게 쓸 수도 있지만,
집에 가서도 쓰면 되니 뭐..
간단하게 내부 디비를 왜 써야 하는지 설명하겠다.
보통 우리는 데이터를 받아 쓸 때, 서버에 api를 요청하거나 응답하여,
그 코드를 json으로 가공하여 사용하곤 한다.
여기서 발생되는 단점은 비용인데,
채팅 부분에서는 유저가 많아질수록 상당하다.
그렇다면, 상황에 따라 데이터를 계속 가지고 있거나(정적인 데이터)
아니면 계속 업데이트를 해줘야 하는 데이터(동적인 데이터)가 있을 것이다.
전자는 내부 디비에 저장을 하고,
후자는 외부 디비에서 가져오면 되는 것이다.
아무래도 굳이 값이 변경되지 않는 데이터는 계속해서 서버에 요청할 필요가 없기 때문에
앱 내의 있는 데이터베이스를 이용하면 되는 것이다.
그것이 바로 sqlite이다.
그렇다면 sqlite pakage를 yaml파일에다가 넣어주자.
https://pub.dev/packages/sqflite
dependencies:
flutter:
sdk: flutter
sqflite: ^2.0.0+3
path: ^1.8.0
sqlite를 넣어주고,
path 패키지도 넣어준다.
path 패키지는 sqlite를 사용하여 디스크에 저장할 데이터베이스의 위치를
정확히 정의할 수 있는 함수를 제공해주는 패키지입니다.!!
sqlite와 한쌍이라고 보시면 됩니다.
이제 두 패키지를 Pub get 해주시고, 사용법을 익혀봅시다.
우선 데이터베이스를 생성하는 동시에 테이블을 만들어 주겠습니다.
제가 사용한 거주지역 코드를 예시로 들겠습니다.
1. 데이터베이스와 테이블 만들기
class DataBase{
// 데이터 베이스를 담아두기 위한 변수(값이 있으면 중복 호출하지 않음.)
var _fixed_database;
// 데이터베이스 값 반환하기
Future<Database> get fixed_database async {
//데이터 베이스가 있으면 중복호출하지 않기 위해
// 변수에 있는 데이터베이스를 그대로 반환한다.
if (_fixed_database != null) return _fixed_database;
//openDatabase 메서드를 호출하여 데이터베이스를 OPEN한다.
_fixed_database = openDatabase(
join(await getDatabasesPath(), 'fixed_database.db'), //경로를 저장한다.
onCreate: (db, version) => createTable(db), //onCreate 인자의 생성한 디비를 넣어주어 테이블을 생성합니다.
version: 1, //데이터베이스의 업그레이드와 다운그레이드를 함으로써, 수정하기 위한 경로를 제공
);
return _fixed_database;
}
//테이블을 만들어 줍니다.
void createTable(Database db) {
// 거주지역
db.execute(
'CREATE TABLE location_cities (id INTEGER PRIMARY KEY, value TEXT)');
//이 아래로 여러 개의 테이블을 한번에 만들어도 됨.
//db.execute(
'CREATE TABLE '테이블명' (컬럼명1 INTEGER PRIMARY KEY, 컬럼명2 타입, 컬럼명3 타입)');
}
}
내가 빼곡하게 주석으로 쌓아두었다.
위에 코드 중에서 중요한 건
아래 createTable 메서드는 한 번만 호출해야 한다.
그래서
if(_fixed_database != null) return _fixed_database;
즉 _fixed_database에 값이 있으면(이미 한번 fixed_database가 실행이 되었으면)
값이 담겨 있는 _fixed_database함수를 리턴해주라는 것이다.
그리하여 createTable함수는 한 번만 호출되는 것이다.
그럼 테이블도 만들었으니 값을 넣어보자.
우선 값을 담아서 사용하기 용이하고,
맵핑을 하기 유용하게 할 수 있는 모델 클래스를 하나 만들어 주겠습니다.
//거주지역 클래스
class CityModel {
//멤버 변수
int id;
String location;
//생성자로 값을 바로 받고 멤버변수에 넣어줍니다.
CityModel({this.id, this.location});
//내부 디비용(sqlite)
Map<String, dynamic> toMap() {
return <String, dynamic>{
'id' : id,
'value' : location
};
}
}
저 같은 경우는 거주지역 모델 클래스인 CityModel을 정의하였습니다.
생성자를 통해 멤버 변수에 id값과 location값을 바로 전달할 수 있게 하였습니다.
아래 Map<String, dynamic> 타입으로 반환해주는 toMap() 함수를 정의하였습니다.
이후에 설명을 드릴 거지만,
왼쪽(key)은 내부 디비의 컬럼명이고, 오른쪽(value)은 생성자를 통해 들어온 담긴 멤버 변수 값들입니다.
이제 한 번 이 모델 클래스를 통해
내부 디비에 값을 저장해보겠습니다(insert)
class DataBase{
...
main(){
insertCityModel(CityModel('1', '서울'));
}
//INSERT
Future<void> insertCityModel(CityModel cityModel) async {
// 아까 전 만들었던 디비다. async await로 값을 가져오자.
final db = await fixed_database;
await db.insert(
'location_cities', //테이블 명
cityModel.toMap(), //내부 디비에 맵핑을 한 데이터를 넣는다.(아까전 toMap() 메서드)
conflictAlgorithm: ConflictAlgorithm.replace, //기본키 중복시 대체
);
}
}
insertCityModel() 메서드를 실행하여,
CityModel 값을 location_cities 테이블에 id와 value 컬럼에 저장하였다.
아까 전 모델 클래스 안에 있던 toMap() 메서드를 실행하여,
내가 넣었던 (1, 서울) 값을 맵핑하여 넣었습니다.
아래 conflictAlgorithm은 기본키 중복 시 대체로 넣는 것인데,
조건 충동을 해소하기 위한 알고리즘으로 이 알고리즘을 사용하면,
에러가 발생되지 않는다고 한다.
그러면 이제 값을 저장하였으니, 불러오는 방법을 알아보자.
class DataBase{
...
main(){
getAllCityModel().then((value) => print(value[0]));
}
//SELECT
Future<List<CityModel>> getAllCityModel() async {
final db = await fixed_database;
// 모든 CityModel을 얻기 위해 테이블에 질의합니다.
final List<Map<String, dynamic>> maps = await db.query('location_cities');
// List<Map<String, dynamic>를 List<CityModel>으로 변환합니다.
return List.generate(maps.length, (i) {
return CityModel(
id: maps[i]['id'],
location: maps[i]['value'],
);
});
}
insert부분과 데이터베이스를 가져오는 것은 다를 것이 없다.
중요한 것은 저 테이블에 질의하는 부분이다.
테이블명을 입력하면 해당 테이블의 데이터들을 전부 가져와
List형식으로 반환을 한다.
고로, 위에 찍힐 print값은 다음과 같을 것이다.
[CityModel{id:1, location:서울}]
만약 여러 값 저장하고, 불러온다면, 굉장히 많은 데이터들을 뽑아낼 수 있을 것이다.
그럼 여기서 궁금한 점이 생길 수 있다.
데이터를 전부 말고, 내가 원하는 데이터만 가져올 수는 없을까?
물론 방법이 있다.
만약 컬럼명이 location이고 서울이라고 쓰여 있는 값만 가져오고 싶으면
다음과 같이 작성하면 된다.
String location = '서울';
//SELECT
Future<List<CityModel>> getCityModel(String location) async {
final db = await fixed_database;
// 모든 CityModel을 얻기 위해 테이블에 질의합니다.
final List<Map<String, dynamic>> maps = await db.query(
'location_cities'
where: 'value = ?',
whereArgs: [location]
);
// List<Map<String, dynamic>를 List<CityModel>으로 변환합니다.
return List.generate(maps.length, (i) {
return CityModel(
id: maps[i]['id'],
location: maps[i]['value'],
);
});
}
query() 메서드 안에
이름 있는 인자인
where과 whereArgs를 사용하면 된다.
문서에 사용법은 저런 식으로 나와 있다.
where 인자로는 '디비컬럼' = ?
이런 식으로 입력하면 해당 칼럼을 지정하고,
whereArgs에는 찾고자 하는 인자를 넣어주면 된다.
whereArgs는 덤으로, 해커로 부터 영향받는
SQL Injection도 방지할 수 있다고 한다.
하지만 여기서 아쉽다.
다른 복잡한 조건으로 내가 원하는 값을 가져오고 싶다면,
rawQuery() 메서드를 사용하면 된다.!!
//SELECT
Future<List<CityModel>> getAllCityModel(String location) async {
final db = await fixed_database;
// 모든 CityModel을 얻기 위해 테이블에 질의합니다.
final List<Map<String, dynamic>> maps =
await db.rawQuery('SELECT * FROM location_cities WHERE value = $location');
// List<Map<String, dynamic>를 List<CityModel>으로 변환합니다.
return List.generate(maps.length, (i) {
return CityModel(
id: maps[i]['id'],
location: maps[i]['value'],
);
});
}
위에 식처럼
location_citiees 테이블의 인자로 넘어온 location값과 같은 value 값을 가진 데이터를
리스트로 반환하게 해 줄 수 있다.
rawQuery() 메서드는 sql문을 활용하여,
원하는 데이터를 가져올 수 있게 하므로,
sql문에 대해 더 알아보고, 잘 활용할 수 있을 것이다.
이제 마지막으로 데이터를 지우는 법을 알아보자.
String location = '서울';
//SELECT
Future<List<CityModel>> getCityModel(String location) async {
final db = await fixed_database;
// 모든 CityModel을 얻기 위해 테이블에 질의합니다.
final List<Map<String, dynamic>> maps = await db.delete(
'location_cities',
where: 'value = ?',
whereArgs: [location]
);
}
간단하다.
원하는 인자를 아까 전 SELECT부분처럼 인자를 넘겨주어 whereArgs에 넣고,
where에는 해당 컬러명을 넣고 ?를 넣어주어,
해당 데이터를 삭제해준다.
다른 점이 있다면, delete함수를 썼다는 것이다.
값을 수정(update) 하는 법도 이와 동일하다.
String location = '서울';
//SELECT
Future<List<CityModel>> getCityModel(CityModel cityModel) async {
final db = await fixed_database;
// 모든 CityModel을 얻기 위해 테이블에 질의합니다.
final List<Map<String, dynamic>> maps = await db.update(
'location_cities',
cityModel.toMap,
where: 'value = ?',
whereArgs: [location]
);
}
// 1의 지역을 수정합니다.
await updateCity(CityModel(
id: 1,
location: '부산'
));
말씀드린 것처럼 update() 메서드를 호출하여
바꾸고 싶은 값을 whereArgs 인자로 넘겨주어
where에 컬럼을 수정한다.
그리고 하나 더 추가된 것이 있다면,
값 자체를 넘겨 value가 일치하는 모델 값을 바꿔주는 것이다.
위와 같은 코드이면,
id = 1 값의 모델을 찾아 location을 부산으로 바꾸어주게 될 것이다.
오랜만에 개발 글을 써서 뿌듯하다.
이로써, sqlite의 중요한 부분에 대한 내용을 작성해보았다.
이 방법 말고도 여러 방법이 있을 것이다.
아무래도 rawQuery() 메서드로 웬만한 처리는 쉽지 않을까?
생각이 든다.
벌써 시간이 늦었다.
sqlite의 사용법을 마무리하겠다.
내일도 열심히 코딩을 해보자.
파이팅!!
flutter/dart pubspec.yaml를 이용해서 현재 앱 버전 정보 가져오기 TIL # 49 (0) | 2021.08.10 |
---|---|
Mac 안드로이드 스튜디오 한글 깨짐 현상 해결법 / TIL # 48 (2) | 2021.08.03 |
Flutter/Dart 리스트 슬라이드 로딩 구현 SmartRefresher 사용법 / Refresh 새로고침 구현 하기 TIL # 47 (2) | 2021.07.27 |
flutter/dart sort 간단한 설명 / List 정렬하기 TIL # 46 (0) | 2021.07.21 |
flutter/Dart SingleTon 싱글톤에 대해서 알아보자 / TIL # 44 (0) | 2021.07.11 |
Flutter/Dart 선택인자와 이름 있는 인자/ positional optional parameter && named optional parameter TIL # 43 (0) | 2021.07.08 |
안드로이드 플러터 StatelessWidget와 StatefulWidget의 생명주기 TIL # 42 (0) | 2021.06.19 |
Android Flutter Provider 패키지 정리/ feat.Consumer TIL # 41 (0) | 2021.06.15 |