ORM 래퍼 라이브러리 Room을 Realm으로 바꾸기 (Kotlin)
Kotlin용 Room 샘플 다운로드
정확하게는 Room & Rx Java (Kotlin)이지만 AndroidStudio에서 샘플을 다운로드할 수 있습니다.
프로젝트를 열지 않은 화면 또는 File>new>Import Sample에서
다만 프로젝트에 문제가 있어 빌드할 수 없고, RxJava는 사용하지 않기 때문에 Persistance 디렉토리의 내용만 받습니다.
entity
@Entity(tableName = "users")
data class User(@PrimaryKey
@ColumnInfo(name = "userid")
val id: String = UUID.randomUUID().toString(),
@ColumnInfo(name = "username")
val userName: String)
공식적으로 data class가 서포트되고 있는 것은 기쁘네요.
다오
@Dao
interface UserDao {
/**
* Get a user by id.
* @return the user from the table with a specific id.
*/
@Query("SELECT * FROM Users WHERE userid = :id")
fun getUserById(id: String): Flowable<User>
/**
* Insert a user in the database. If the user already exists, replace it.
* @param user the user to be inserted.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: User)
/**
* Delete all users.
*/
@Query("DELETE FROM Users")
fun deleteAllUsers()
}
Flowable은 RxJava 클래스이므로 나중에 제거합니다. 이것에 한정되지 않고 List등의 컨테이너도 사용할 수 있습니다.
database
@Database(entities = arrayOf(User::class), version = 1)
abstract class UsersDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile private var INSTANCE: UsersDatabase? = null
fun getInstance(context: Context): UsersDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
UsersDatabase::class.java, "Sample.db")
.build()
}
}
컴패니언 오브젝트를 사용하고 있습니다만 Kotlin이므로 Singleton의 오브젝트를 따로 만드는 편이 그것같은 생각이 듭니다.
모듈 만들기
DDD이므로 Database는 모듈에 격리됩니다.
기본 프로젝트는 이전에 소개한 이 입니다.
build.gradle는 이런 느낌.
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
// Room用に追加
implementation "android.arch.persistence.room:runtime:1.0.0"
kapt "android.arch.persistence.room:compiler:1.0.0"
// DDDなのでモデルのモジュールに依存する
compile project(path: ':fehsbattlemodel')
}
entity를 Realm용으로 만든 클래스에서 복사합니다.
@RealmClass
open class RealmArmedHero(
@PrimaryKey
var nickname: String = "",
var baseName: String = "",
var weapon: String = "NONE",
...
) : RealmObject() {
fun toModelObject(): ArmedHero {}
}
@Entity(tableName = "heroes")
data class RoomArmedHero(
@PrimaryKey
var nickname: String = "",
var baseName: String = "",
var weapon: String = "NONE",
...
) {
fun toModelObject(): ArmedHero {}
}
Realm은 Entity가 되는 클래스를 상속해 기능을 자동 생성하는 편리상, @RealmClass 과 open, RealmObject 의 상속이 필요합니다.
한편, Room측은 Entity의 클래스는 그대로 사용합니다. @Entity (tableName = "") 어노테이션을 붙이기만 하면 됩니다. 굳이 컬럼명은 지정하지 않고 최대한 손을 뽑아 봅니다.
dao는 Realm 측에도 대응하는 것이 있지만 이름을 잘 모르겠습니다. 샘플은 Content라는 이름이었습니다만….
object RealmArmedHeroContent : RealmContent<ArmedHero>() {
/** realmのkotlin用ハンドラ */
private var realm: Realm by Delegates.notNull()
/** 初期化ブロック。テーブル変更時などはここでマイグレーションすることになる */
init {
realm = Realm.getDefaultInstance()
realm.executeTransaction {
// realm.deleteAll()
}
}
override fun delete(item: ArmedHero): Int {
val results = realm.where(RealmArmedHero::class.java).equalTo("nickname", item.name).findAll()
realm.executeTransaction {
results.deleteAllFromRealm()
}
return results.size
}
override fun deleteById(id: String): Int {
val results = realm.where(RealmArmedHero::class.java).equalTo("nickname", id).findAll()
realm.executeTransaction {
results.deleteAllFromRealm()
}
return results.size
}
override fun createOrUpdate(item: ArmedHero): ArmedHero {
item.apply {
realm.executeTransaction {
realm.copyToRealmOrUpdate(RealmArmedHero(name, baseHero.name, weapon.value, refinedWeapon.value, assist.value, special.value, aSkill.value, bSkill.value, cSkill.value, seal.value, rarity, levelBoost, boon.name, bane.name
, defensiveTerrain, atkBuff, spdBuff, defBuff, resBuff, atkSpur, spdSpur, defSpur, resSpur))
}
}
return item
}
override fun allItems(): List<ArmedHero> {
return heroDao.allHeroes().map { e -> e.toModelObject() }
}
override fun getById(id: String): ArmedHero? = heroDao.getHeroById(id)?.toModelObject()
}
※ 클래스를 직접 지정하는 조금 오래된 코드입니다.
@Dao
interface HeroDao {
@Query("SELECT * FROM heroes WHERE nickname = :id")
fun getHeroById(id: String): RoomArmedHero
@Query("SELECT * FROM heroes")
fun allHeroes(): List<RoomArmedHero>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertHero(hero: RoomArmedHero)
@Query("DELETE FROM heroes")
fun deleteAllHeroes()
@Query("DELETE FROM heroes WHERE nickname = :id")
fun deleteHero(id: String)
}
@Query 어노테이션 중에 SQL을 작성합니다. @Insert (onConflict = OnConflictStrategy.REPLACE)에서 create/update가 될 것 같습니다. (시도 잊어)
Room 측의 Dao는 실제로 리포지토리를 상속하고 Dao를 호출하는 객체가 필요합니다.
object RoomArmedHeroContent : ModelObjectRepository<ArmedHero> {
var appContext: Context? = null
val heroDao get() = UsersDatabase.getInstance(appContext!!).heroDao()
override fun delete(item: ArmedHero): Int {
heroDao.deleteHero(item.name)
return 1
}
override fun deleteById(id: String): Int {
heroDao.deleteHero(id)
return 1
}
override fun createOrUpdate(item: ArmedHero): ArmedHero {
item.apply {
heroDao.insertHero(RoomArmedHero(name, baseHero.name, weapon.value, refinedWeapon.value, assist.value, special.value, aSkill.value, bSkill.value, cSkill.value, seal.value, rarity, levelBoost, boon.name, bane.name
, defensiveTerrain, atkBuff, spdBuff, defBuff, resBuff, atkSpur, spdSpur, defSpur, resSpur))
}
return item
}
override fun allItems(): List<ArmedHero> {
return heroDao.allHeroes().map { e -> e.toModelObject() }
}
override fun getById(id: String): ArmedHero? = heroDao.getHeroById(id)?.toModelObject()
}
하는 일은 거의 같습니다. SQL 상당의 것을 리포지토리에 쓸지 Dao에 쓸지 어떨지 정도입니다.
Entity가 늘어난 DB는 이런 느낌으로.
@Database(entities = arrayOf(User::class, RoomArmedHero::class), version = 1)
abstract class UsersDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun heroDao(): HeroDao
companion object {
@Volatile private var INSTANCE: UsersDatabase? = null
fun getInstance(context: Context): UsersDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
UsersDatabase::class.java, "Sample.db").allowMainThreadQueries()
.build()
}
}
@Database (entities = arrayOf(User::class, RoomArmedHero::class), version = 1)에 대상 Entity를 추가하여 Dao도 늘립니다.
allowMainThreadQueries()는 다른 스레드로 나누지 않고 액세스하기 위한 기술입니다. 번거롭기 때문에 추가했습니다만 없어서 끝낼 수 없는 편이 좋을 것입니다.
테스트
@RunWith(AndroidJUnit4::class)
class RoomInstrumentedTest {
@Test
@Throws(Exception::class)
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
appContext.deleteDatabase("Sample.db")
RoomArmedHeroContent.appContext = appContext
val modelHero = ArmedHero(StandardBaseHero.get("エフラム")!!,"new エフラム")
RoomArmedHeroContent.createOrUpdate(modelHero)
val insertedArmedHero = RoomArmedHeroContent.getById("new エフラム")
assertEquals("new エフラム",insertedArmedHero!!.name)
}
}
Model에 선언한 인터페이스를 경유해 보통에 액세스 할 수 있었습니다. 실기에서도 똑같이 바꾸어 움직일 수 있습니다. 동시에 사용할 수도 있었지만 반드시 의미는 없을 것입니다.
감상
· 양쪽 안드로이드에 의존하고 있기 때문에 이번에는 의미는 없지만, 서버 사이드에 이식할 때에 어노테이션에 SQL을 쓰는 라이브러리, 예를 들어 myBatis와 공유한다면 꽤 의미가 있을지도?
・Realm 쪽이 여러가지 편이지만 SQL의 자동 생성 등이 얽혀 오면 Room도 나쁘지 않나?
・Realm은 컬럼이 늘어나는 만큼 마이그레이션 빼도 움직이기도 하지만 Room은 엄격하다
Reference
이 문제에 관하여(ORM 래퍼 라이브러리 Room을 Realm으로 바꾸기 (Kotlin)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/turanukimaru/items/67bec6d02ff16c0de13a
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
@Entity(tableName = "users")
data class User(@PrimaryKey
@ColumnInfo(name = "userid")
val id: String = UUID.randomUUID().toString(),
@ColumnInfo(name = "username")
val userName: String)
@Dao
interface UserDao {
/**
* Get a user by id.
* @return the user from the table with a specific id.
*/
@Query("SELECT * FROM Users WHERE userid = :id")
fun getUserById(id: String): Flowable<User>
/**
* Insert a user in the database. If the user already exists, replace it.
* @param user the user to be inserted.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUser(user: User)
/**
* Delete all users.
*/
@Query("DELETE FROM Users")
fun deleteAllUsers()
}
@Database(entities = arrayOf(User::class), version = 1)
abstract class UsersDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile private var INSTANCE: UsersDatabase? = null
fun getInstance(context: Context): UsersDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
UsersDatabase::class.java, "Sample.db")
.build()
}
}
DDD이므로 Database는 모듈에 격리됩니다.
기본 프로젝트는 이전에 소개한 이 입니다.
build.gradle는 이런 느낌.
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
// Room用に追加
implementation "android.arch.persistence.room:runtime:1.0.0"
kapt "android.arch.persistence.room:compiler:1.0.0"
// DDDなのでモデルのモジュールに依存する
compile project(path: ':fehsbattlemodel')
}
entity를 Realm용으로 만든 클래스에서 복사합니다.
@RealmClass
open class RealmArmedHero(
@PrimaryKey
var nickname: String = "",
var baseName: String = "",
var weapon: String = "NONE",
...
) : RealmObject() {
fun toModelObject(): ArmedHero {}
}
@Entity(tableName = "heroes")
data class RoomArmedHero(
@PrimaryKey
var nickname: String = "",
var baseName: String = "",
var weapon: String = "NONE",
...
) {
fun toModelObject(): ArmedHero {}
}
Realm은 Entity가 되는 클래스를 상속해 기능을 자동 생성하는 편리상, @RealmClass 과 open, RealmObject 의 상속이 필요합니다.
한편, Room측은 Entity의 클래스는 그대로 사용합니다. @Entity (tableName = "") 어노테이션을 붙이기만 하면 됩니다. 굳이 컬럼명은 지정하지 않고 최대한 손을 뽑아 봅니다.
dao는 Realm 측에도 대응하는 것이 있지만 이름을 잘 모르겠습니다. 샘플은 Content라는 이름이었습니다만….
object RealmArmedHeroContent : RealmContent<ArmedHero>() {
/** realmのkotlin用ハンドラ */
private var realm: Realm by Delegates.notNull()
/** 初期化ブロック。テーブル変更時などはここでマイグレーションすることになる */
init {
realm = Realm.getDefaultInstance()
realm.executeTransaction {
// realm.deleteAll()
}
}
override fun delete(item: ArmedHero): Int {
val results = realm.where(RealmArmedHero::class.java).equalTo("nickname", item.name).findAll()
realm.executeTransaction {
results.deleteAllFromRealm()
}
return results.size
}
override fun deleteById(id: String): Int {
val results = realm.where(RealmArmedHero::class.java).equalTo("nickname", id).findAll()
realm.executeTransaction {
results.deleteAllFromRealm()
}
return results.size
}
override fun createOrUpdate(item: ArmedHero): ArmedHero {
item.apply {
realm.executeTransaction {
realm.copyToRealmOrUpdate(RealmArmedHero(name, baseHero.name, weapon.value, refinedWeapon.value, assist.value, special.value, aSkill.value, bSkill.value, cSkill.value, seal.value, rarity, levelBoost, boon.name, bane.name
, defensiveTerrain, atkBuff, spdBuff, defBuff, resBuff, atkSpur, spdSpur, defSpur, resSpur))
}
}
return item
}
override fun allItems(): List<ArmedHero> {
return heroDao.allHeroes().map { e -> e.toModelObject() }
}
override fun getById(id: String): ArmedHero? = heroDao.getHeroById(id)?.toModelObject()
}
※ 클래스를 직접 지정하는 조금 오래된 코드입니다.
@Dao
interface HeroDao {
@Query("SELECT * FROM heroes WHERE nickname = :id")
fun getHeroById(id: String): RoomArmedHero
@Query("SELECT * FROM heroes")
fun allHeroes(): List<RoomArmedHero>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertHero(hero: RoomArmedHero)
@Query("DELETE FROM heroes")
fun deleteAllHeroes()
@Query("DELETE FROM heroes WHERE nickname = :id")
fun deleteHero(id: String)
}
@Query 어노테이션 중에 SQL을 작성합니다. @Insert (onConflict = OnConflictStrategy.REPLACE)에서 create/update가 될 것 같습니다. (시도 잊어)
Room 측의 Dao는 실제로 리포지토리를 상속하고 Dao를 호출하는 객체가 필요합니다.
object RoomArmedHeroContent : ModelObjectRepository<ArmedHero> {
var appContext: Context? = null
val heroDao get() = UsersDatabase.getInstance(appContext!!).heroDao()
override fun delete(item: ArmedHero): Int {
heroDao.deleteHero(item.name)
return 1
}
override fun deleteById(id: String): Int {
heroDao.deleteHero(id)
return 1
}
override fun createOrUpdate(item: ArmedHero): ArmedHero {
item.apply {
heroDao.insertHero(RoomArmedHero(name, baseHero.name, weapon.value, refinedWeapon.value, assist.value, special.value, aSkill.value, bSkill.value, cSkill.value, seal.value, rarity, levelBoost, boon.name, bane.name
, defensiveTerrain, atkBuff, spdBuff, defBuff, resBuff, atkSpur, spdSpur, defSpur, resSpur))
}
return item
}
override fun allItems(): List<ArmedHero> {
return heroDao.allHeroes().map { e -> e.toModelObject() }
}
override fun getById(id: String): ArmedHero? = heroDao.getHeroById(id)?.toModelObject()
}
하는 일은 거의 같습니다. SQL 상당의 것을 리포지토리에 쓸지 Dao에 쓸지 어떨지 정도입니다.
Entity가 늘어난 DB는 이런 느낌으로.
@Database(entities = arrayOf(User::class, RoomArmedHero::class), version = 1)
abstract class UsersDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun heroDao(): HeroDao
companion object {
@Volatile private var INSTANCE: UsersDatabase? = null
fun getInstance(context: Context): UsersDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
UsersDatabase::class.java, "Sample.db").allowMainThreadQueries()
.build()
}
}
@Database (entities = arrayOf(User::class, RoomArmedHero::class), version = 1)에 대상 Entity를 추가하여 Dao도 늘립니다.
allowMainThreadQueries()는 다른 스레드로 나누지 않고 액세스하기 위한 기술입니다. 번거롭기 때문에 추가했습니다만 없어서 끝낼 수 없는 편이 좋을 것입니다.
테스트
@RunWith(AndroidJUnit4::class)
class RoomInstrumentedTest {
@Test
@Throws(Exception::class)
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getTargetContext()
appContext.deleteDatabase("Sample.db")
RoomArmedHeroContent.appContext = appContext
val modelHero = ArmedHero(StandardBaseHero.get("エフラム")!!,"new エフラム")
RoomArmedHeroContent.createOrUpdate(modelHero)
val insertedArmedHero = RoomArmedHeroContent.getById("new エフラム")
assertEquals("new エフラム",insertedArmedHero!!.name)
}
}
Model에 선언한 인터페이스를 경유해 보통에 액세스 할 수 있었습니다. 실기에서도 똑같이 바꾸어 움직일 수 있습니다. 동시에 사용할 수도 있었지만 반드시 의미는 없을 것입니다.
감상
· 양쪽 안드로이드에 의존하고 있기 때문에 이번에는 의미는 없지만, 서버 사이드에 이식할 때에 어노테이션에 SQL을 쓰는 라이브러리, 예를 들어 myBatis와 공유한다면 꽤 의미가 있을지도?
・Realm 쪽이 여러가지 편이지만 SQL의 자동 생성 등이 얽혀 오면 Room도 나쁘지 않나?
・Realm은 컬럼이 늘어나는 만큼 마이그레이션 빼도 움직이기도 하지만 Room은 엄격하다
Reference
이 문제에 관하여(ORM 래퍼 라이브러리 Room을 Realm으로 바꾸기 (Kotlin)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/turanukimaru/items/67bec6d02ff16c0de13a텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)