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.)
                                
                                
                                우수한 개발자 콘텐츠 발견에 전념
                                (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.)