Room Relations Guide - One-to-Many, Many-to-Many, and Embedded in Android
Android Room database relationships can be complex, but with the right patterns, you can create well-structured data models. This guide covers the main relationship patterns you'll encounter.
@Embedded - One-to-One Relationships
Embed related data in a single entity:
@Entity(tableName = "addresses")
data class Address(
val street: String,
val city: String,
val zipCode: String
)
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
val name: String,
@Embedded val address: Address
)
// Query - address fields are flattened in users table
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserWithAddress(id: Int): User
}
@Relation - One-to-Many Relationships
Link parent entity with multiple child entities:
@Entity(tableName = "authors")
data class Author(
@PrimaryKey val id: Int,
val name: String
)
@Entity(tableName = "books")
data class Book(
@PrimaryKey val id: Int,
val title: String,
val authorId: Int,
@ForeignKey(entity = Author::class, parentColumns = ["id"], childColumns = ["authorId"])
)
data class AuthorWithBooks(
@Embedded val author: Author,
@Relation(parentColumn = "id", entityColumn = "authorId")
val books: List<Book>
)
@Dao
interface AuthorDao {
@Transaction
@Query("SELECT * FROM authors")
suspend fun getAuthorsWithBooks(): List<AuthorWithBooks>
}
Junction Tables - Many-to-Many Relationships
Connect two entities through a junction table:
@Entity(tableName = "students")
data class Student(
@PrimaryKey val id: Int,
val name: String
)
@Entity(tableName = "courses")
data class Course(
@PrimaryKey val id: Int,
val title: String
)
@Entity(
tableName = "student_course",
primaryKeys = ["studentId", "courseId"],
foreignKeys = [
ForeignKey(Student::class, ["id"], ["studentId"]),
ForeignKey(Course::class, ["id"], ["courseId"])
]
)
data class StudentCourse(
val studentId: Int,
val courseId: Int
)
data class StudentWithCourses(
@Embedded val student: Student,
@Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(StudentCourse::class, parentColumn = "studentId", entityColumn = "courseId")
)
val courses: List<Course>
)
@Dao
interface StudentDao {
@Transaction
@Query("SELECT * FROM students")
suspend fun getStudentsWithCourses(): List<StudentWithCourses>
}
Type Converters - Custom Data Types
Convert complex types to Room-compatible formats:
data class Timestamp(val millis: Long)
class Converters {
@TypeConverter
fun timestampToLong(timestamp: Timestamp?): Long? = timestamp?.millis
@TypeConverter
fun longToTimestamp(millis: Long?): Timestamp? =
millis?.let { Timestamp(it) }
@TypeConverter
fun listToJson(list: List<String>): String =
Json.encodeToString(list)
@TypeConverter
fun jsonToList(json: String): List<String> =
Json.decodeFromString(json)
}
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
@Transaction - Data Consistency
Ensure related operations complete atomically:
@Dao
interface TransactionDao {
@Transaction
suspend fun insertUserAndAddress(user: User, address: Address) {
insertUser(user)
insertAddress(address)
}
@Transaction
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserWithAllRelations(id: Int): UserData
}
Relationship Patterns Summary
| Pattern | Structure | Use Case |
|---|---|---|
| @Embedded | 1:1 in same table | Address, contact info |
| @Relation | 1:many across tables | Author → Books |
| Junction | Many:many with link table | Students ↔ Courses |
| @TypeConverter | Custom type mapping | Enums, complex objects |
| @Transaction | Atomic operations | Multi-entity inserts |
8 Android app templates available on Gumroad
Top comments (0)