티스토리 뷰

Mobile/Android

[Android] Android ORM: ROOM

춘햄 2022. 6. 10. 10:56

Android 는 단말의 자체 데이터베이스로 SQLite를 사용하고 있으며, 이를 그대로 어플에서 사용이 가능하다. 

이전에 포스팅했던 내용처럼 JDBC와 동일하게 POJO Style로 쿼리문을 작성하고, excute해도 문제가 없지만 개발하고 있는 어플의 특성상 데이터 저장 주기가 빈번하고, 데이터 수도 꽤나 많을 것으로 예상이 되어서 JPA와 같은 ORM 표준이 혹시 있나? 하는 마음에 찾아봤다. 

 

Room은 Android에서 사용을 적극 권장하는 프레임워크로 ORM이기 때문에 아래와 같은 장점을 가지고 있다.

  • SQL 쿼리의 컴파일 시간 확인
  • 반복적이고 오류가 발생하기 쉬운 상용구 코드를 최소화하는 편의 주석
  • 간소화된 데이터베이스 이전 경로

보다 정확한 레퍼런스와 자세한 구현 방법은 여기를 참조하자.

 

위 레퍼런스에서 객체간 관계 정의나 비동기 쿼리 등의 개념도 자세하게 다루고 있으니, 아래는 간단한 insert, select 테스트만 진행한다.


우선, Room을 위한 gradle설정부터 하자.

 

◎build.gradle

dependencies {
    def room_version = "2.4.2"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"

    // optional - RxJava2 support for Room
    implementation "androidx.room:room-rxjava2:$room_version"

    // optional - RxJava3 support for Room
    implementation "androidx.room:room-rxjava3:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "androidx.room:room-guava:$room_version"

    // optional - Test helpers
    testImplementation "androidx.room:room-testing:$room_version"

    // optional - Paging 3 Integration
    implementation "androidx.room:room-paging:2.5.0-alpha01"
}

사실 이것만으로 Room을 사용하기 위한 대부분의 준비가 끝났다고 볼 수 있다. 

 

 이제, 다른 ORM 프레임워크와 마찬가지로 Entity Class를 작성해준다.

 

◎LeagueRankEntity.class

@Entity(tableName = "league_rank")
public class LeagueRankEntity {
    @PrimaryKey
    private int rank;

    @ColumnInfo(name="team_code")
    private int teamCode;

    private int win;

    private int lose;

    private int wd;

    public int getRank() {
        return rank;
    }

    public void setRank(int rank) {
        this.rank = rank;
    }

    public int getTeamCode() {
        return teamCode;
    }

    public void setTeamCode(int teamCode) {
        this.teamCode = teamCode;
    }

    public int getWin() {
        return win;
    }

    public void setWin(int win) {
        this.win = win;
    }

    public int getLose() {
        return lose;
    }

    public void setLose(int lose) {
        this.lose = lose;
    }

    public int getWd() {
        return wd;
    }

    public void setWd(int wd) {
        this.wd = wd;
    }
}

 

이제, 이 데이터베이스의 쿼리를 실행할 DAO클래스를 다음과 같이 작성한다.

 

◎TestDAO.class

@Dao
public interface TestDAO {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public void insertLeagueRankEntity(LeagueRankEntity leagueRankEntity);

    @Query("SELECT * FROM league_rank")
    public List<LeagueRankEntity> loadAllLeagueRankEntity();
}

 

DAO 클래스 작성이 완료 되었으니, 해당 DB 인스턴스를 생성하고 제어할 Database 클래스를 작성한다.

 

Database 클래스는 RoomDatabase를 상속해야하며, @Database어노테이션 내부에 해당 DB가 가지고 있는 모든 테이블 엔티티 클래스를 추가해줘야 한다.

 

또한 Database 인스턴스를 생성할 때 리소스가 꽤나 많이 들어가기 때문에 Android는 이를 아래와 같이 싱글톤으로 작성하는 것을 권장하고 있다.

 

◎AppDatabase.class

@Database(entities = {
        ChampionEntity.class,
        ChampionCounterEntity.class,
        LeagueRankEntity.class,
        LeagueScheduleEntity.class,
        MainRosterEntity.class,
        MatchDataEntity.class,
        PlayerChampionPlayDataEntity.class,
        PlayerEntity.class,
        PogPointRankEntity.class,
        SubRosterEntity.class,
        TeamEntity.class,
        TransferWindowEntity.class,
        UserEntity.class,
        UserRecordEntity.class
}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static AppDatabase database;

    private static String DATABASE_NAME = "database";

    public synchronized static AppDatabase getInstance(Context context)
    {
        if (database == null)
        {
            database = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME)
                    .allowMainThreadQueries()
                    .fallbackToDestructiveMigration()
                    .build();
        }
        return database;
    }

    public abstract TestDAO textDao();
}

 

생성한 DB 클래스 내부에는 사용할 DAO 클래스도 위와 같이 명시해줘야 한다.

 

위 코드에서 databaseBuilder를 사용할 때 allowMainThreadQueries는 메인 쓰레드에서 DB 쿼리를 실행하게 해주는 메서드이다.

 

이는 테스트를 위해 추가했을 뿐, 메인 쓰레드에서 쿼리를 실행하는 것은 전혀 권장되지 않는다.

 

이제 간단한 insert와 select문을 실행하여 테스트 결과를 확인해보자.

 

◎MainActivity.class

 

AppDatabase db;

LeagueRankEntity leagueRankEntity;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    db = AppDatabase.getInstance(this);


    leagueRankEntity = new LeagueRankEntity();
    leagueRankEntity.setRank(12);
    leagueRankEntity.setLose(8);
    leagueRankEntity.setWin(5);
    leagueRankEntity.setWd(-3);
    leagueRankEntity.setTeamCode(3)

    testDataInsertAndSelect(db, leagueRankEntity);

  }
  
  
 private void testDataInsertAndSelect(AppDatabase db, LeagueRankEntity leagueRankEntity) {
        TestDAO testDAO = db.textDao();
        testDAO.insertLeagueRankEntity(leagueRankEntity);
        List<LeagueRankEntity> temp = testDAO.loadAllLeagueRankEntity();

        Log.d("test:", Integer.toString(temp.get(0).getRank()));
}

돌려보면, 정상적으로 로그가 찍히는 것을 확인할 수 있다.

 

Comments