Android Jetpack组件(五)Room

Android Jetpack组件系列文章:
Android Jetpack组件(一)LifeCycle
Android Jetpack组件(二)Navigation
Android Jetpack组件(三)ViewModel
Android Jetpack组件(四)LiveData
Android Jetpack组件(五)Room
Android JetPack组件(六)DataBinding
Android Jetpack组件(七)Paging
Android Jetpack组件(八)WorkManager
Android Jetpack组件(九)DataStore

首语

Android使用SQLite作为数据库存储数据,但是SQLite使用繁琐且容易出错,有许多开源的数据如GreenDAO、ORMLite等,这些都是为了方便SQLite的使用而出现的,Google也意识到了这个问题,在Jetpack组件中推出了Room,Room在SQLite上提供了一层封装,可以流畅的访问数据库。

优势

  1. 拥有SQLite的所有操作功能。
  2. 使用简单,通过注解的方式实现相关功能,编译时自动生成实现类impl。
  3. 与LiveData、LifeCycle及Paging天然支持。

依赖

  implementation "androidx.room:room-runtime:2.2.6"
  annotationProcessor "androidx.room:room-compiler:2.2.6"

相关概念

Room主要包含三个组件:

  • 数据库:包含数据库持有者,作为应用已保留的持久关系型数据的底层连接的主要接入点。
    使用 @Database注解的类应满足以下条件:
  • 是扩展 RoomDatabase的抽象类。
  • 在注释中添加与数据库关联的实体列表。
  • 包含具有0个参数且返回使用 @Dao注释的类的抽象方法。
  • Entity:表示数据库中的表。
  • DAO:包含用于访问数据库的方法。

应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。Room架构图如图所示。

Android Jetpack组件(五)Room

; 使用

创建数据库。

@SkipQueryVerification
@Database(entities = {Student.class}, version = 1, exportSchema = false)

public abstract class StudentDatabase extends RoomDatabase {

    private static volatile StudentDatabase database;

    private StudentDatabase() {
    }

    public static StudentDatabase getInstance() {
        if (database == null) {
            synchronized (StudentDatabase.class) {
                if (database == null) {

                    database = Room.databaseBuilder(AppGlobals.getApplication(), StudentDatabase.class, "room_cache")

                            .allowMainThreadQueries()

                            .fallbackToDestructiveMigration()

                            .fallbackToDestructiveMigrationFrom()

                            .addMigrations(StudentDatabase.sMigration, StudentDatabase.mMigration)
                            .build();
                }
            }
        }
        return database;
    }

    static Migration sMigration = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("alter table teacher rename to student");
            database.execSQL("alter table teacher add column teacher_age INTEGER NOT NULL default 0");
        }

    };

    static Migration mMigration = new Migration(2, 3) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {

        }
    };
}

注意:如果我们设置了 exportSchema(默认值是true),需要在app.gradle中配置存放位置。

defaultConfig {
        ...

        javaCompileOptions {
            annotationProcessorOptions {
                arguments=["room.schemaLocation":"$projectDir/schemas".toString()]
            }
        }
    }

关于Room的诸多注解,可参考Room的源码,在 room_common jar包下,注释非常详细。

创建Entity
@Fts4(languageId ="china")

@Entity(tableName = "student" ,foreignKeys = {@ForeignKey(entity = User.class,parentColumns = "id",childColumns = "key",onDelete = ForeignKey.CASCADE,onUpdate = ForeignKey.RESTRICT)}, ignoredColumns = "score",indices = {@Index("index"),@Index(value = {"name","age"},unique = true)})
public class Student extends Score{

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    public int id;

    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    public String name;

    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.TEXT)
    public String age;

    public Student(int id, String name, String age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Ignore
    public Student(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public ForeignTable foreignTable;

    @Relation(entity = User.class,parentColumn = "id",entityColumn ="key" ,projection = {"name","age"})
    public User mUSer;
}

 class ForeignTable{
    @PrimaryKey
    @NonNull
    public String foreign_key;

    public byte[] foreign_data;
}

默认情况下,Room将Entity类名作为表名,想单独设置,可通过 @Entity注解里的 tableName设置。
每个Entity至少有一个字段作为主键,如果想让数据库为字段自动分配ID,可以使用 autoGenerate,如果Entity想有符合主键,可以使用 @Entity注解里的 primaryKeys,设置复合主键。
Room通过 @Ignore设置忽略字段,如果Entity继承了父Entity的字段,可以通过 @Entity注解里的 ignoredColumns属性设置。
Room支持全文搜索,通过使用 @Fts3(仅在应用程序具有严格的磁盘空间要求或需要与较旧的SQLite版本兼容时使用)或 @Fts4添加到Entity来实现。Room版本须高于2.1.0。
需要注意的是:启用Fts的表必须使用Integer类型的主键,且列名为” rowid“。
如果表支持以多种语言显示内容,可以使用 languageId指定用于存储每一行语言信息的列。
如果应用不支持使用全文搜索,可以将数据库的某些列编入索引,加快查询速度,通过 @Entity注解添加 indices,列出要在索引或符合索引中包含的列名称。
有时候,数据库中的某些字段必须是唯一的,可以通过 @Index注解的 unique属性设为 true,强制实施此唯一属性。如上代码所示可防止 nameage同组值的两行。
在 Room 2.1.0 以上版本中,基于 Java 的不可变值类(使用 @AutoValue 进行注释)用作应用数据库中的Entity。此支持在Entity的两个实例被视为相等(如果这两个实例的列包含相同的值)时尤为有用。
将带有 @AutoValue 注释的类用作实体时,可以使用 @PrimaryKey@ColumnInfo@Embedded@Relation 为该类的抽象方法添加注释。但是,您必须在每次使用这些注解时添加 @CopyAnnotations 注解,以便 Room 可以正确解释这些方法的自动生成实现。

    @AutoValue
    @Entity
    public abstract class User {
        @CopyAnnotations
        @PrimaryKey
        public abstract long getId();

        public abstract String getFirstName();
        public abstract String getLastName();

        public static User create(long id, String firstName, String lastName) {
            return new AutoValue_User(id, firstName, lastName);
        }
    }
创建DAO

最后,我们通过DAO来访问数据。DAO可以是接口,也可以是抽象类,如果是抽象类,则该DAO可以选择有一个以RoomDatabase为唯一参数的构造函数。Room 会在编译时创建每个 DAO 实现。在DAO文件上方添加 @DAO注解。

@Dao
public interface CacheDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    long save(Cache cache);

    @Query("select *from cache where key=:key")
    Cache getCache(String key);

    @Delete
    int delete(Cache cache);

    @Update(onConflict = OnConflictStrategy.REPLACE)
    int update(Cache cache);

    @RawQuery
    Cache getAllCache(SupportSQLiteQuery sqLiteQuery);
}

我们创建好了数据库,定义好了Entity和DAO后,可以操作数据。需要注意,数据操作应在工作线程操作,除非指定在主线程可以查询,否则会发生崩溃。


 public abstract CacheDao getCache();

long rowID = StudentDatabase.getInstance().getCache().save(cache);

int lines = StudentDatabase.getInstance().getCache().delete(cache);

销毁与重建

如果需要对数据库中的字段类型进行修改,最好的方式就是销毁与重建。
主要包含以下几个步骤:

  1. 创建一张和修改的表同数据结构的临时表。
  2. 将数据从修改的表复制到临时表中。
  3. 删除要修改的表。
  4. 将临时表重命名为修改的表名。
static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            database.execSQL("CREATE TABLE temp_Student (" +
                    "id INTEGER PRIMARY KEY NOT NULL," +
                    "name TEXT," +
                    "age TEXT)");
            database.execSQL("INSERT INTO temp_Student (id, name, age)" +
                    "SELECT id, name, age FROM Student");
            database.execSQL("DROP TABLE Student");
            database.execSQL("ALTER TABLE temp_Student RENAME TO Student");
        }
    };

预填充数据库

有时候,需要在应用启动的时候就加载一组特定的数据,这就称为预填充数据库。

从应用资源预填充

如需从位于应用assets/目录中的任意位置的预封装数据库文件预填充Room数据库,请先从 RoomDatabase.Builder对象调用 createFromAsset(),然后再调用 build()

@Database(entities = {Cache.class}, version = 1)
public abstract class PreDatabase extends RoomDatabase {

    private static volatile PreDatabase database;

    private PreDatabase() {

    }

    public static PreDatabase getInstance() {
        if (database == null) {
            synchronized (PreDatabase.class) {
                if (database == null) {
                    Room.databaseBuilder(context, PreDatabase.class, "Sample.db")
                            .createFromAsset("database/myapp.db")
                            .build();
                }
            }
        }
        return database;
    }

}
从文件系统预填充

如果觉得在assets目录下占用应用体积,可以在应用启动时从服务端下载数据库文件到本地,从设备文件系统任意位置(应用的 assets/ 目录除外)的预封装数据库文件预填充Room数据库,请先从 RoomDatabase.Builder 对象调用 createFromFile() ,然后再调用 build()

@Database(entities = {Cache.class}, version = 1)
public abstract class PreDatabase extends RoomDatabase {

    private static volatile PreDatabase database;

    private PreDatabase() {

    }

    public static PreDatabase getInstance() {
        if (database == null) {
            synchronized (PreDatabase.class) {
                if (database == null) {
                    Room.databaseBuilder(appContext, PreDatabase.class, "Sample.db")
                            .createFromFile(new File("mypath"))
                            .build();

                }
            }
        }
        return database;
    }
}

Room与LiveData和ViewModel的结合

当Room数据库中的数据发生变化时 ,能够通过LiveData组件通知View层,实现数据的自动更新。
首先使用LiveData将返回的数据包装起来。

 @Query("select *from cache")
 LiveData<Cache> getCache();

创建ViewModel,实例化数据库。

public class CacheViewModel extends AndroidViewModel {

    private MyDatabase myDatabase;
    private LiveData<Cache> cacheLiveData;

    public CacheViewModel(@NonNull Application application) {
        super(application);

        myDatabase=MyDatabase.getInstance(application);
        cacheLiveData=myDatabase.getCacheDao().getCache();
    }

    public LiveData<Cache> getCacheLiveData(){
        return cacheLiveData;
    }
}

在Activity中实例化 CacheViewModel,监听LiveData的变化。当我们对数据库进行相关操作时, onChanged()会自动调用。

  LiveData<Cache> cacheLiveData = new ViewModelProvider(this).get(CacheViewModel.class).getCacheLiveData();
        cacheLiveData.observe(this, new Observer<Cache>() {
            @Override
            public void onChanged(Cache cache) {
                Log.e("yhj", "onChanged: "+cache.key);
            }
  });

我之前使用的网络框架是RxJava+Retrofit+SQLite组合使用,学习完Jetpack后,我使用LiveData+Retrofit+Room封装了网络请求缓存框架,将Jetpack组合使用能更好的理解相关组件。
Github地址:https://github.com/hujuny/EasyHttp

Original: https://blog.csdn.net/yang_study_first/article/details/115232804
Author: 八归少年
Title: Android Jetpack组件(五)Room

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/817383/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球