
SQLiteOpenHelper 클래스, SQLiteDatabase 클래스, Cursor 인터페이스 활용
| 클래스 | 메소드 | 주요 용도 |
|---|---|---|
| SQLiteOpenHelper | 생성자 | DB 생성 |
| onCreate() | 테이블 생성 | |
| OnUpgrade() | 테이블 삭제 후 다시 생성 | |
| getReadableDatabase() | 읽기 전용 DB 열기, SQLiteDatabase 반환 | |
| getWritableDatabase() | 읽고 쓰기용 DB 열기, SQLiteDatabase 반환 | |
| SQLiteDatabase | execSQL() | SQL문(Insert/Update/Delete)실행 |
| close() | DB 닫기 | |
| query(), rawQuery() | Select 실행 후 커서 반환 | |
| 생성자 | DB 생성 | |
| 생성자 | DB 생성 |
| 인터페이스 | 메소드 | 주요 용도 |
|---|---|---|
| Cursor | moveToFirst() | 커서의 첫 행으로 이동 |
| moveToLast() | 커서의 마지막 행으로 이동 | |
| moveToNext() | 현재 커서의 다음 행으로 이동 | |
| getInt(), getString(), getLong() .. | DB 테이블의 실제 데이터 반환 | |
| getColumnIndex(String columnName) | DB 테이블의 해당 필드명 반환 | |
| getColumnName(int columnIndex) | 필드 index에 해당하는 필드명 반환 | |
| getPosition() | Cursor가 가리키고 있는 DB 테이블 행 positon 반환 | |
| getCount() | Cursor가 참조할 수 있는 해당 테이블의 행의 갯수 반환 | |
| getColumnCount() | DB 테이블의 필드 갯수 반환 |
보통 SQLiteOpenHelper 클래스를 상속받아 앱 실행 시
onCreate() 메서드를 통해 처음 데이터베이스를 생성하고
테이블을 정의하여 초기 데이터를 입력할 수 있다.
하지만 내가 구현하고자 하는 술안주 월드컵 앱은
술 안주의 메뉴, 메뉴 사진 경로, 술집 위도, 경도 등등..
데이터베이스에 저장해야 할 데이터가 꽤나 있기 때문에
앱 실행 시 모든 데이터를 입력하고 DB를 생성하는 것은
앱 로딩 시간이 오래걸려 비효율적일 것 같다는 생각이 문득 들었다.
이미 만들어 놓은 데이터베이스 테이블과 데이터 정보를 저장해둔
데이터베이스 파일을 바로 이용할 수 있는 방법이 없을까?
1.DB Browser for SQLite를 사용해서 미리 구축한 DB를 저장해둔다.
2. Assets 폴더 생성 후, 이 폴더 안에 미리 만들어둔 DB 파일을 넣는다.


위 이미지처럼 worldcupDB.db 파일을 assets 폴더 내에 복사하면
/main/assets/worldcupDB.db 경로를 가진다.
AndroidManifest.xml 파일에 권한을 추가해야 한다.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
public class DataBaseHelper extends SQLiteOpenHelper {
private final static String TAG = "DataBaseHelper";
private static String DB_PATH = "";
private final static String DB_NAME = "worldcupDB.db";
private static final int DB_VERSION = 1;
private SQLiteDatabase mDataBase;
private Context mContext;
public DataBaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
DB_PATH = "/data/data/" + context.getPackageName() + "/databases/";
this.mContext = context;
dataBaseCheck();
}
private void dataBaseCheck() {
File dbFile = new File(DB_PATH + DB_NAME);
if (!dbFile.exists()) {
dbCopy();
Log.d(TAG,"Database is copied.");
}
}
@Override
public synchronized void close() {
if (mDataBase != null) {
mDataBase.close();
}
super.close();
}
@Override
public void onCreate(SQLiteDatabase db) {
Log.d(TAG,"onCreate()");
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
Log.d(TAG,"onOpen() : DB Opening!");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG,"onUpgrade() : DB Schema Modified and Excuting onCreate()");
}
private void dbCopy() {
try {
File folder = new File(DB_PATH);
if (!folder.exists()) {
folder.mkdir();
}
String out_filename = DB_PATH + DB_NAME;
InputStream inputStream = mContext.getAssets().open(DB_NAME);
OutputStream outputStream = new FileOutputStream(out_filename);
byte[] mBuffer = new byte[1024];
int mLength;
while ((mLength = inputStream.read(mBuffer)) > 0) {
outputStream.write(mBuffer,0,mLength);
}
outputStream.flush();;
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
Log.d("dbCopy","IOException 발생함");
}
}
}
DataBaseHelper 생성자에서 생성과 동시에 dataBaseCheck() 메서드에서
해당 DB파일이 없다면 assets 폴더의 DB파일을 복사해오는 로직이 있다.
SQLite는 내장 데이터베이스이기에
복사된 DB파일은 Device File Explorer에서 아래 경로로 확인할 수 있다.
date > data > com. (프로젝트 이름) > databases > (DB 이름).db
데이터베이스의 menu 테이블에서 선택된 라운드에 따라
16개, 8개, 4개의 메뉴를 가져오는 코드 구현해야 한다.
하지만
ANR(Application Not Responding) 오류 발생 가능 → 즉, UI가 응답하지 않음.라운드에 맞는 갯수만큼 랜덤으로 인덱스를 추출하여 List에 저장한다.
랜덤 인덱스가 저장된 리스트를 반환하기 전 오름차순으로 정렬한다.
cursor.getPosition() 메서드를 사용해 현재 커서가 가르키고 있는 행의 index와 랜덤 index가 동일한 데이터만 menuList에 저장한다.
ANR 오류를 방지하기 위해 스레드풀을 사용하여 데이터베이스 작업은 백그라운드 스레드에서 처리한다.
public class WorldCupActivity extends AppCompatActivity {
int round;
DataBaseHelper worldCupDB;
SQLiteDatabase db;
List<MenuInfo> menuList;
List<Integer> randomColumnIndexes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_worldcup);
round = getIntent().getIntExtra("ROUND_NUMBER", 0);
Log.d("WorldCupActivity", round + "round start");
worldCupDB = new DataBaseHelper(WorldCupActivity.this);
db = worldCupDB.getWritableDatabase();
// Run database operation in a new thread
new Thread(() -> {
getMenuByRound();
}).start();
}
private void getMenuByRound() {
Cursor cursor = db.rawQuery("SELECT * FROM menu;", null);
if (cursor != null && cursor.moveToFirst()) {
int totalColumns = cursor.getCount();
Log.d("WorldCupActivity","DB select Record : " + totalColumns);
randomColumnIndexes = getRandomIndexes(totalColumns, round);
menuList = new ArrayList<>();
do {
MenuInfo menuInfo;
for (int index : randomColumnIndexes) {
if(cursor.getPosition() == index){
String menuName = cursor.getString(1);
String imagePath = cursor.getString(2);
menuInfo = new MenuInfo(menuName, imagePath);
menuList.add(menuInfo);
}
}
} while (cursor.moveToNext());
Log.d("WorldCupActivity","Menu information list complete");
cursor.close();
} else {
Log.d("Menu", "No data available");
}
runOnUiThread(() -> {
// UI-related operations after database operation
printMenuList();
});
}
private List<Integer> getRandomIndexes(int totalColumns, int limit) {
List<Integer> indexes = new ArrayList<>();
Random random = new Random();
while (indexes.size() < limit) {
int randomIndex = random.nextInt(totalColumns);
if (!indexes.contains(randomIndex)) {
indexes.add(randomIndex);
}
}
Collections.sort(indexes);
Log.d("WorldCupActivity", "random number size : " + indexes.size());
return indexes;
}
private void printMenuList() {
if (menuList != null && !menuList.isEmpty()) {
for (MenuInfo menuInfo : menuList) {
Log.d("MenuInfo", "Menu Name: " + menuInfo.getMenuName() + ", Image Path: " + menuInfo.getImagePath());
}
} else {
Log.d("MenuInfo", "MenuList is empty or null");
}
}
}
8강 선택시 8개의 술안주 메뉴를 성공적으로 추출하는 것을 아래 log를 통해 확인할 수 있다.
