@Target(allowedTargets=[AnnotationTarget.FIELD, AnnotationTarget.FUNCTION])
@Retention(value = AnnotationRetention.BINARY)
public annotation Embedded
public Embedded(@NonNull String prefix)
@ColumnInfo 로 이름이 지정되어 있더라도 접두어가 우선 적용@PrimaryKey로 표시되어 있더라도, 상위 엔티티의 기본 키로 간주되지 않음android.database.Cursor에서 null이라면, Room은 그 필드를 null로 설정TypeConverter가 null 컬럼을 non-null 값으로 변환하도록 정의되어도, 하위 필드의 모든 컬럼이 null이면 TypeConverter는 호출X. 해당 @Embedded 필드는 생성되지 않음.@Embedded 필드에 androidx.annotation.NonNull 애너테이션 추가data class Coordinates (
val latitude: Double,
val longitude: Double
)
data class Address (
val street: String,
@Embedded
val coordinates: Coordinates
)
latitude와 longitude를 Address 클래스의 필드처럼 취급street, latitude, longitude 컬럼을 반환하면, Room은 Address 객체 구성@Entity로 선언되어 있으면, 데이터베이스 테이블은 street, latitude, longitude 세 컬럼을 보유@Target(allowedTargets=[AnnotationTarget.FIELD, AnnotationTarget.FUNCTION])
@Retention(value = AnnotationRetention.BINARY)
public annotation Relation
public Relation(
@NonNull KClass<@NonNull ?> entity,
@NonNull String parentColumn,
@NonNull String entityColumn,
@NonNull Junction associateBy,
@NonNull String[] projection
)
@Relation이 붙은 필드의 타입이 java.util.List나 java.util.Set 이어야 함.@Relation의 entity 속성으로 명시 가능@Entity
data class Song(
@PrimaryKey
val songId: Int,
val albumId: Int,
val name: String
// 기타 필드
)
data class AlbumNameAndAllSongs(
val id: Int,
val name: String,
@Relation(parentColumn = "id", entityColumn = "albumId")
val songs: List<Song>
)
@Dao
interface MusicDao {
@Query("SELECT id, name FROM Album")
fun loadAlbumAndSongs(): List<AlbumNameAndAllSongs>
}
SongNameAndId는 POJO이지만, 그 안의 모든 필드는 @Relation 애너테이션의 entity로 지정된 Song 엔티티에서 가져옴SongNameAndId 안에 다른 관계가 정의되어 있더라도, Room은 그것들도 자동으로 함께 가져옴@Entity
data class Song(
@PrimaryKey
val songId: Int,
val albumId: Int,
val name: String
// 기타 필드
)
data class Album(
val id: Int
// 기타 필드
)
data class SongNameAndId(
val songId: Int,
val name: String
)
data class AlbumAllSongs(
@Embedded
val album: Album,
@Relation(
parentColumn = "id",
entityColumn = "albumId",
entity = Song::class
)
val songs: List<SongNameAndId>
)
@Dao
interface MusicDao {
@Query("SELECT * FROM Album")
fun loadAlbumAndSongs(): List<AlbumAllSongs>
}
projection 속성을 통해 자식 엔티티에서 어떤 컬럼만 조회할지 지정 가능data class AlbumAndAllSongs(
@Embedded
val album: Album,
@Relation(
parentColumn = "id",
entityColumn = "albumId",
entity = Song::class,
projection = ["name"]
)
val songNames: List<String>
)
associateBy 속성을 통해 해당 테이블 지정 가능@Relation POJO 클래스에서만 사용 가능1)개념
@Target(allowedTargets = [AnnotationTarget.FUNCTION])
@Retention(value = AnnotationRetention.BINARY)
public annotation Transaction
super) 메서드를 실행. 모든 매개변수와 반환 타입은 그대로 유지@Dao
abstract class SongDao {
@Insert
abstract fun insert(song: Song)
@Delete
abstract fun delete(song: Song)
@Transaction
fun insertAndDeleteInTransaction(newSong: Song, oldSong: Song) {
// 이 메서드 안의 모든 동작은 하나의 트랜잭션 안에서 실행
insert(newSong)
delete(oldSong)
}
}
SELECT 문을 가진 Query 메서드에 사용될 때, 생성된 Query 코드는 트랜잭션 안에서 실행. 이를 사용하는 주요 이유는 두 가지.android.database.CursorWindow에 맞지 않을 때, CursorWindow 교체 사이에 데이터베이스가 변경되어 결과가 손상@Relation 필드들은 별도의 쿼리로 조회. 쿼리 간의 일관된 결과를 보장하려면 하나의 트랜잭션으로 실행해야 함.data class AlbumWithSongs : Album (
@Relation(parentColumn = "albumId", entityColumn = "songId")
val songs: List<Song>
)
@Dao
public interface AlbumDao {
@Transaction
@Query("SELECT * FROM album")
fun loadAll(): List<AlbumWithSongs>
}
androidx.lifecycle.LiveData 또는 RxJava의 Flowable를 반환하여 쿼리가 비동기적일 경우, 트랜잭션은 메서드가 호출될 때 처리X. 쿼리가 실제로 실행될 때 처리.@Insert, @Update, @Delete 메서드에는 영향X. 이러한 메서드는 항상 트랜잭션 안에서 실행되기 때문.@Query 메서드가 INSERT, UPDATE, DELETE 문을 실행하는 경우에도 자동으로 트랜잭션이 감싸지므로 효과XUserWithPlaylistsAndSongs 클래스는 세 가지의 모든 항목 클래스(User, Playlist, Song) 간의 관계를 간접적으로 모델링
@Entity
data class User(
@PrimaryKey val userId: Long,
val name: String,
val age: Int
)
@Entity
data class Playlist(
@PrimaryKey val playlistId: Long,
val userCreatorId: Long,
val playlistName: String
)
@Entity
data class Song(
@PrimaryKey val songId: Long,
val songName: String,
val artist: String
)
@Entity(primaryKeys = ["playlistId", "songId"])
data class PlaylistSongCrossRef(
val playlistId: Long,
val songId: Long
)
// Playlist 항목 클래스와 Song 항목 클래스 간의 다대다 관계
data class PlaylistWithSongs(
@Embedded val playlist: Playlist,
@Relation(
parentColumn = "playlistId",
entityColumn = "songId",
associateBy = Junction(PlaylistSongCrossRef::class)
)
val songs: List<Song>
)
//User 항목 클래스와 PlaylistWithSongs 관계 클래스 간의 일대다 관계를 모델링
//새 관계 내부에 기존 관계를 중첩
data class UserWithPlaylistsAndSongs(
@Embedded val user: User
@Relation(
entity = Playlist::class,
parentColumn = "userId",
entityColumn = "userCreatorId"
)
val playlists: List<PlaylistWithSongs>
)
// Room에서 여러 쿼리를 실행해야 함
// 전체 작업이 원자적으로 실행되도록 @Transaction 주석 추가
@Transaction
@Query("SELECT * FROM User")
fun getUsersWithPlaylistsAndSongs(): List<UserWithPlaylistsAndSongs>