早些年没有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。这样就完成了配置,可以正常在实体中使用了。