Android Room 笔记

导入

app.build.gradle中的dependecies{}代码块内添加如下引用

def room_version = "2.2.3"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

    // optional - RxJava support for Room
    implementation "androidx.room:room-rxjava2:$room_version"

建库

创建继承自androidx.room.RoomDatabase的抽象类,并为该类添加注解,如下所示

@Database(entities = {User.class, Login.class}, version = 1)
public abstract class DatabaseName extends RoomDatabase {
    ...
}
@Database(entities = arrayOf(User::class, Login::class), version = 1)
abstract class DatabaseName: RoomDatabase() {
    ...
}

该例子建立了一个拥有User、Login两张表的版本为1的数据库

之后对数据库进行初始化(建议在Application中进行)

初始化示例如下

void initDB(Context ctx, String dbName){
    DatabaseName db = Room.databaseBuilder(ctx.getApplicationContext(), DatabaseName.class, dbName).build();
}
fun initDB(ctx: Context, dbName: String){
    val db = Room.databaseBuilder(ctx.applicationContext, DatabaseName::class.java, dbName).build()
}

获取到DatabaseName实例后,建议确保其为单例模式使用,因为每次获取的开销都不小

使用@Database的类应满足以下条件

  • 继承自RoomDatabase的抽象类
  • 在注释中添加与数据库关联的实体列表
  • 包含无参且返回通过@Dao注释的类的抽象方法(在建表部分详细解释)

建表

@Entity

通过由@Entity注解修饰的Java Bean类来构建,请确保各个属性对Room来说都是可见的

基本用法
@Entity
public class User {
    @PrimaryKey
    public long uid;
    
    @ColumnInfo(name = "first_name")
    public String firstName;
    
    @ColumnInfo(name = "last_name")
    public String lastName;
}
@Entity
data class User(
    @PrimaryKey val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

如上创建了一个User表,包含一个主键uid和两个字段。

自增主键

通过@PrimaryKey修饰的字段会被当成主键。若需要该主键自动生成,可以使用如下写法

@PrimaryKey(autoGenerate = true)
public long uid;
复合主键

每个实体必须至少有一个字段为主键。通过@Entity(primaryKeys = [])来进行配置,此时请不要使用@PrimaryKey修饰其它字段,并确保主键均为NonNull类型

@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
    @NonNull
    public String firstName;
    @NonNull
    public String lastName;
}
@Entity(primaryKeys = arrayOf("firstName", "lastName"))
    data class User(
        val firstName: String,
        val lastName: String
    )
定义字段名

默认下,数据库将用类名作为表名。若希望规定不同名称,可以使用@EntitytableName属性(数据库名称不区分大小写)

@Entity(tableName = "users")
public class User {
    ...
}
@Entity(tableName = "users")
data class User (
     ...
)

同样的,可以用@ColumnInfoname属性定义字段名

忽略字段

Room会为实体的每一个字段创建一个类。如果有些字段并非用来建表,可以使用@Ignore为其进行注释

若其存在继承关系,则建议使用@EntityignoredColumns进行注释

官方的示例如下

@Entity(ignoredColumns = "picture")
public class RemoteUser extends User {
    @PrimaryKey
    public int id;

    public boolean hasVpn;
}
open class User {
    var picture: Bitmap? = null
}

@Entity(ignoredColumns = arrayOf("picture"))
data class RemoteUser(
    @PrimaryKey val id: Int,
    val hasVpn: Boolean
) : User()
字段唯一

部分数据库中的字段或字段组内容必须唯一,可以用@Indexunique属性进行设置。当尝试插入重复字段时会抛出RuntimeException

@Entity(indices = {@Index(value = {"first_name", "last_name"},
            unique = true)})
    public class User {
        @PrimaryKey
        public int id;

        @ColumnInfo(name = "first_name")
        public String firstName;

        @ColumnInfo(name = "last_name")
        public String lastName;

        @Ignore
        Bitmap picture;
    }
@Entity(indices = arrayOf(Index(value = ["first_name", "last_name"],
            unique = true)))
    data class User(
        @PrimaryKey val id: Int,
        @ColumnInfo(name = "first_name") val firstName: String?,
        @ColumnInfo(name = "last_name") val lastName: String?,
        @Ignore var picture: Bitmap?
    )

@Dao

使用@Dao修饰的接口类来确定业务接口。该接口最后会通过生成Room.databaseBuilder时实现其前文提到的无参抽象方法来获取。

官方示例非常清晰

@Dao
    public interface UserDao {
        @Query("SELECT * FROM user")
        List<User> getAll();

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        List<User> loadAllByIds(int[] userIds);

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        User findByName(String first, String last);

        @Insert
        void insertAll(User... users);

        @Delete
        void delete(User user);
    }
@Dao
    interface UserDao {
        @Query("SELECT * FROM user")
        fun getAll(): List<User>

        @Query("SELECT * FROM user WHERE uid IN (:userIds)")
        fun loadAllByIds(userIds: IntArray): List<User>

        @Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
               "last_name LIKE :last LIMIT 1")
        fun findByName(first: String, last: String): User

        @Insert
        fun insertAll(vararg users: User)

        @Delete
        fun delete(user: User)
    }

记得在之前编写的DatabaseName.class中添加获取该类实例的抽象方法

@Database(entities = {User.class, Login.class}, version = 1)
public abstract class DatabaseName extends RoomDatabase {
    public abstract UserDao getUserDao();
}
@Database(entities = {User.class, Login.class}, version = 1)
public abstract class DatabaseName extends RoomDatabase {
    abstract fun userDao(): UserDao
}

在实际操作中,即可使用得到的数据库类实例来进行操作

void sampleFun(){
    DatabaseName db = Room.databaseBuilder(ctx.getApplicationContext(), DatabaseName.class, dbName).build();
    
    //查询所有用户
    List<User> userList = db.getUserDao().getAll();
}
fun sampleFun(){
    val db = Room.databaseBuilder(ctx.applicationContext, DatabaseName::class.java, dbName).build()
    
    val userList = db.userDao.all
}