# 데이터베이스 쿼리 및 관리

#### 데이터베이스 연결 설정

Dart에서 데이터베이스를 사용하려면 먼저 데이터베이스와의 연결을 설정해야 한다. 일반적으로 Dart에서는 `sqflite` 패키지나 `postgres` 패키지와 같은 라이브러리를 사용하여 데이터베이스에 접근할 수 있다. SQLite는 로컬 데이터베이스로 많이 사용되고, PostgreSQL은 서버 기반 데이터베이스로 많이 사용된다.

**SQLite 연결 예시**

SQLite 데이터베이스를 사용하는 경우, 데이터베이스 파일을 지정하고 연결을 열어야 한다.

```dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

void main() async {
  var database = openDatabase(
    join(await getDatabasesPath(), 'my_database.db'),
    onCreate: (db, version) {
      return db.execute(
        'CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
      );
    },
    version: 1,
  );
}
```

여기서 `openDatabase` 함수는 데이터베이스 연결을 열고, 데이터베이스가 없으면 자동으로 생성한다.

#### 데이터 삽입

데이터베이스에 데이터를 삽입하려면 `INSERT` 쿼리를 사용한다. Dart에서는 보통 `insert` 함수를 통해 데이터를 삽입한다.

```dart
Future<void> insertUser(User user) async {
  final db = await database;

  await db.insert(
    'users',
    user.toMap(),
    conflictAlgorithm: ConflictAlgorithm.replace,
  );
}
```

위 코드에서는 `insertUser` 함수를 통해 사용자의 데이터를 `users` 테이블에 삽입하고, 만약 같은 ID가 이미 존재한다면 덮어쓰기를 선택하는 방식이다.

#### 데이터 검색

데이터를 검색할 때는 `SELECT` 쿼리를 사용한다. Dart에서 데이터 검색은 주로 `query` 함수를 통해 이루어진다.

```dart
Future<List<User>> users() async {
  final db = await database;
  final List<Map<String, dynamic>> maps = await db.query('users');

  return List.generate(maps.length, (i) {
    return User(
      id: maps[i]['id'],
      name: maps[i]['name'],
      age: maps[i]['age'],
    );
  });
}
```

위 함수는 `users` 테이블에서 모든 사용자의 데이터를 검색하여 `User` 객체 리스트로 변환한다.

#### 데이터 업데이트

데이터베이스에 저장된 데이터를 업데이트하려면 `UPDATE` 쿼리를 사용한다. Dart에서는 `update` 함수를 통해 데이터를 업데이트할 수 있다.

```dart
Future<void> updateUser(User user) async {
  final db = await database;

  await db.update(
    'users',
    user.toMap(),
    where: 'id = ?',
    whereArgs: [user.id],
  );
}
```

위 코드에서는 특정 `id` 값을 가진 사용자의 데이터를 업데이트한다. `where`와 `whereArgs`는 업데이트할 데이터를 특정하는 데 사용된다.

#### 데이터 삭제

데이터를 삭제하려면 `DELETE` 쿼리를 사용한다. Dart에서는 `delete` 함수를 통해 특정 데이터를 삭제할 수 있다.

```dart
Future<void> deleteUser(int id) async {
  final db = await database;

  await db.delete(
    'users',
    where: 'id = ?',
    whereArgs: [id],
  );
}
```

위 코드에서는 주어진 `id` 값을 가진 사용자의 데이터를 삭제한다.

#### 데이터베이스 트랜잭션 관리

데이터베이스 트랜잭션은 여러 SQL 작업을 하나의 원자적 단위로 처리하여, 중간에 오류가 발생할 경우 모든 작업을 원래 상태로 롤백하는 기능을 제공한다. Dart에서 트랜잭션을 관리하려면 `transaction` 함수를 사용하여 여러 SQL 작업을 하나의 트랜잭션으로 묶을 수 있다.

```dart
Future<void> performTransaction() async {
  final db = await database;

  await db.transaction((txn) async {
    await txn.insert('users', {'id': 1, 'name': 'Alice', 'age': 25});
    await txn.update('users', {'name': 'Bob'}, where: 'id = ?', whereArgs: [1]);
    await txn.delete('users', where: 'id = ?', whereArgs: [1]);
  });
}
```

위 코드에서는 `insert`, `update`, `delete` 명령이 하나의 트랜잭션으로 묶여 있으며, 트랜잭션 중 하나라도 실패하면 모든 변경 사항이 롤백된다.

#### 인덱스 최적화

데이터베이스에서 인덱스는 검색 성능을 크게 향상시키는 중요한 요소이다. 인덱스를 사용하면 테이블 내 특정 열에 대한 검색 속도를 높일 수 있다. SQLite에서는 `CREATE INDEX` 문을 사용하여 인덱스를 생성한다.

```dart
await db.execute('CREATE INDEX idx_user_name ON users(name)');
```

위 코드에서는 `users` 테이블의 `name` 열에 대한 인덱스를 생성하여, 해당 열을 검색할 때 성능을 개선한다.

#### 데이터베이스 조인

여러 테이블에서 데이터를 동시에 가져오기 위해 `JOIN`을 사용할 수 있다. Dart에서는 `query` 함수에 `JOIN` 구문을 직접 사용할 수 있다.

```dart
Future<List<User>> fetchUsersWithOrders() async {
  final db = await database;

  final List<Map<String, dynamic>> result = await db.rawQuery(
    'SELECT users.name, orders.order_id FROM users '
    'INNER JOIN orders ON users.id = orders.user_id'
  );

  // 결과를 변환하여 사용
  return List.generate(result.length, (i) {
    return User(
      name: result[i]['name'],
      orderId: result[i]['order_id'],
    );
  });
}
```

이 코드는 `users` 테이블과 `orders` 테이블을 `INNER JOIN`으로 연결하여 두 테이블에서 데이터를 동시에 가져온다.

#### 데이터베이스 최적화 기법

데이터베이스 최적화는 쿼리 성능을 높이는 중요한 기술이다. 주요 최적화 방법은 다음과 같다:

1. **쿼리 최소화**: 여러 데이터를 한 번에 가져올 수 있도록 쿼리를 줄이다. 예를 들어, 중복된 쿼리를 여러 번 호출하는 대신 한 번에 데이터를 가져온다.
2. **인덱스 사용**: 자주 검색되는 열에 인덱스를 추가하여 검색 속도를 향상시킨다.
3. **캐시 활용**: 데이터를 메모리에 캐싱하여, 데이터베이스에 대한 과도한 요청을 줄이다.

#### 데이터베이스 보안

데이터베이스 보안은 데이터 무결성과 기밀성을 유지하는 데 중요한 요소이다. Dart 애플리케이션에서 데이터베이스 보안을 강화하기 위한 몇 가지 방법은 다음과 같다:

1. **SQL 인젝션 방지**: 사용자 입력을 직접 SQL 쿼리에 포함하지 않고, `whereArgs` 매개변수를 활용하여 SQL 인젝션 공격을 방지한다.

   ```dart
   await db.rawQuery('SELECT * FROM users WHERE name = ?', [userInput]);
   ```
2. **암호화**: 민감한 정보를 저장할 때 데이터베이스에 저장되기 전에 데이터를 암호화한다. Dart에서 `encrypt` 패키지를 사용하여 데이터를 암호화할 수 있다.
3. **접근 제어**: 데이터베이스 사용자 계정을 생성하고, 최소 권한 원칙을 적용하여 사용자에게 필요한 권한만 부여한다.

#### 데이터베이스 백업 및 복구

데이터베이스의 데이터 손실을 방지하기 위해 정기적인 백업 및 복구 전략이 필요하다. Dart에서는 파일 시스템 API를 사용하여 데이터베이스 파일을 복사하여 백업할 수 있다.

```dart
import 'dart:io';

Future<void> backupDatabase() async {
  final dbPath = await getDatabasesPath();
  final databaseFile = File('$dbPath/my_database.db');
  final backupFile = File('$dbPath/my_database_backup.db');

  await databaseFile.copy(backupFile.path);
}
```

이 코드는 현재 데이터베이스 파일을 복사하여 백업을 생성하는 간단한 예시이다.

#### 데이터베이스 성능 모니터링

성능 모니터링은 데이터베이스의 건강 상태를 유지하고 성능 문제를 미리 발견하는 데 중요하다. Dart에서는 성능을 모니터링하기 위해 몇 가지 방법을 사용할 수 있다:

1. **쿼리 로그**: 데이터베이스에서 실행된 쿼리 로그를 기록하여 성능이 낮은 쿼리를 식별한다.
2. **데이터베이스 상태 확인**: 특정 주기로 데이터베이스의 성능 지표를 확인하고, 이상 징후를 감지하여 사전 예방적으로 조치를 취한다.

#### 예제 코드 통합

데이터베이스 쿼리 및 관리의 여러 기능을 통합한 전체적인 예제 코드는 다음과 같다.

```dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class User {
  final int id;
  final String name;
  final int age;

  User({required this.id, required this.name, required this.age});

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'name': name,
      'age': age,
    };
  }
}

class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  static Database? _database;

  factory DatabaseHelper() {
    return _instance;
  }

  DatabaseHelper._internal();

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  Future<Database> _initDatabase() async {
    return await openDatabase(
      join(await getDatabasesPath(), 'my_database.db'),
      onCreate: (db, version) {
        return db.execute(
          'CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
        );
      },
      version: 1,
    );
  }

  Future<void> insertUser(User user) async {
    final db = await database;
    await db.insert(
      'users',
      user.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  Future<List<User>> fetchUsers() async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query('users');

    return List.generate(maps.length, (i) {
      return User(
        id: maps[i]['id'],
        name: maps[i]['name'],
        age: maps[i]['age'],
      );
    });
  }
}
```

이 코드에서는 `User` 클래스를 통해 사용자의 정보를 저장하고, `DatabaseHelper` 클래스를 통해 데이터베이스와의 상호작용을 관리한다. 삽입, 조회 등의 기능이 포함되어 있다.
