RxJava Sqlite with Room

By | 2021년 11월 2일
Table of Content

RxJava Sqlite with Room

참조

참조

의존성 추가

2021-11-02 일자 기준으로 Room 은 2.3.0 이 안전화 버전이다.

dependencies {
    implementation 'io.reactivex.rxjava2:rxjava:2.2.21'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation "androidx.room:room-rxjava2:2.3.0"
    implementation "androidx.room:room-runtime:2.3.0"
    annotationProcessor "androidx.room:room-compiler:2.3.0"

    // LocalDateTime 백포트 버전
    implementation 'com.jakewharton.threetenabp:threetenabp:1.3.0'
}

Entity 생성

Room 은 String Boot JPA 와 유사한 형식인듯(?)

LocalDateTime 을 sqlite 에서 처리를 못하기에, 스트링으로 변환해 준다.

import 가 org.threeten.bp.LocalDateTime 이다.
(왜 뜬금없이 이상한 라이브러리 쓸까 궁금하시면 안쓰시면 이유를 알게됩니다.)

import org.threeten.bp.LocalDateTime;

public class LocalDateTimeConverter {

    @TypeConverter
    public static LocalDateTime toDate(String dateString) {
        if (dateString == null) {
            return null;
        } else {
            return LocalDateTime.parse(dateString);
        }
    }

    @TypeConverter
    public static String toDateString(LocalDateTime date) {
        if (date == null) {
            return null;
        } else {
            return date.toString();
        }
    }
}

import 가 org.threeten.bp.LocalDateTime 이다.

@Entity(tableName = "tbl_favorite_folder", indices = {@Index(value = {"id"}, unique = true)})
public class FavoriteFolder {

    @PrimaryKey(autoGenerate = true)
    int id;

    @ColumnInfo(name = "name")
    String name;

    @ColumnInfo(name = "status")
    String status;

    @ColumnInfo(name = "display_order")
    int displayOrder;

    @ColumnInfo(name = "modify_time")
    @TypeConverters({LocalDateTimeConverter.class})
    LocalDateTime modifyTime;

    @ColumnInfo(name = "insert_time")
    @TypeConverters({LocalDateTimeConverter.class})
    LocalDateTime insertTime;

    public FavoriteFolder(String name, String status, int displayOrder) {
        this.name = name;
        this.status = status;
        this.displayOrder = displayOrder;
        if (id == 0) {
            insertTime = LocalDateTime.now();
        }
        modifyTime = LocalDateTime.now();
    }

    // Getter() and Setter()
}

Dao 생성

FlowableObservable 의 변형으로,
DB 에서의 데이타가 많은 경우 일정량씩 잘라서 순차적으로 보내주는 기능이다.

구현 복잡도가 올라가니 Single<List<FavoriteFolder>> 로 바꾸고,
Dao 쪽에서 페이징 기능을 넣는 것이 낫다.

CompletableObservable 의 변형으로, Response 가 없을때 쓰인다.

SingleObservable 의 변형인데, Query 의 종류에 따라 리턴값이 달라진다.

  • Insert

    Single<Long>, Single<List<Long>> 의 형태로 생성된 데이타의 PK 를 반환받는다.

  • Update/Delete

    Single<Integer> 의 형태로 rows affected 가 반환된다.

  • Select

    Single<User>, Single<List<User>> 형태로 검색된 데이타가 반환된다.

@Dao
public interface FavoriteFolderDao {

    @Query("SELECT * FROM tbl_favorite_folder ORDER BY display_order, id LIMIT 20 OFFSET (20 * :page)")
    Single<List<FavoriteFolder>> getFolderList(Integer page);  // start from 0

    @Query("DELETE FROM tbl_favorite_folder ")
    Completable deleteAllItem();

    @Query("DELETE FROM tbl_favorite_folder  WHERE id = :id")
    Single<Integer> deleteFolder(Integer id);

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    Single<Long> insertFolder(FavoriteFolder favoriteFolder);

    @Query("SELECT * FROM tbl_favorite_folder WHERE id = :id")
    Single<FavoriteFolder> getFolder(Integer id);

    @Update
    Single<Integer> modifyFolder(FavoriteFolder favoriteFolder);
}

데이타베이스 생성

@Database(entities = { FavoriteFolder.class }, version = 1, exportSchema = false)
public abstract class AppDataBase extends RoomDatabase {

    public abstract FavoriteFolderDao RoomDao();

    private static AppDataBase instance = null;
    private static String DB_NAME = "db_mg";

    public static AppDataBase getInstance(Context context) {
        return instance != null ? instance : buildDataBase(context);
    }

    private static AppDataBase buildDataBase(Context context ) {

        return Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class,DB_NAME)
                .fallbackToDestructiveMigration()
                .addCallback(new RoomDatabase.Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);
                    }
                }).build();
    }
}

MainActivity

subscribeOn 은 첫 연산의 실행을 어느 Thread 에서 할지를 결정한다.
observeOn 은 다음 연산이 어느 Thread 에서 실행할지 결정한다.

.subscribeOn(Schedulers.io()) 은 첫 연산(DB 연결하여 데이타 습득) 이 IO Thread 에서 실행되는 것을 의미한다.

.observeOn(AndroidSchedulers.mainThread()) 은 다음 연산(UI 에 데이타 표시)를 Android Main Thread 에서 실행됨을 의미한다.

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    // ......
    private AppDataBase appDataBase;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ......
        appDataBase = AppDataBase.getInstance(this);
        AndroidThreeTen.init(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_value_button:
                appDataBase.RoomDao().insertFolder(new FavoriteFolder("name", "status", 1))
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .doOnSuccess(id -> {
                            Log.d("getKeyHash", "Inserted row id : " + id);
                        })
                        .doOnError(throwable -> {
                            Log.e("getKeyHash", "Error : " + throwable.toString());
                        })
                        .subscribe();

                appDataBase.RoomDao().getFolder(1)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .doOnSuccess(favoriteFolder -> {
                            Log.i("getKeyHash", "Data Found : " + favoriteFolder.getName());
                        })
                        .doOnError(throwable -> {
                            Log.e("getKeyHash", "Error : " + throwable.toString());
                        })
                        .subscribe();

                appDataBase.RoomDao().getFolderList(0)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .doOnSuccess(items -> {
                            Log.d("getKeyHash", "Total count : " + items.size());
                            for (FavoriteFolder item : items) {
                                Log.d("getKeyHash", item.getName());
                                Log.d("getKeyHash", "aaa");
                            }
                        })
                        .doOnError(throwable -> {
                            Log.e("getKeyHash", "Error : " + throwable.toString());
                        })
                        .subscribe();

                break;
        }
    }
}

여담

처음 안드로이드 개발했을 때,
sqlite 는 날쿼리로 작업하고,
비동기 실행따위 안중에도 없이 Main Thread 에서 작업했던 기억이…. ㅋㅋㅋㅋ

One thought on “RxJava Sqlite with Room

답글 남기기