jetpack之Room数据库

早些年没有GreenDao,没有Room框架,我们只能不断地重复造轮子,通过自己封装SqliteDataBase来满足自己的项目需求。而自从GreenDao的横空出世,我们对数据库的操作不再那么望洋兴叹,他不仅集成了常规的增删改查,还能多表级联查询,数据库升级等,我们新建一张数据库表只需要添加一个注解就能搞定,这一时间成为大多数程序员的宠儿,然而,自从google官方推出Room数据库以后,很多人开始渐渐的转向他,它不仅支持以上GreenDao的大部分功能,另外还能指定查询的返回数据类型为Observable,LiveData等类型,这使得配合Rxjava以及MVVM框架使用起来相当方便。这篇文章将带领大家熟悉Room数据库的常规操作

一、导入

app的build.gradle中添加如下配置:

	implementation 'androidx.room:room-runtime:2.2.5'
    annotationProcessor 'androidx.room:room-compiler:2.2.5'

二、入门

1、建表Entity
@Entity(tableName = "t_entity01")
public class Entity01 {

    @PrimaryKey(autoGenerate = true)
    private Long id;

    @ColumnInfo
    private Long productId;

    @ColumnInfo(name = "userName")
    private String user_name;

    @Ignore
    private String desc;

   
 }
  • @Entity:注解在类之上,代表是一张数据库表,tableName属性可以指定表的名称,默认为类名。
  • @PrimaryKey:表的主键,autoGenerate =true代表自增长。
  • @ColumnInfo:字段名称,name属性可以指定字段名称,默认为变量名。
  • @Ignore:该注解表示当前数据库忽略当前属性,不会创建该字段。
  • 默认不写@ColumnInfo注解,也会在数据库根据变量名新建一个字段。
2、数据库操作类Dao
@Dao
public interface  EntityDao1 {

    @Insert
    void insert(Entity01 entity01);

    @Query("Select * from t_entity01 where productId = :targetId")
     List<Entity01> search(Long targetId);

    @Query("Select *from t_entity01")
    List<Entity01> searchByAge();

}
  • 当前类必须是interface
  • @Dao 注解在类之上,提供给外部操作数据库的方法
  • @Insert 添加,可以添加单个对象,亦可以是List、Array等集合类型。
  • @Query ,可以根据sql语句进行一系列查询、更新、删除操作。
3、数据库RoomDatabase
@Database(entities = {
        Entity01.class
}, version = 1, exportSchema = false)

public abstract class EntityDaoDatabase extends RoomDatabase {

    public abstract EntityDao1 entityDao1();
 }
  • @Database: 注解在类之上,抽象类,对外提供每张表的操作类
  • entities:所有的数据库表代表的类
  • version:数据库版本,升级数据库需要修改版本好,一般和Migration配合使用
  • exportSchema :存储展示数据库的结构信息,如果不设置的话,需要再database类上配置exportSchema = false,要不然编译的时候会出现警告。
4、创建数据库db
public class RoomBuilder {
    private EntityDaoDatabase dataBase;
    private RoomBuilder() {
        //操作数据库应该在子线程中操作
        dataBase = Room.databaseBuilder(BaseApplication.getContext(),
                EntityDaoDatabase.class, "knowledge.db")
                //.addMigrations(new M_1_to_2(),new M_2_to_1())//数据库版本升级
                //.fallbackToDestructiveMigration()   //这个方法也可以迁移数据库,但会将数据摧毁导致数据的丢失
                .build();
    }

    public EntityDaoDatabase getDataBase() {
        return dataBase;
    }

}

通过Room.databaseBuilder方式构建RoomDatabase 。这样在其他地方就可以通过RoomDatabase.getDao() 的形式进行数据库操作了。

5、简单示例
Entity01 entity01 = new Entity01();
entity01.setProductId(1111L);
entity01.setUser_name(System.currentTimeMillis()+"");

final EntityDao1 entityDao1=RoomBuilder.getInstance().getDataBase().entityDao1();
//插入到数据库
entityDao1.insert(entity01);

三、数据库升级

往往在实际的项目开发阶段会有这样的需求:后台服务器返回的实体字段信息一天一个样。如果我们想之前的数据信息不被覆盖,但是要把新的字段信息添加到数据库表中,就需要借助Migration了。

1、新增字段

新增字段还是比较容易的。比如我在上面的Entity01中新增字段:

 @ColumnInfo
 private int age;

(1)第一步:
新建升级类M_1_to_2继承Migration,重写migrate(xxx),以及构造方法,在migrate方法中进行sql语句操作

public class M_1_to_2 extends Migration {
    /**
     * 这个版本2要和对应的数据库版本对应
     */
    public M_1_to_2() {
        super(1, 2);
    }

    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {
        // 为旧表添加新的字段
        database.execSQL("ALTER TABLE t_entity01 "
                + " ADD COLUMN  age INTEGER NOT NULL DEFAULT 10");
    }
}

注意,构造方法中的版本是从1到2。

(2)第二步:
修改数据库版本,在RoomDatabase的实现类EntityDaoDatabase 中修改version版本为2

@Database(entities = {
        Entity01.class
}, version = 2, exportSchema = false)

public abstract class EntityDaoDatabase extends RoomDatabase {
   ...
 }

(3)第三步:
添加数据库升级配置,在Room.databaseBuilder中进行操作:

//操作数据库应该在子线程中操作
dataBase = Room.databaseBuilder(BaseApplication.getContext(),
          EntityDaoDatabase.class, "knowledge.db")
          .addMigrations(new M_1_to_2())//数据库版本升级
          //.fallbackToDestructiveMigration()   //这个方法也可以迁移数据库,但会将数据摧毁导致数据的丢失
          .build();

ok,以上就完成了表中新增字段的配置。

2、减少字段

这个相对来说,比新增字段要复杂的多。比如我在上面的基础上再把age字段删除掉。

(1)第一步:
将实体类中的age字段删除。

(2)第二步:
新建升级类M_2_to_1继承Migration,重写**migrate(xxx),以及构造方法,在migrate方法中进行sql语句操作

public class M_2_to_1 extends Migration {

    /**
     * 这个版本2要和对应的数据库版本对应
     */
    public M_2_to_1() {
        super(2, 1);
    }
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {

        //TODO 1、创建一个备份表
        database.execSQL("CREATE TABLE t_entity01_temp (id INTEGER PRIMARY KEY , productId INTEGER," +
                "userName TEXT)");

        //TODO 2、把数据拷贝到备份表中
        database.execSQL("INSERT INTO t_entity01_temp (id,productId,userName)" +
                "SELECT id,productId,userName FROM t_entity01");

        //TODO 3、删除旧表
        database.execSQL("DROP TABLE t_entity01");

        //TODO 4、将备份表名称改为原来的表名称
        database.execSQL("ALTER TABLE t_entity01_temp RENAME to t_entity01");

    }
}

注意,构造方法中版本是从2到1,需要在migrate方法中执行4个步骤才能将age字段移除。

  • 创建备份表,包含所有字段,除了age字段
  • 把原始数据拷贝到新的备份表中
  • 删除原始表
  • 修改备份表名称为原始表名称

(3)第三步:
修改数据库版本,在RoomDatabase的实现类EntityDaoDatabase 中修改version版本为1

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

public abstract class EntityDaoDatabase extends RoomDatabase {
   ...
 }

(4)第四步:
添加数据库升级配置,在Room.databaseBuilder中进行操作:

//操作数据库应该在子线程中操作
dataBase = Room.databaseBuilder(BaseApplication.getContext(),
          EntityDaoDatabase.class, "knowledge.db")
          .addMigrations(new M_1_to_2(),new M_2_to_1())//数据库版本升级
          //.fallbackToDestructiveMigration()   //这个方法也可以迁移数据库,但会将数据摧毁导致数据的丢失
          .build();

ok,以上就完成了表中删除字段的配置。

四、多表父子查询

在开发过程中,难免会遇到这样的数据类型,一个实体中存放List列表数据:

//CategoryBean 
@Entity(tableName = "t_category")
public class CategoryBean {

    @PrimaryKey
    public int id;

    public String categoryName;

    public int categoryId;

    @Ignore
    public List<ProductBean> mProductBeans = new ArrayList<>();
}

//ProductBean 
@Entity(tableName = "t_product")
public class ProductBean {

    @PrimaryKey(autoGenerate = true)
    public int id;

    public String productName;

    public int categoryId;
 }

对于这种数据格式,我们该如何一次性查出所有的ProductBean 呢?

1、方式一

先查CategoryBean ,然后根据categoryId再去查ProductBean 。两个for循环解决。

2、方式二

List 转换成String类型存储,需要序列化和反序列化。

3、方式三

其实Room已经为我们想到了这一点,需要借助中间类实现

public class CategoryWithProducts {
    @Embedded
    public CategoryBean t_category;

    /**
     * parentColumn:对应CategoryBean的 id
     * entityColumn:对应ProductBean中的 categoryId ,其实也是 CategoryBean.id
     */
    @Relation(parentColumn = "id",entityColumn = "categoryId")
    public List<ProductBean> mProductBeans = new ArrayList<>();

}
  • @Embedded :内嵌对象,使用该注解,表示当前实体类包含了CategoryBean 所有的属性。
  • @Relation:建立父子关系 parentColumn代表父类CategoryBean 的id,entityColumn 代表子类ProductBean中categoryId。

查询的时候适用如下语句,这样就可以将父类和子类一块查出来了:

@Query("Select *from t_category")
List<CategoryWithProducts> listAll()

五、外键、索引

外键的使用需要配合 @Entity 注解

//-------SchoolBean
@Entity(tableName = "t_school")
public class SchoolBean {
    @PrimaryKey
    public long id;
    public String name;
}

//-------StudentBean 
@Entity(
        tableName = "t_student",
        foreignKeys = {
                @ForeignKey(
                        entity = SchoolBean.class,
                        parentColumns = "id",
                        childColumns = "s_id"
                )
        },
        indices = @Index(value = {"s_id"}, unique = true)

)
public class StudentBean {
    @PrimaryKey(autoGenerate = true)
    public long id;
    @ColumnInfo(name = "s_id")
    public long schoolId;
    public String studentName;

}
  • foreignKeys :外键集合
  • @ForeignKey:外键,entity为关联的对象 ,parentColumns被关联属性,childColumns 当前类属性
  • indices:索引集合
  • @Index:索引,value 需要索引的属性,unique 设置为true表示这将是一个惟一的索引,任何重复的索引都将被拒绝。

举个例子:

	SchoolBean schoolBean1 = new SchoolBean(1,"学校1");
    SchoolBean schoolBean2 = new SchoolBean(2,"学校1");
    SchoolBean schoolBean3 = new SchoolBean(3,"学校1");
    SchoolBean schoolBean4 = new SchoolBean(4,"学校1");
    List<SchoolBean> schoolBeans = new ArrayList<>();
    schoolBeans.add(schoolBean1);
    schoolBeans.add(schoolBean2);
    schoolBeans.add(schoolBean3);
    schoolBeans.add(schoolBean4);
	//SchoolBeans添加到数据库
    SchoolDao schoolDao = RoomBuilder.getInstance().getDataBase().schoolDao();
    schoolDao.insert(schoolBeans);

    StudentBean studentBean1 = new StudentBean(1,"学生A");
    StudentBean studentBean2 = new StudentBean(3,"学生B");
	//Students添加到数据库
    StudentDao studentDao = RoomBuilder.getInstance().getDataBase().studentDao();
    studentDao.insert(studentBean1,studentBean2);

	//查询得到的结果存放到JoinInResult中
    List<JoinInResult> queryList = studentDao.listSchoolAndStudent();
    Log.e("sssssssssss",queryList.toString());
public class JoinInResult {

    @ColumnInfo(name = "s_id")
    public long schoolId;
    public String name;
    public String studentName;
}

打印的结果如下:

[
  JoinInResult{
    schoolId=1,
    name='学校1',
    studentName='学生A'
  },
  JoinInResult{
    schoolId=3,
    name='学校1',
    studentName='学生B'
  }
]

六、字段类型转换

在开发过程中,我想让我的实体类存放一个枚举类型的字段,该如何操作呢?这就需要借助 @TypeConverter 注解完成。

@Entity(tableName = "t_fruit")
public class FruitBean {
    @PrimaryKey
    private long id;
	//枚举类型
    private FruitEnum fruit;

}

我们来看一下如何自定义转换。

1、创建TypeConvert转换类
public class FruitConvert {

    @TypeConverter
    public static FruitEnum fromSting(String value){
        switch (value){
            case "apple":
                return FruitEnum.APPLE;
            case "banana":
                return FruitEnum.BANANA;
            case "orange":
                return FruitEnum.ORANGE;

        }
        return null;
    }
    @TypeConverter
    public static String fruitToString(FruitEnum fruitEnum){
        switch (fruitEnum){
            case APPLE:
                return "apple";
            case BANANA:
                return "banana";
            case ORANGE:
                return "orange";
        }
        return null;
    }
}

需要定义两个方法,分别阐述转换的具体操作:如何从枚举转换为实际数据类型?如何从数据类型转换为枚举类型?

2、关联TypeConvert

RoomDatabase实现类中添加转换类型

Database(entities = {
        ...
}, version = 1, exportSchema = false)

@TypeConverters({
        FruitConvert.class
})
public abstract class EntityDaoDatabase extends RoomDatabase {
	...
}

使用类注解 @TypeConverters。这样就完成了配置,可以正常在实体中使用了。

你可能感兴趣的:(AAC)