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에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
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의 사용방법에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
오늘도 감사합니다. 😀
그리드형