React & React Native/오류노트

[RN/오류노트] Solved - database is locked : SQLite

adjh54 2024. 12. 15. 20:00
728x170
해당 글에서는 database is locked 오류에 대해서 이를 해결하는 방법에 대해 알아봅니다

1) 문제점


💡 문제점

- 모바일 기기에서 API 통신을 하는 도중에 아래와 같은 오류가 발생하였습니다.

- database is locked

- 해당 환경에서 데이터베이스를 사용하는 경우는 내부 DB로 SQLite를 사용하였고, 외부 DB로 PostgreSQL을 사용하고 있습니다.

 

💡 HTTP Client를 통한 로컬 데이터베이스 조회

- 실제 IntelliJ HTTP Client로 호출을 하였을때 database is locked라는 문제가 발생하지 않았습니다.
- 또한, 특정 모바일 기기에서만 수행이 되기에 해당 외부 PostgreSQL은 문제가 되지 않는다고 판단이 되었습니다.

 

💡 [참고] HTTP Client에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
 

[IntelliJ] HTTP Client 사용하기 : Postman 대체하기

해당 글에서는 Postman을 대체하여 클라이언트에서 서버로 API를 전송(Request)하고 반환(Response)을 받는 테스트에 사용이 되는 HTTP Client에 대해서 공유합니다. 1) 문제사항 및 적용 계기 💡 Client에서

adjh54.tistory.com

 

 

 

2) 해결방법


💡 연결 관리 미흡 적용

- 데이터베이스 인스턴스를 생성하고 연결을 닫지 않은 경우에 발생할 수 있습니다.
- 아래와 같은 경우에서는 데이터베이스 인스턴스를 생성하고 dbInstance.transaction를 통해서 트렌잭션을 수행하고, 별도의 인스턴스를 닫아주는 작업이 처리가 되지 않았습니다.
/**
 * 데이터베이스 인스턴스를 생성합니다.
 * @returns 
 */
async createDBInstance(): Promise<SQLite.SQLiteDatabase | { transaction: () => { executeSql: () => void } }> {
    if (Platform.OS === "web") {
        return {
            transaction: () => ({
                executeSql: () => { },
            }),
        };
    }
    const dbInstance = SQLite.openDatabase(this.DATABASE_NAME);

    if (!dbInstance) {
        throw new Error("데이터베이스 인스턴스를 생성할 수 없습니다.")
    }

    await this.enableForeignKeys(dbInstance);   // 왜래키 제약조건을 활성화하여 인스턴스를 최종 구성합니다.
    return dbInstance;
}

 /**
   * 데이터 베이스 명령어를 수행합니다.
   * @param query 
   * @returns 
   */
  async executeQuery(query: string, params?: any[]): Promise<any> {
      if (params && params.length === 0) params = [];
      console.log("파라미터를 확인합니다.", params)

      let execute
      const dbInstance = await this.createDBInstance();
      try {
          execute = new Promise(async (resolve, reject) => {
              dbInstance.transaction(tx => {
                  tx.executeSql(
                      query,
                      params,
                      (_, result) => {
                          console.log("[+] SQL 실행 성공 :: ", result)
                          resolve(result.rows._array)
                      },
                      (_, error) => {
                          reject(`SQL 실행 실패: ${error}`);
                          return false;
                      }
                  );
              });
          });
      } catch (error) {
          console.log(`쿼리 실행중에 오류가 발생하였습니다`, error)
      }
      return execute;

  }

 

 

 

 💡 이를 위해서 아래와 같이 생성한 인스턴스는 트랜잭션 수행이후에 close() 해주도록 처리를 수행하였습니다.

- SQLite의 transaction() 함수의 두번째, 세번째 인자로 errorCallback, successCallback를 제공하기 이에 따르는 후처리로 성공 시 dbInstance를 닫아주는 형태로 구성하여 이를 해결하였습니다.
/**
 * 데이터베이스 인스턴스를 생성하고 이를 반환합니다.
 * @returns {Promise<SQLite.SQLiteDatabase | { transaction: () => { executeSql: () => void } }>}
 */
private async createDBInstance(): Promise<SQLite.SQLiteDatabase> {
    // Web의 경우에는 이를 사용할 수 없습니다.
    if (Platform.OS === "web") {
        return {
            transaction: () => ({
                executeSql: () => { },
            }),
        } as any;
    }
    // 데이터베이스 인스턴스가 존재하지 않는 경우 이를 생성하고, 제약조건을 추가합니다.
    const dbInstance = SQLite.openDatabase(this.DATABASE_NAME); // 데이터베이스를 열어주고 값을 대입합니다.
    await this.enableForeignKeys(dbInstance);                   // 왜래키 제약조건을 활성화 합니다.
    return dbInstance;
}

 /**
   * 데이터베이스 인스턴스를 닫아줍니다.
   * @returns {Promise<void>}
   */
  private async closeDatabase(dbInstance): Promise<void> {
      await dbInstance.closeAsync();
      dbInstance = null;
  }
  
  /**
     * 데이터베이스 명령어를 전달받은 파라미터 SQL을 기반으로 수행합니다
     * - params는 Optional하게 존재 할 수도 있고 존재하지 않을 수도 있습니다.
     * @param {string} query 
     * @param {any[]} params 
     * @returns 
     */
    async executeQuery(query: string, params?: any[]): Promise<any> {
        if (params && params.length === 0) params = [];
        try {
            this.dbInstance = await this.createDBInstance() as SQLite.SQLiteDatabase;
            const execute = new Promise(async (resolve, reject) => {
                // 해당 트랜잭션을 수행하면 성공 시 COMMIT / 실패 시 ROLLBACK이 수행됩니다.
                this.dbInstance!.transaction(async (tx) => {
                    tx.executeSql(query, params,
                        // SQL 실행 성공
                        (_, result) => {
                            resolve(result.rows._array)
                        },
                        // SQL 실행 실패 
                        (_, error) => {
                            reject(error);
                            return false;
                        },
                    )
                },
                    // errorCallback: 트랜잭션 실패 시 수행
                    (error) => {
                        console.log("[-] 트랜잭션 실패 :: ", error);
                        reject(error);
                    },

                    // successCallback: 트랜잭션 성공 시 수행
                    () => {
                        // 트랜잭션이 성공적으로 완료된 후에만 데이터베이스를 닫습니다
                        this.closeDatabase(this.dbInstance)
                            .then(() => console.log("[+] 데이터베이스 연결 종료"))
                            .catch(error => console.log("[-] 데이터베이스 연결 종료 실패 :: ", error));
                    });
            });
            return execute;
        } catch (error) {
            throw new Error(`쿼리 실행중에 오류가 발생하였습니다 :: ${error}`,)
        }
    }

 

 

💡 [참고] React Native의 SQLite의 사용방법에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
 

[RN] React Native expo-sqlite 이해 및 설정 방법 -2 : 활용 방법 및 데이터 확인 방법

해당 글에서는 React Native에서 expo-sqlite를 이용하는 활용 방법 및 데이터베이스 데이터를 GUI 툴을 이용하여 확인하는 방법에 대해 알아봅니다 💡 [참고] 이전에 작성한 글을 참고하시면 도움이

adjh54.tistory.com

 

 

 

 

 

오늘도 감사합니다. 😀

 

 

 

그리드형