前言
room 是在SQLite的基础之上,提供了一个抽象层。让我们可以更方便执行数据库的增删改查的。
所以,我们使用room任然是在使用Android 原生的SQLite数据库。
1.依赖项
最新版本依赖配置:
dependencies {
implementation "androidx.room:room-runtime:2.3.0"
annotationProcessor "androidx.room:room-compiler:2.3.0"
// 如果需要扩展 可以加上下面的。普通使用可以不用下面的
// 扩展- 支持 RxJava2
implementation "androidx.room:room-rxjava2:2.3.0"
// 扩展- 支持 RxJava3
implementation "androidx.room:room-rxjava3:2.3.0"
// 扩展 - 支持Guava 库 包括:Optional 和 ListenableFuture
implementation "androidx.room:room-guava:2.3.0"
// 扩展 -支持 测试用例
testImplementation "androidx.room:room-testing:2.3.0"
// 扩展- 支持分页集成 -- ps 有需要分页数据查询可以了解了解 paging库
implementation "androidx.room:room-paging:2.4.0-alpha04"
}
同时使用最新的room SDK 。建议JDK版本11以上。如果仍然使用8.0 会有警告提示:
警告: Current JDK version 1.8.0_291-b10 has a bug (https://bugs.openjdk.java.net/browse/JDK-8007720) that prevents Room from being incremental. Consider using JDK 11+ or the embedded JDK shipped with Android Studio 3.5+.
1 个警告
1.1.1版本以前的依赖配置 老版本
dependencies {
def room_version = "1.1.1"
implementation "android.arch.persistence.room:runtime:$room_version"
// 如果是kotlin 使用kapt 替换 annotationProcessor
annotationProcessor "android.arch.persistence.room:compiler:$room_version"
//下面的在2.0版本已经改了
// 扩展- 支持 RxJava2
implementation "android.arch.persistence.room:rxjava2:$room_version"
// 扩展 - 支持Guava 库 包括:Optional 和 ListenableFuture
implementation "android.arch.persistence.room:guava:$room_version"
// 扩展 -支持 测试用例
testImplementation "android.arch.persistence.room:testing:$room_version"
}
2.了解
room 都是通过注解的方式来使用的。
主要分为三个模块:(都是注解)
@Entity
: 定义数据库表名,列名, 主键等。 room根据该注解标注的类创建表结构。 一般放在实体类上
@Dao
: 定义数据库的增删改查操作,通常定义在接口类上。 所有的SQL语句 都是采用注解的方式绑定在函数中。
@Database
:必须添加在继承自RoomDatabase
的抽象类上。该类主要作用是创建数据库和创建Daos(data access objects,数据访问对象)。可以理解成 整个数据库的入口和初始化操作。之后的Entity 和Dao 都是依赖于该注解标注的类对象。通过它来访问Entity和Dao
3. 配置初始化
3.1 创建 Entity 类 (数据库表结构)
样例
import androidx.room.Entity;
//默认情况,将以类名做数据库表表名
@Entity()
public class MyTable {
}
//定义表名后,将按照该名称创建数据库表
@Entity(tableName = "Zinyan")
public class MyTable{
}
每一个表,都必须要有一个PrimaryKey 主键值。 所以@Entity
标识的实体类,必须有一个属性是进行@PrimaryKey
标注
示例:
@PrimaryKey
private String id;
@PrimaryKey(autoGenerate = true)
private String id;
autoGenerate =true 代表该主键自增,默认为false。
默认情况下,实体类中所有的属性都将序列化后并创建相应的列。可以通过@Ignore
注解,标注该属性不进行序列化
示例:
@Ignore
private boolean isCC;
每个属性,如果存储在数据库中。建议添加完整的set和get函数。
默认情况下,列名就是由属性名决定的。但是我们也可以通过@ColumnInfo 定义新的列名。
同时关于列的一些属性配置也可以通过该注解来添加
示例:
@ColumnInfo(name = "name", index = false, defaultValue = "")
private String title;
3.1.1 columnInfo 常用字段
定义列的一些常用属性,都可以通过该注解方式,并在括号里面添加指定值。
名称 | 数据类型 | 默认值 | 介绍 |
---|---|---|---|
name | String | 属性名 | 定义列名 |
typeAffinity | int | UNDEFINED | SQLite独有的类型相像,定义表数存储类型的。 |
index | boolean | false | 是否为索引字段 |
collate | int | UNSPECIFIED | 列值排序规则 |
defaultValue | String | 默认值 | 设置参数的默认值,当参数没有值的时候,数据库会使用默认值 |
TypeAffinity 类型的选项值:使用方式:ColumnInfo.UNDEFINED 可以拿到
名称 | 介绍 |
---|---|
UNDEFINED | 默认 |
TEXT | 字符串 String |
INTEGER | 整型数值 int 或者booleans |
REAL | 浮点数类型 floats 或者 doubles |
BLOB | 二进制数据 |
collate 类型的参数: (使用方式:ColumnInfo.UNSPECIFIED)
名称 | 介绍 |
---|---|
UNSPECIFIED | 默认值 - 效果等同BINARY |
BINARY | 区分大小写的排序规则 |
NOCASE | 不区分大小写的排序规则 |
RTRIM | 区分大小写的排序规则,忽略结尾的空格 |
LOCALIZED | 使用系统当前语言环境的排序(Android API 21 及以上) |
UNICODE | 使用Unicode 排序(Android API 21 及以上) |
创建一个简单类的示例:
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import java.util.List;
//定义表名后,将按照该名称创建数据库表
@Entity(tableName = "Zinyan")
public class MyTable {
@PrimaryKey
private String id;
@ColumnInfo(name = "name")
@NonNull //设置为非空
private String title;
@Ignore
private List list;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
3.2 创建DAO 类
示例:
package com.zinyan.demo;import androidx.room.Dao;@Daopublic interface MyDao {}
DAO 类是一个抽象接口。
在@Dao
下面有相应的增删改查的注解:@Insert
添加, @Update
更新,@Delete
删除 @Query
自定义SQL语句
其中。除了@Query
外,其他的注解都是封装好了SQL语句的。只会执行该sql语句的功能。如果需要特异性功能,那么就使用@Query
语句,然后填写你的SQL代码就可以了
3.2.1 insert 添加
示例代码: 全是在@Dao
注解定义的 接口类里面
//可以支持一个对象,或者多个对象的添加
@Insertvoid
addMyTable(MyTable... myTables);
// 可以批量添加
@Insert(onConflict = OnConflictStrategy.REPLACE)
void addMuTableList(List<MyTable> list);
更多示例代码:
public interface MusicDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertSongs(Song... songs);
@Insert
public void insertBoth(Song song1, Song song2);
@Insert
public void insertAlbumWithSongs(Album album, List<Song>songs);
}
如果,我只想添加部分数据,或者只想更新部分参数。我们可以使用entity 字段,来解决
entity 目标实体定义
//例如有一个表结构
@Entity
public class Playlist {
@PrimaryKey(autoGenerate = true)
long playlistId;
String name;
@Nullable
String description
@ColumnInfo(defaultValue = "normal")
String category;
@ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String createdTime;
@ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String lastModifiedTime;
}
public class NameAndDescription {
String name;
String description
}
@Dao
public interface PlaylistDao {
@Insert(entity = Playlist.class)
// 我们可以通过entity 实体,只添加部分字段。
//room会自动识别。将数据添加到正确的表之中。
public void insertNewPlaylist(NameAndDescription nameDescription);
}
onConflice 控制
主要定义在insert 数据时 出现了错误或者冲突时,程序该如何处理。
示例:
@Insert(onConflict = OnConflictStrategy.REPLACE)
void addMuTableList(List<MyTable> list);
onConflict 总共有三种参数可选:
OnConflictStrategy.ABORT :默认值。出现冲突,撤销当前操作记录。但是已添加的数据不影响,并停止后续的操作。
OnConflictStrategy.REPLACE :出现冲突,将已有数据,进行更新。替换成最新记录。继续执行后面的语句。
OnConflictStrategy.IGNORE: 出现冲突,保留已有数据,本次操作不生效。继续执行后面的语句。
3.2.2 update 更新
示例:
@Updateint
updateTable(MyTable table); //更新一个,
@Updateint
updateTable(MyTable... table); //更新多个
@Updateint
updateTable(List<MyTable> table); //更新数组
它也拥有 entity 和onConflice 的属性,操作功能和效果与insert 是一样的。
例如:
@Update(onConflict = OnConflictStrategy.REPLACE)
int updateTable(MyTable table); //更新一个,
很多地方和insert 是类似的。只是相对于Insert 它定义的函数,会有返回值。
返回值通常是int 。主要是告诉我们更新成功的条目数量。
3.2.3 Delete 删除
示例
@Deletevoid
DeleteTable(MyTable table);
@Deletevoid
DeleteTable(List<MyTable> table); //删除多个
删除函数,有entity
属性,但是没有onConflice
属性
3.2.4 query 请求
SQLite脚本逻辑 官网文档:www.sqlite.org 需要外网才能访问。 因为query更多的使用sql脚本进行的。
普通结构的删除,修改,添加我们可以使用上面已经封装好的脚本。但是如果碰见
带参数的查询,带参数的删除。等复杂维度的查询条件。我们就需要配置自己的sql了。
query注解就是来解决这个需求的。
示例:
@Query("DELETE FROM WORD") //删除数据库
void deleteWords();
@Query("DELETE FROM device WHERE id = :id") //删除指定id 的值
void deleteByDeviceId(String id);
@Query("SELECT * FROM device WHERE id = :id") // 查询 id指定id 的数据
List<Device> getDevicesById(String id);
@Query("SELECT * FROM device WHERE id = :id order by time desc") //查询指定id的数据,并按照time进行降序排序,(desc 降序,asc正序) 我们可以将查询的结果输出成
liveDataLiveData<List<Device>> getDevicesById(String id);
@Query("SELECT * FROM song WHERE id IN(:songIds)") //批量查询
List<Song>; findByIds(long[] songIds);
3.3 创建Database 类
创建完表结构,创建完SQL查询接口,我们最后就要创建一个数据库,将上面的东西封装起来,并调用了。
示例
@Database(entities = {MyTable.class, Space.class, Device.class},
version = 1, exportSchema = false)
@TypeConverters({StringConverters.class})
public abstract class AppDataBase extends RoomDatabase {
public abstract MyTableDao myTableDao();
public abstract DeviceDao deviceDao();
}
有多少个类,我们就需要在entities之中写多少个class 对象,version 定义了版本号。exportSchema:设置是否导出数据库schema,默认为true,需要在build.gradle中设置。
主要就是当数据库变动之后,我们可以通过这个配置将数据库导出到指定的本地路径,进行存储。
4. 使用数据库
我们定义的各种类,当前都是一个抽象对象。我们需要实例化对象。然后才能在后面的业务之中使用它
第一步:我们实例化@Database
注解标注的类
private AppDataBaseHelper(@NonNull Context context) {
mDataBase = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class, "zinyan.db") //数据库文件名,我们可以自定义
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
//Room启动时将检测version是否发生增加,如果有,那么将找到Migration去执行特定的操作。如果没有因fallbackToDestructiveMigration()。将会删除数据库并重建.
.fallbackToDestructiveMigration()
.build();
}
我们之后就可以直接使用mDataBase 对象获取不同表的DAO方法,执行各种SQL操作了。
4.1 Migrations 版本库迁移
我们如果在升级版本的时候,希望数据进行迁移。我们就需要创建Migration对象。
示例:
private final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
//例如这个 将数据库升级时 会在数据库表中 新增一个列。
database.execSQL("ALTER TABLE 你的表名 ADD COLUMN parentIds TEXT");
}
};
5 注意事项
1.SQLite之中表名是大小写敏感的。
2.DAO 查询事件是在子线程之中操作的。数据库操作都是在子线层中。
- 如果通过LiveData 观察者模式获取数据,那么当该表发生,Delete,Update,Insert 时,都会触发这个观察者对象重新查询。
如果你这个观察者对查询涉及多个表,那么每个表的变化,都会造成观察者被触发,收到通知。
评论区