简介
谷歌提供的数据库封装库,不建议直接用Sqlite
简单使用
- 配置依赖
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
//java使用
annotationProcessor "androidx.room:room-compiler:$room_version"
//kotlin使用
//kapt "androidx.room:room-compiler:$room_version"
- 配置表
//默认表名是user,如果想要换可以通过tableName自定义
@Entity(tableName = "my_user")
public class User {
//每个表必须有primaryKey,如果需要Room自动分配可通过@PrimaryKey(autoGenerate = true)设置,该值默认false//如果是复合主键,可通过@Entity(primaryKeys = {"firstName", "lastName"})设置@PrimaryKeypublic int uid;//Room默认使用字段名称作为数据库中的列名称即firstName,如果想要自定义可以通过@ColumnInfo(name = "first_name")指定,默认值用defaultValue指定@ColumnInfo(name = "first_name", defaultValue = "a")public String firstName;@ColumnInfo(name = "last_name")public String lastName;public String image;public int level;//Room会为@Entity标注的实体中每个字段都创建一个列,如果想要忽略某个字段使用@Ignore//如果是继承的可以使用@Entity(ignoredColumns = {"", ""})设置@Ignorepublic String password;//嵌套对象使用@Embedded注解,这是在Room中的表就会为Address中的street和city字段创建列,不会新建Address表,但是插入查询等操作不变,还是使用address,@Embeddedpublic Address address;
}public class Address{
public String street;@ColumnInfo(name = "local_city", defaultValue = "火星")public String city;
}
这边简单起见字段都用public,如果是private需要提供getter和setter方法,它们的名称要遵循Room中JavaBeans规范
此外,Sqlite是关系型数据库,Room可以在表之间添加关联
- 一对一关联
场景举例:每个用户至多只有一个收藏列表,想要查询出哪些用户有收藏列表
使用:
@Entity
public class User{
@PrimaryKeypublic long uid;
}
@Entity
public class Collection{
@PrimaryKeypublic long cid;//子实体需包含父实体的主键的引用public long uidInCol;
}
//为它们创建一对一关联类
public class UserAndCollection{
//引入其他实体需要@Embedded注解@Embeddedpublic User user;//子实体需要使用@Relation注解,其中parentColumn为父实体主键列名称,entiryColumn为引用父主键的名称@Relation(parentColumn = "uid", entiryColumn = "uidInCol")public Collection collection;
}
//最后在dao中使用查询
@Transaction //原子操作
@Query("SELECT * FROM user")
public List<UserAndCollection> getUsersAndCollections();
- .一对多关联
场景举例:每个用户可以有多个播放列表
使用:使用基本同一对一只是在创建关联的时候稍有不同,子实体需要用List
public class UserAndCollection{
@Embeddedpublic User user;//这边使用集合类@Relation(parentColumn = "uid", entiryColumn = "uidInCol")public List<Collection> collections;
}
- 多对多关联
场景举例:每个播放列表对应多首歌曲,每个歌曲可以在多个播放列表中
使用:
//创建表
@Entity
public class PlayList{
//播放列表@PrimaryKeypublic long playListId;
}
@Entity
public class Song{
//歌曲@PrimaryKeypublic long songId;
}
@Entity(primaryKeys = {
"playListId", "songId"})
public class PlayListsAndSongs{
//保存列表和歌曲对应关系public long playListId;public long songId;
}//添加多对多关联
public class PlayListWithSongs{
@Embeddedpublic PlayList playList;//使用associateBy字段来标识PlayList和Song如何对应@Relation(parentColumn = "playListId", entiryColumn = "songId", associateBy = @Junction(PlayListsAndSongs.class))public List<Songs> songs;
}
//最后在dao中使用查询
@Transaction //原子操作
@Query("SELECT * FROM PlayList")
public List<PlayListsAndSongs> getPlayListsAndSongs();
- 嵌套
场景举例:每个用户有多个播放列表,每个列表有多首歌曲
使用:表的声明都没变
//添加关联
public class UserWithPlayListsAndSongs{
@Embeddedpublic User user;@Relation(entity = PlayList.class, parentColumn = "uid", entiryColumn = "uidInPlayList")public List<PlayListWithSongs> playListWithSongs;
}
//在dao中查询所有用户下播放列表,和每个播放列表下的歌曲
@Transaction
@Query("SELECT * FROM user")
public List<UserWithPlayListsAndSongs> getUserWithPlayListsAndSongs();
- 写关于表的操作
- @Dao
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)void insertAll(User... users);@Insertvoid insertUser(User user1, User user2);@Insertvoid insertUserAndCollection(User user, List<Collection> collections);//按主键匹配更新user,一般返回一个int值,标识更新了多少行@Updateint update(User... users);@Updateint update(List<User> users);//按主键匹配删除user,一般返回一个int值,标识删除了多少行@Deleteint delete(User... users);@Query("SELECT * FROM user")LiveData<List<User>> getAll();
}
- @Inset
@Insert(onConflict = OnConflictStrategy.REPLACE)
onConflict标识当发生冲突时处理办法 默认是ABORT,即终止,常用的还有IGNORE 忽略 和 REPLACE替换
@Insert(entity = User.class)
指定使用哪个实体
比如:
@Entity
publicclass User{
@PrimaryKey(autoGenerate = true)long uid;String name;String age;@ColumnInfo(defaultValue = "火星")String address;
}
public class SimpleUser{
String name;String age;
}//这时在dao中使用时就需要指定实体
@Insert(entity = User.class)
public void insert(SimpleUser simpleUser);
- @Update
和Insert一样,可以通过onConflict来指定冲突发送时处理方式,entity来指定实体 - @Delete
和Insert一样,可以通过entity来指定实体 - @Query
@Query("SELECT * FROM user")
里面的语句会在编译时进行验证,因此如果查询出现问题,如表名错误、字段名错误、查询规则没写完等,就会发生编译错误,而不是运行时失败.
如果这样写:
@Query("SELECT * FROM user")
List<SimpleUser> getAll();
Room 会验证查询的返回值,如果SimpleUser和User有部分字段匹配,则会发出警告。如果没有字段匹配,则会发出错误。
这样写法用的较多,因为页面不会同时需要user的所有数据,这时就可以为该页面创建一个新类作为查询返回值,Room会从user匹配相应的列到返回值中,但是注意SimpleUser中属性名需要和User表生成的列匹配。从而加快查询效率,节省资源。还有一种情况就是多表查询,查询出来的结果一般不是需要这些表所有列,这时也是需要新写一个类,比如:
@Query("SELECT user.name AS userName, pet.name AS petName " +"FROM user, pet " +"WHERE user.id = pet.user_id")
public LiveData<List<UserPet>> loadUserAndPetNames();
设置查询条件:参数在sql中按 :参数名 方式使用,Room 通过参数名称进行匹配。如果有不匹配的情况,也会在编译时报错。
@Query("SELECT * FROM user WHERE first_name LIKE :firstName")
List<User> getAll(String firstName);
这边实验了一下,如果想传对象,比如user,它是否会自动找user的first_name然后匹配,即
@Query("SELECT * FROM user WHERE first_name LIKE :user")
List<User> getAll(User user);
结果编译时报错
Query method parameters should either be a type that can be converted into a database column or a List / Array that contains such type. You can consider adding a Type Adapter for this.
想法错误,这错误大致意思是参数名必须是列名称,或者包含列名称的列表、数组,即
@Query("SELECT * FROM user WHERE first_name LIKE :firstNames")
List<User> getAll(List<String> firstNames);
返回值处理是数据对象外还可以是游标,但是谷歌不建议这样写,官方描述是Cursor无法保证行是否存在或者行包含哪些值。只有当您已具有需要光标且无法轻松重构的代码时,才使用此功能。
其实一般返回值不直接写数据对象,因为这样Room就是一个简单的数据库操作,体现不出架构组件的优势,而实际往往需要当数据变化时界面自动更新这时就要使用 LiveData 类型的返回值。当数据库更新时,Room 会生成更新 LiveData 所必需的所有代码
@Query("SELECT * FROM user")
LiveData<List<User>> getAll();
- 配置数据库
//使用@Database注解生成数据库,由它注释的类必须是一个抽象类并且继承自RoomDatabase
@Database(entities = {
User.class, Message.class}, version = 1, exportSchema = false)
public abstract class CalendarDataBase extends RoomDatabase {
public abstract UserDao userDao();public abstract MessageDao messageDao();public abstract MessageAndUserDao mAuDao();
}
数据库对其内部的Entity 和 Dao 没有数量限制,但是不可重复。exportSchema标识Room将数据库架构导出到文件夹中,还有一个参数是views 和 entities 标识保存的实体view
- 使用
//先封装一个类方便外部调用
public class CalendarDbManager {
public static final String DB_NAME = "calendar_db";private Context mContext;public static CalendarDbManager getInstance(){
return InnerCalendarDbManager.instance;}private static class InnerCalendarDbManager{
private static CalendarDbManager instance = new CalendarDbManager();}public void initCalendarDb(Context context) {
if (context instanceof Application){
mContext = context;}else{
// throw new Exception("");}}public void insert(){
//需在子线程中new Thread(){
@Overridepublic void run() {
CalendarDataBase dataBase = Room.databaseBuilder(mContext, CalendarDataBase.class, DB_NAME).build();User user = new User();user.uid = 1;user.firstName = "a";user.lastName = "b";dataBase.userDao().insertAll(user);}}.start();}public LiveData<List<User>> queryAll(){
CalendarDataBase dataBase = Room.databaseBuilder(mContext, CalendarDataBase.class, DB_NAME).build();return dataBase.userDao().getAll();}
}//在Activity或者Fragment中使用
CalendarDbManager.getInstance().initCalendarDb(getApplicationContext());
CalendarDbManager.getInstance().insert();
CalendarDbManager.getInstance().queryAll().observe(this, new Observer<List<User>>() {
@Overridepublic void onChanged(List<User> users) {
mBinding.setUser(users.get(0));}
});