当前位置: 代码迷 >> 综合 >> Android Jetpack - Room
  详细解决方案

Android Jetpack - Room

热度:18   发布时间:2023-11-21 07:57:00.0

简介

谷歌提供的数据库封装库,不建议直接用Sqlite

简单使用

  1. 配置依赖
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
//java使用
annotationProcessor "androidx.room:room-compiler:$room_version"
//kotlin使用
//kapt "androidx.room:room-compiler:$room_version"
  1. 配置表
//默认表名是user,如果想要换可以通过tableName自定义
@Entity(tableName = "my_user")
public class User {
    //每个表必须有primaryKey,如果需要Room自动分配可通过@PrimaryKey(autoGenerate = true)设置,该值默认false//如果是复合主键,可通过@Entity(primaryKeys = {"firstName", "lastName"})设置@PrimaryKeypublic int uid;//Room默认使用字段名称作为数据库中的列名称即firstName,如果想要自定义可以通过@ColumnInfo(name = "first_name")指定,默认值用defaultValue指定@ColumnInfo(name = "first_name", defaultValue = "a")public String firstName;@ColumnInfo(name = "last_name")public String lastName;public String image;public int level;//Room会为@Entity标注的实体中每个字段都创建一个列,如果想要忽略某个字段使用@Ignore//如果是继承的可以使用@Entity(ignoredColumns = {"", ""})设置@Ignorepublic String password;//嵌套对象使用@Embedded注解,这是在Room中的表就会为Address中的street和city字段创建列,不会新建Address表,但是插入查询等操作不变,还是使用address,@Embeddedpublic Address address;
}public class Address{
    public String street;@ColumnInfo(name = "local_city", defaultValue = "火星")public String city;
}

这边简单起见字段都用public,如果是private需要提供getter和setter方法,它们的名称要遵循Room中JavaBeans规范

此外,Sqlite是关系型数据库,Room可以在表之间添加关联

  • 一对一关联
    场景举例:每个用户至多只有一个收藏列表,想要查询出哪些用户有收藏列表
    使用:
@Entity
public class User{
    @PrimaryKeypublic long uid;
}
@Entity
public class Collection{
    @PrimaryKeypublic long cid;//子实体需包含父实体的主键的引用public long uidInCol;
}
//为它们创建一对一关联类
public class UserAndCollection{
    //引入其他实体需要@Embedded注解@Embeddedpublic User user;//子实体需要使用@Relation注解,其中parentColumn为父实体主键列名称,entiryColumn为引用父主键的名称@Relation(parentColumn = "uid", entiryColumn = "uidInCol")public Collection collection;
}
//最后在dao中使用查询
@Transaction //原子操作
@Query("SELECT * FROM user")
public List<UserAndCollection> getUsersAndCollections();
  • .一对多关联
    场景举例:每个用户可以有多个播放列表
    使用:使用基本同一对一只是在创建关联的时候稍有不同,子实体需要用List
public class UserAndCollection{
    @Embeddedpublic User user;//这边使用集合类@Relation(parentColumn = "uid", entiryColumn = "uidInCol")public List<Collection> collections;
}
  • 多对多关联
    场景举例:每个播放列表对应多首歌曲,每个歌曲可以在多个播放列表中
    使用:
//创建表
@Entity
public class PlayList{
    //播放列表@PrimaryKeypublic long playListId;
}
@Entity
public class Song{
    //歌曲@PrimaryKeypublic long songId;
}
@Entity(primaryKeys = {
    "playListId", "songId"})
public class PlayListsAndSongs{
    //保存列表和歌曲对应关系public long playListId;public long songId;
}//添加多对多关联
public class PlayListWithSongs{
    @Embeddedpublic PlayList playList;//使用associateBy字段来标识PlayList和Song如何对应@Relation(parentColumn = "playListId", entiryColumn = "songId", associateBy = @Junction(PlayListsAndSongs.class))public List<Songs> songs;
}
//最后在dao中使用查询
@Transaction //原子操作
@Query("SELECT * FROM PlayList")
public List<PlayListsAndSongs> getPlayListsAndSongs();
  • 嵌套
    场景举例:每个用户有多个播放列表,每个列表有多首歌曲
    使用:表的声明都没变
//添加关联
public class UserWithPlayListsAndSongs{
    @Embeddedpublic User user;@Relation(entity = PlayList.class, parentColumn = "uid", entiryColumn = "uidInPlayList")public List<PlayListWithSongs> playListWithSongs;
}
//在dao中查询所有用户下播放列表,和每个播放列表下的歌曲
@Transaction
@Query("SELECT * FROM user")
public List<UserWithPlayListsAndSongs> getUserWithPlayListsAndSongs();
  1. 写关于表的操作
  • @Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)void insertAll(User... users);@Insertvoid insertUser(User user1, User user2);@Insertvoid insertUserAndCollection(User user, List<Collection> collections);//按主键匹配更新user,一般返回一个int值,标识更新了多少行@Updateint update(User... users);@Updateint update(List<User> users);//按主键匹配删除user,一般返回一个int值,标识删除了多少行@Deleteint delete(User... users);@Query("SELECT * FROM user")LiveData<List<User>> getAll();
}
  • @Inset
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    onConflict标识当发生冲突时处理办法 默认是ABORT,即终止,常用的还有IGNORE 忽略 和 REPLACE替换
    @Insert(entity = User.class)
    指定使用哪个实体
    比如:
@Entity
publicclass User{
    @PrimaryKey(autoGenerate = true)long uid;String name;String age;@ColumnInfo(defaultValue = "火星")String address;
}
public class SimpleUser{
    String name;String age;
}//这时在dao中使用时就需要指定实体
@Insert(entity = User.class)
public void insert(SimpleUser simpleUser);
  • @Update
    和Insert一样,可以通过onConflict来指定冲突发送时处理方式,entity来指定实体
  • @Delete
    和Insert一样,可以通过entity来指定实体
  • @Query
@Query("SELECT * FROM user")

里面的语句会在编译时进行验证,因此如果查询出现问题,如表名错误、字段名错误、查询规则没写完等,就会发生编译错误,而不是运行时失败.
如果这样写:

@Query("SELECT * FROM user")
List<SimpleUser> getAll();

Room 会验证查询的返回值,如果SimpleUser和User有部分字段匹配,则会发出警告。如果没有字段匹配,则会发出错误。

这样写法用的较多,因为页面不会同时需要user的所有数据,这时就可以为该页面创建一个新类作为查询返回值,Room会从user匹配相应的列到返回值中,但是注意SimpleUser中属性名需要和User表生成的列匹配。从而加快查询效率,节省资源。还有一种情况就是多表查询,查询出来的结果一般不是需要这些表所有列,这时也是需要新写一个类,比如:

@Query("SELECT user.name AS userName, pet.name AS petName " +"FROM user, pet " +"WHERE user.id = pet.user_id")
public LiveData<List<UserPet>> loadUserAndPetNames();

设置查询条件:参数在sql中按 :参数名 方式使用,Room 通过参数名称进行匹配。如果有不匹配的情况,也会在编译时报错。

@Query("SELECT * FROM user WHERE first_name LIKE :firstName")
List<User> getAll(String firstName);

这边实验了一下,如果想传对象,比如user,它是否会自动找user的first_name然后匹配,即

@Query("SELECT * FROM user WHERE first_name LIKE :user")
List<User> getAll(User user);

结果编译时报错

Query method parameters should either be a type that can be converted into a database column or a List / Array that contains such type. You can consider adding a Type Adapter for this.

想法错误,这错误大致意思是参数名必须是列名称,或者包含列名称的列表、数组,即

@Query("SELECT * FROM user WHERE first_name LIKE :firstNames")
List<User> getAll(List<String> firstNames);

返回值处理是数据对象外还可以是游标,但是谷歌不建议这样写,官方描述是Cursor无法保证行是否存在或者行包含哪些值。只有当您已具有需要光标且无法轻松重构的代码时,才使用此功能。

其实一般返回值不直接写数据对象,因为这样Room就是一个简单的数据库操作,体现不出架构组件的优势,而实际往往需要当数据变化时界面自动更新这时就要使用 LiveData 类型的返回值。当数据库更新时,Room 会生成更新 LiveData 所必需的所有代码

@Query("SELECT * FROM user")
LiveData<List<User>> getAll();
  1. 配置数据库
//使用@Database注解生成数据库,由它注释的类必须是一个抽象类并且继承自RoomDatabase
@Database(entities = {
    User.class, Message.class}, version = 1, exportSchema = false)
public abstract class CalendarDataBase extends RoomDatabase {
    public abstract UserDao userDao();public abstract MessageDao messageDao();public abstract MessageAndUserDao mAuDao();
}

数据库对其内部的Entity 和 Dao 没有数量限制,但是不可重复。exportSchema标识Room将数据库架构导出到文件夹中,还有一个参数是views 和 entities 标识保存的实体view

  1. 使用
//先封装一个类方便外部调用
public class CalendarDbManager {
    public static final String DB_NAME = "calendar_db";private Context mContext;public static CalendarDbManager getInstance(){
    return InnerCalendarDbManager.instance;}private static class InnerCalendarDbManager{
    private static CalendarDbManager instance = new CalendarDbManager();}public void initCalendarDb(Context context)  {
    if (context instanceof Application){
    mContext = context;}else{
    
// throw new Exception("");}}public void insert(){
    //需在子线程中new Thread(){
    @Overridepublic void run() {
    CalendarDataBase dataBase = Room.databaseBuilder(mContext, CalendarDataBase.class, DB_NAME).build();User user = new User();user.uid = 1;user.firstName = "a";user.lastName = "b";dataBase.userDao().insertAll(user);}}.start();}public LiveData<List<User>> queryAll(){
    CalendarDataBase dataBase = Room.databaseBuilder(mContext, CalendarDataBase.class, DB_NAME).build();return dataBase.userDao().getAll();}
}//在Activity或者Fragment中使用
CalendarDbManager.getInstance().initCalendarDb(getApplicationContext());
CalendarDbManager.getInstance().insert();
CalendarDbManager.getInstance().queryAll().observe(this, new Observer<List<User>>() {
    @Overridepublic void onChanged(List<User> users) {
    mBinding.setUser(users.get(0));}
});
  相关解决方案