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 생성
Flowable
은 Observable
의 변형으로,
DB 에서의 데이타가 많은 경우 일정량씩 잘라서 순차적으로 보내주는 기능이다.
구현 복잡도가 올라가니 Single<List<FavoriteFolder>>
로 바꾸고,
Dao 쪽에서 페이징 기능을 넣는 것이 낫다.
Completable
은 Observable
의 변형으로, Response 가 없을때 쓰인다.
Single
도 Observable
의 변형인데, 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 에서 작업했던 기억이…. ㅋㅋㅋㅋ
https://medium.com/androiddevelopers/room-rxjava-acb0cd4f3757