文章目录
Android之Room学习
前言
学习Room数据库,顺便复习了自己的数据库知识。
Room在SQLite上提供一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
这是开发指南上给出Room的概念,即简化了SQLite的操作,减少代码量。
1. Room组件内容
Room主要由三部分组成:
-
数据库(Database): 存储数据表的容器
@Database(entity={User.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { ...... }
上面标识用于指定自定义数据库类(抽象类)包括数据库中的表和数据库版本版本
自定义数据库类继承RoomDatabase,用来连接Entity和DAO,配置一些数据库功能。
由于实例化一个 RoomDatabase 对象的开销是比较大的,所以 DataBase 的使用需要遵循单例模式,只在全局创建一个实例即可。
-
实体(Entity):数据库中的表,用于存储结构化数据
- 在标识@Entity时指定表名、索引、外键、组合主键等
- @PrimaryKey指定主键等
- @ColumnInfo指定列信息等
- 在标识@Entity时指定表名、索引、外键、组合主键等
-
访问方法(DAO):访问数据库的方法
DAO即为对数据进行操作,增删改查
@Dao public interface UserDao { @Query("SELECT * FROM users") List<User> getAll(); @Update void update(User... users); @Insert void insertAll(User... users); @Delete void deleteByID(User user); }
增删改查四大操作,只有查询操作是相对丰富的。其余的基本根据传入的结构进行相应的操作,个人认为主要是依靠主键进行操作,同时@Query中的语句同样可以完成其他三种操作。
2. Entity属性
2.1 主键
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
......
}
@Entity(tableName = "users")
public class User {
public class User {
public int id;
......
}
2.2 索引和唯一约束
索引的写法
@Entity(tableName = "users", indices = { @Index(name = "name",value = {"first_name","last_name"})})
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
.......
}
UNIQUE写法
@Entity(tableName = "users", indices = { @Index(name = "name",value = {"first_name","last_name"}, unique = true)})
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
.......
}
2.3 主键与唯一索引的区别
是否保证唯一性 | 是否允许为空 | 一个表中的数量 | 是否允许组合 | |
---|---|---|---|---|
主键 | 是 | 否 | 至多1个 | 是,但不推荐 |
唯一索引 | 是 | 是 | 可以有多个 | 是,但不推荐 |
2.4 外键
外键,从表外键字段到主表的某一字段,一般都是外键字段是主表的主键字段。
@Entity(tableName = "Userinfo", foreignKeys = @ForeignKey(entity=User.class, parentColumns = "id", childColumns = "user_id"))
public class Userinfo {
@PrimaryKey
public int user_id;
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "sex")
public String sex;
@ColumnInfo(name = "age")
public int age;
}
3. 数据库升级
升级数据库需要在数据库实例化时添加一个.addMigrations(Migration)
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("Create table userInfo(user_id integer primary key," +
"sex String," +
"age integer," +
"id integer," +
"constraint FK_ID foreign key(user_id) References user(id))");
Log.d("appDatabase", "升级成功");
}
};
public static AppDatabase create(Context context){
//allowMainThreadQueries()方法,所以加上这行代码是为了防止 【Cannot access database on the main thread since it may potentially lock the UI for a long period of time.】 的报错。正式使用时,请务必去掉这行代码,因为它会让所有耗时操作运行在主线程!
return Room.databaseBuilder(context, AppDatabase.class, "app-db").allowMainThreadQueries().addMigrations(MIGRATION_1_2).build();
}
4. ROOM对象关系
ROOM不支持对象之间的引用,因此当我们所希望的某些成员是一个封装的紧密内部实体,而非基础型数据时,就引入了嵌套的概念.穿件嵌套主要通过依赖@Embedded
注释来实现.
4.1 创建嵌套对象
定义
如果某一Entity
的某一字段是一个紧密整体,即非基础类型数据时,可以使用嵌套的方法对该紧密整体进行拆解到Entity
的字段中.
实现
使用@Embedded将该紧密整体(类)中数据分解成实体的列字段.
比如:用户User
的数据字段有住址Address
型,使用嵌套将Address
的各个数据单独存储到User
的列字段.如下:
单独声明一个住址类Address
(注意不注释为Entity
)
public class Address {
public String street;
public String state;
public String city;
@PrimaryKey
@ColumnInfo(name = "post_code")
public String postCode;
}
在User
中嵌套住址类Address
@Entity(tableName = "user")
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Embedded
public Address address;
}
查询User时,得到的字段有:
id
、first_name
、last_name
、street
、state
、city
、postCode
4.2 一对一关系
定义
一对一关系指的是父类Entity
与子类Entity
一一对应,可以类比于一次函数,一个 x 对应一个 y ,同样一个 y 对应一个 x .
实现
在父Entity
中进行@Embedded
声明子Entity
类,并对两者进行一对一关系建立.
如音乐软件中用户User
的歌曲库Libary
只能有一个,且每一个歌曲库对应着自己的
- 分别注释实体
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
@Entity(tableName = "Libary")
public class Libary {
@PrimaryKey
public long libaryID;
@ColumnInfo(name = "user_id")
public long userID;
}
-
定义二者之间的一对一关系类
UserAndLibary
.使用
@Embedded
来指定父Entity
.使用
@Relation
来指定对应关系,parentColumn
为父Entity
的主键,entityColumn
为"引用"父Entity
主键的子Entity
列字段.
public class UserAndLibary {
@Embedded public User user;
@Relation(
parentColumn = "id",
entityColumn = "user_id"
)
public Libary libary;
}
-
DAO中添加方法返回关系类实例
由于该方法需要 Room 运行两次查询,因此应向该方法添加
@Transaction
注释,以确保整个操作以原子方式执行。
4.3 一对多关系
定义
一对多关系指的是父类Entity
的一条实例与子类Entity
中n条实例(n可以为任意非负整数)对应,可以类比于高次函数,一个 x 对应一个 y ,但是一个 y 可以对应 n 个 x .
实现
在父Entity
中进行@Embedded
声明子Entity
类,并对两者进行一对多关系建立.
如音乐软件中用户User
的播放列表MusicList
可以有多个,且每一个播放列表对应着自己的用户
-
分别注释实体
@Entity(tableName = "users") public class User { @PrimaryKey(autoGenerate = true) public int id; @ColumnInfo(name = "first_name") public String firstName; @ColumnInfo(name = "last_name") public String lastName; }
@Entity(tableName = "musicList") public class MusicList { @PrimaryKey public long id; @ColumnInfo(name = "list_name") public String listName; @ColumnInfo(name = "user_id") public long userID; }
-
定义二者之间的一对多关系类
UserWithMusicLists
.使用
@Embedded
来指定父Entity
.使用
@Relation
来指定对应关系,parentColumn
为父Entity
的主键,entityColumn
为"引用"父Entity
主键的子Entity
列字段,同时与一对一不同的是,此处子Entity
改为列表型数据类型.public class UserWithMusicLists { @Embedded public User user; @Relation( parentColumn = "id", entityColumn = "user_id" ) public List<MusicList> musicLists; }
-
DAO中添加方法返回关系类实例
与一对一相似,不再赘述.
@Transaction @Query("SELECT * FROM users") List<UserWithMusicLists> getAllUsersWithMusicLists();
4.4 多对多关系
定义
多对多关系指的是父类Entity
的一条实例与子类Entity
中n条实例(n可以为任意非负整数)对应; 同样,子类Entity
的一条实例与父类Entity
中n条实例(n可以为任意非负整数)对应.
实现
在父Entity
中进行@Embedded
声明子Entity
类,同样,也在子Entity
类中进行@Embedded
声明父Entity
,并对两者进行多对多关系建立.
如音乐软件中播放列表MusicList
中有许多音乐Music
,Music
可以存放许多播放列表MusicList
.
-
分别注释实体并关联两个实体
@Entity(tableName = "users") public class User { @PrimaryKey(autoGenerate = true) public int id; @ColumnInfo(name = "first_name") public String firstName; @ColumnInfo(name = "last_name") public String lastName; }
@Entity(tableName = "musicList") public class MusicList { @PrimaryKey public long id; @ColumnInfo(name = "list_name") public String listName; @ColumnInfo(name = "user_id") public long userID; }
@Entity(tableName = "musicLists_musics", primaryKeys = {"music_list_id","music_id"}) public class MusicListAndMusic { @ColumnInfo(name = "music_list_id") public long musicListId; @ColumnInfo(name = "music_id") public long musicId; }
-
定义二者之间的多对多关系类
MusicWithMusicLists
、MusicListWithMusics
.使用
@Embedded
来指定父Entity
.使用
@Relation
来指定对应关系,parentColumn
为父Entity
的主键,entityColumn
为"引用"父Entity
主键的子Entity
列字段,引入associateBy=@Junction(XXX.class)
表明多对多关系.public class MusicWithMusicLists { @Embedded public Music music; @Relation( parentColumn = "id", entityColumn = "id", associateBy = @Junction(MusicListAndMusic.class) ) public List<MusicList> musicLists; }
public class MusicListWithMusics { @Embedded public MusicList musicList; @Relation( parentColumn = "id", entityColumn = "id", associateBy = @Junction(MusicListAndMusic.class) ) public List<Music> musics; }
-
DAO中添加方法返回关系类实例
与上文类似,不再赘述.
@Transaction @Query("SELECT * FROM musicLists") List<MusicListWithMusics> getAllMusicsWithMusicLists(); @Transaction @Query("SELECT * FROM musics") List<MusicWithMusicLists> getAllMusicListsWithMusics();
4.5 嵌套关系
定义
当出现查询多表集合时,想实现表格之间的相互关联就需要使用嵌套关系。
实现
拆分处理,画出关联图,理清嵌套关系,从最底层开始一层一层往上嵌套.
如下图用户和播放列表之间一对多,播放列表和音乐多对多。
-
先处理音乐与播放列表的对应关系
与多对多关系定义重复,因此省略。
-
处理用户与带有音乐的列表的关系
新建关系类
UserWithMusicListsWithMusics
引入
entity
指定entityColumn
来源.public class UserWithMusicListsAndMusics { @Embedded public User user; @Relation( parentColumn = "id", entity = MusicList.class, entityColumn = "user_id" ) public List<MusicListWithMusics> musicLists; }
4.6 注意
嵌套关系非常影响处理数据的速度,因为回去嵌套在内的表都会被查询,过于复杂.
4.7 思考
学习到最后,才发觉这一部分内容和数据库中联合查询的内容有着很大的相似程度——多表查询
直接使用Query进行联合查询也可以得到相同的结果.
5. ROOM数据库视图
定义
从一个或多个表导出的虚拟的表,表内容由查询定义。具有普通表的结构,但是不实现数据存储。
对视图的修改:单表视图一般用于查询和修改,会改变基本表的数据
多表视图一般用于查询,不会改变基本表的数据。
定位:虚拟表,并没有实际储存,多用于查询。
实现
与一般数据库操作类似。
以上文用户为例,实现如下:
@DatabaseView("SELECT users.id, users.first_name, users.last_name , libary.libaryId FROM users INNER JOIN libary ON libary.user_id = users.id")
public class UserDetailView {
public long id;
public String firstName;
public String lastName;
public long libaryId;
}
在数据库方面:
@Database(entities = {User.class, Address.class, Libary.class, Music.class, MusicList.class, MusicListAndMusic.class}, views = {UserDetailView.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
......
}
总结
至此,ROOM的基本学习结束.
在这一块主要掌握几点
- 数据表的生成,主要是
Entity
的定义 - 数据库的生成,主要继承
RoomDatabase
类,且数据库只需要一个实例,因此需要进行单例模式改造(使用懒汉式加锁双重排查) - 数据表的关联,定义
Entity
,然后定义关联类(类中@Embedded
父类实体+@Relation
指定相关字段),具体看上述分析 - 数据表的视图,注释
@DataView
,在数据库上指定即可
其他学习分享系列
数据结构与算法系列
数据结构与算法之哈希表
数据结构与算法之跳跃表
数据结构与算法之字典树
数据结构与算法之2-3树
数据结构与算法之平衡二叉树
数据结构与算法之十大经典排序
数据结构与算法之二分查找三模板
如有兴趣可以关注我的微信公众号,每周带你学一点算法与数据结构。