Jetpack之Room的使用

1、Room介绍

        Android采用Sqlite作为数据库存储。Sqlite代码写起来繁琐且容易出错,所以开源社区里逐渐出现了各种ORM(Object Relational Mapping)库。这些开源ORM库都是为了方便Sqlite的使用,包括数据库的创建,升级,增删改查等。常见的ORM有ORMLite,GreenDAO等。Google也意识到了推出自家ORM的必要性,于是有了Room。
        Room持久性库在SQLite的基础上提供了一个抽象层,让用户能够在充分利用SQLite的强大功能的同时,获享更强健的数据库访问机制。

2、Room前言

        我们操作数据库首先我们要建立我们的:1、库。2、表。3、访问数据库对象Dao。
        而Room也是依照这这种结构帮助我们去使用Sqlite。
        下面我们认识一下Room这几个属性(先有个大概印象,后面还会结合代码示范):

关于表

1、@Entity:他标识在我们的Model类上。而被表示的Model类就成为了我们Room的一张表。也可以说Entity类是Sqlite表结构在Java类的映射。
2、@PrimaryKey:表示在我们Model的字段上,指定该字段为我们数据表的主键。
3、@ColumnInfo:标签可用于设置该字段存储在数据库表中的名字并指定字段的类型。
4、@Ignore:标签用来告诉系统忽略该字段或者方法。

关于库

1、@Database(entities = [TakeCash::class, version = 9):Database用于告知此类是我们的数据库对象,entities属性用于指定该数据库有哪些表,若需建立多张表,以逗号相隔开。version属性用于指定数据库版本号,后续数据库的升级正是依据版本号来判断的。
2、@TypeConverters:你可以理解是为了解决数据库只识别基础类型,比如你有个一个List那么就需要进行转换。而TypeConverters就是解决这个问题的。

关于Dao

1、@Dao:一个Entity代表着一张表,而每张表都需要一个Dao对象,进而对这张表进行各种增删改查操作。
2、@Insert、@Query、@Delete、@Update。增删改查标签。

3、Room的使用

添加依赖

(MAC M1要使用2.4.0以上哦,不然会不适配)

dependencies {
  def room_version = "2.4.0"
  implementation "androidx.room:room-runtime:$room_version"
  kapt "androidx.room:room-compiler:$room_version" 
  // Room的Kotlin扩展和对协程的支持
  implementation "androidx.room:room-ktx:$room_version"
}

指定Room的schemas路径

defaultConfig {
        ... //指定Room的schemas路径
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                                     "$projectDir/schemas".toString()]
            }
        }

    }

        等我们创建好表和数据库等信息的时候,编译一下,会在app/schemas目录下生成这样的Json文件。
在这里插入图片描述

创建实体类对于数据库的一张表

@Entity(tableName = "student")
public class Student {
    @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;

    /**
     * Room会使用这个构造器来存储数据,也就是当你从表中得到Student对象时候,Room会使用这个构造器
     * */
    public Student(int id, String name, String age)
    {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    /**
     * 由于Room只能识别和使用一个构造器,如果希望定义多个构造器,你可以使用Ignore标签,让Room忽略这个构造器
     * 同样,@Ignore标签还可用于字段,使用@Ignore标签标记过的字段,Room不会持久化该字段的数据
     * */
    @Ignore
    public Student(String name, String age)
    {
        this.name = name;
        this.age = age;
    }
}

创建Dao接口操作类

        DAO必须是接口或者抽象类,Room使用注解帮我们生成访问数据库的代码。接下来我们创建一个DAO类,具有简单的增删改查的功能。

@Dao
public interface StudentDao {
    @Insert
    void insertStudent(Student student);

    @Delete
    void deleteStudent(Student student);

    @Update
    void updateStudent(Student student);

    @Query("SELECT * FROM student")
    List<Student> getStudentList();

    @Query("SELECT * FROM student WHERE id = :id")
    Student getStudentById(int id);
}

创建数据库

        Room数据库必须是一个继承自RoomDatabase的抽象类。通常情况下应用内应该只有一个Room数据库实例。

@Database(entities = {Student.class, Teacher.class}, version = 1)
//数据库的表,和数据库版本
@Database(entities = {Student.class, Teacher.class}, version = 1)
//输入Student需要进行转换成String
@TypeConverters(mConverters.class) //类型转换(在后面)
public abstract class MyDatabase extends RoomDatabase {
    private static final String DATABASE_NAME = "my_db";

    private static MyDatabase databaseInstance;

    public static synchronized MyDatabase getInstance(Context context)
    {
        if(databaseInstance == null)
        {
            databaseInstance = Room
                    .databaseBuilder(context.getApplicationContext(), MyDatabase.class, DATABASE_NAME)
                    .build();
        }
        return databaseInstance;
    }

    public abstract StudentDao studentDao();
    public abstract TeacherDao teacherDao();
}

我们还可以为数据库创建添加相关监听等等

public static synchronized MyDatabase getInstance(Context context)
{
    if(databaseInstance == null)
    {
        databaseInstance = Room
                .databaseBuilder(context.getApplicationContext(), MyDatabase.class, DATABASE_NAME)
                .addCallback(new Callback(){
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        Log.e("onCreate", "onCreate: 创建成功" + db.getVersion());
                    }

                    @Override
                    public void onOpen(@NonNull SupportSQLiteDatabase db) {
                        Log.e("onOpen", "onOpen: 打开数据库" + db.getVersion());
                    }

                    @Override
                    public void onDestructiveMigration(@NonNull SupportSQLiteDatabase db) {
                        Log.e("onDestructiveMigration", "onDestructiveMigration: 数据库迁移" + db.getVersion());
                    }
                })
                .build();
    }
    return databaseInstance;
}

至此,数据库和表的创建工作完成了。

类型转换

有人注意到了@TypeConverters(mConverters.class)。
        @TypeConverters(mConverters.class)使用这个是因为我们在插入数据库的数据为Student,所以需要进行转换成数据库认识的基本类型。这里我们将Student通过Json转换成了String类型

public class mConverters {
    @TypeConverter
    public Student StudentFromJson(String json){
        return JsonUtils.fromJson(json);  //转换成Json串返回String类型
    }

    @TypeConverter
    public String StudentToJson(Student student){
        return JsonUtils.toJson(student);  //转换成Json串返回String类型
    }
}

他会在我们生成的StudentDao_Impl中找到相应方法进行转换
编译后点击我们的StudentToJson方法就能找到相应转换到位置

final String _tmp_1 = __converters.StudentToJson(value.getStudent());

增删改查

        这些对数据库的操作方法都是我们之前在Dao文件中已经定义好的。需要注意的是,不能直接在UI线程中执行这些操作,需要放在工作线程中进行。
        例如,我们可以使用AsyncTask/Kotlin可以用协程来做来进行查询操作。

public class DataTask extends AsyncTask<Void, Void, Void> {
    Context mContext;
    DataTask(Context context){
        mContext = context;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        //由于我们采用单例模式来实例化数据库,所以我们可以这样得到数据库对象
        MyDatabase database = MyDatabase.getInstance(mContext);
        //插入数据
        database.studentDao().insertStudent(new Student("name", "20"));
        //删除数据
        Student student = new Student("name", "20");
        database.studentDao().deleteStudent(student);
        //查询所有学生
        database.studentDao().getStudentList();
        //查询某个学生
        database.studentDao().getStudentById(1);
        return null;
    }

    @Override
    protected void onPostExecute(Void unused) {
        super.onPostExecute(unused);
    }
}

        至此,我们已经学会了如何在Android项目中利用Room创建数据库,以及对数据库进行增删改查等基本操作。但对数据库的访问还需要在工作线程中进行。每次数据库发生变化,我们都需要开启一个工作线程,对数据库进行查询。
        那么,能否在数据库发生变化时,自动收到通知呢?答案是肯定的,通过LiveData就能实现这一点。后续我们会对LiveData进行讲解。

4、数据库的迁移升级

        数据库迁移在任何应用程序开发中都是一个非常重要的概念。当我们在您的应用程序中添加和更改功能时,我们必须更新数据库的架构。每当我们的任何表的架构发生变化时,如果我们不希望我们的用户丢失所有现有数据,我们就需要为现有应用程序编写迁移。
        例如,我们可以考虑一个名为的表users,其中包含用户的信息,它有 3 个列uid,分别是first_name和last_name。现在,如果我们为 增加一个新列age,我们需要编写一个迁移来改变当前表结构,添加一个名为 的新列age。

第一步:更新数据库版本

@Database(entities = {Student.class, Teacher.class}, version = 1)
//更改为  version从1->2
@Database(entities = {Student.class, Teacher.class}, version = 2)

第二步:创建Migration进行迁移操作

        每个Migration类指定一个startVersion和endVersion。在运行时,Room 运行每个 Migration 类的migrate()方法,使用正确的顺序将数据库迁移到更高版本。

Migration migration = new Migration(1, 2) {
    @Override
    public void migrate1_2(@NonNull SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE users ADD COLUMN age INTEGER");
    }
};

第三步:使用数据库构建器将它添加到数据库配置中

public static synchronized MyDatabase getInstance(Context context)
    {
        if(databaseInstance == null)
        {
            databaseInstance = Room
                    .databaseBuilder(context.getApplicationContext(), MyDatabase.class, DATABASE_NAME)
                    .addMigrations(databaseInstance.migration1_2)
                    .build();
        }
        return databaseInstance;
    }

如果后续还会继续升级那么就延续就可以了比如:

.addMigrations(databaseInstance.migration1_2, databaseInstance.migration2_3)

按照上面这样,数据库就会按部就班的先从1升级到2再从2升级到3。

5、配合Kotlin协程使用

        其实Room使用Kotlin配合协程使用会方便的很多。不能直接在UI线程(主线程)中执行这些增删改查的操作。所以需要工作线程去处理。恰巧Kotlin协程提供了便捷的操作。
Java中的AsyncTask:

//Java使用AsyncTask异步写入
public class DataTask extends AsyncTask<Void, Void, Void> {
    Context mContext;
    DataTask(Context context){
        mContext = context;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        //由于我们采用单例模式来实例化数据库,所以我们可以这样得到数据库对象
        MyDatabase database = MyDatabase.getInstance(mContext);
        //插入数据
        database.studentDao().insertStudent(new Student("name", "20"));
        return null;
    }
}

协程:(协程有多种方式,介绍两个简单举例)

MyDatabase database = MyDatabase.getInstance(mContext).studentDao();
runBlocking {
    database.insertStudent(new Student("name", "20"))
}
//或者
GlobalScope.launch {
    database.insertStudent(new Student("name", "20"))
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值