一、基础使用
1.1 Room三个主要操作类
- Entity 实体类 代表一个表中的字段
- Dao数据库操作接口提供增删改查
- 管理创建数据库
1.2 引入依赖
https://developer.android.com/jetpack/androidx/releases/room
选择性引入
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
// optional - Kotlin Extensions and Coroutines support for Room
// implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
// implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
// implementation "androidx.room:room-guava:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
1.3 创建数据库实体 Entity
@Entity(tableName = "User") //数据库中表名
public class User {
//主键 自增
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "user_name", typeAffinity = ColumnInfo.TEXT) //实际数据库中的字段user_name 数据类型
private String name;
@ColumnInfo(name = "user_gender")
private String gender;
private int age;
@Ignore //忽略字段
private int flag;
public User(int id, String name, String gender, int age) {
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
}
public User(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@Ignore
public User(String gender, int age) {
this.gender = gender;
this.age = age;
}
// 省略 get set ......
}
构造方法中的形参代表查询时需要查询的字段,如果有多个构造方法 可以使用注解@Ignore忽略, 同样
1.4 创建数据库操作接口 Dao
@Dao //Database access object 数据库访问接口 所有增删改查等操作都在此声明
public interface UserDao {
// long Not sure how to handle insert method's return type.long
@Insert
void insertUser(User... users);
// int 影响的行数
@Update
int updateUser(User... users);
@Delete
void deleteUser(User... users);
@Query("DELETE FROM USER")
void deleteUser();
@Query("SELECT * FROM USER ORDER BY ID DESC")
List<User> getAllUser();
}
1.4.1 唯一约束+REPLACE实现有则更新无则插入
- 实体类中通过indices 将name字段设置成唯一约束
@Entity(tableName = "chatrow", indices = {@Index(value = {"name"}, unique = true)} ) //数据库实体类
public class User {...}
- Dao中插入语句设置onConflict插入模式
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertUser(User... users);
}
- 插入时如果表中有名字相同则更新本条数据 否则插入
- 更多语法参考官网 https://developer.android.com/training/data-storage/room/accessing-data?hl=zh-cn
1.5 创建数据库管理 Database
// entities ={多个表逗号隔开}
@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
public abstract UserDao getUserDao();
// 若entities有多个实例,则应该写多个Dao
}
1.6 使用
操作数据库必须在子线程中,为了简单此处直接使用allowMainThreadQueries()强制在主线程运行,正常开发不允许
MyDatabase myDatabase = Room.databaseBuilder(this, MyDatabase.class, "user")
.allowMainThreadQueries()//强制在主线程运行 不建议
.build();
UserDao userDao = myDatabase.getUserDao();
//增加
userDao.insertUser(new User("nikola", "男", 22));
//修改ID为3的
int row = userDao.updateUser(new User(3, "kolia", "女", 22));
//查询所有
List<User> allUser = userDao.getAllUser();
二、使用单例注意事项
Google官方称创建数据库实例较耗时,所以使用单例模式,但要注意: 无参构造方法不能使用private修饰 否则Room自动生成实现类时会报错
// entities ={多个集合逗号隔开}
@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
private static final String DATABASE_NAME="my_db.db";
private static MyDatabase myDatabase;
public MyDatabase() {
}
synchronized public static MyDatabase getInstance(Context context) {
if (null == myDatabase) {
myDatabase = Room.databaseBuilder(context.getApplicationContext(), MyDatabase.class, DATABASE_NAME)
.allowMainThreadQueries() //强制在主线程运行 不建议
.build();
}
return myDatabase;
}
public abstract UserDao getUserDao();
// 若entities有多个实例,则应该写多个Dao
}
指定数据库db文件保存路径
synchronized public static MyDatabase getInstance(Context context) {
if (null == myDatabase) {
File priExternalDir = FileUtil.getPriExternalDir(context, Environment.DIRECTORY_DOWNLOADS);
final String dbPath = new File( priExternalDir.getAbsolutePath()+File.separator+DATABASE_NAME).getPath();
myDatabase = Room.databaseBuilder(context.getApplicationContext(), MyDatabase.class, dbPath)
.allowMainThreadQueries() //强制在主线程运行 不建议
.build();
}
return myDatabase;
}
三、 LiveData 监听数据库变化
3.1 返回值使用LiveData包装
数据变化只在查询时提现,所以修改UserDao中查询所有的返回值类型, 且LiveData内部已经实现再子线程中执行,可以直接调用
@Query("SELECT * FROM USER ORDER BY ID DESC")
// List<User> getAllUser();
LiveData<List<User>> getAllUserLive();
3.2 设置监听
LiveData<List<User>> listLiveData = userDao.getAllUserLive();
listLiveData.observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {
......
}
});
当对数据库进行增删改时会回调onChanged
四、数据库版本迁移
- 变更Entity中的字段 要增加 Database中的version,
- 迁移数据库有保留原有数据与 破坏式清空原有数据
4.1 破坏式
allowMainThreadQueries 不保留数据直接删除原有数据库
MyDatabase myDatabase = Room.databaseBuilder(this, MyDatabase.class, "user")
.allowMainThreadQueries()//强制在主线程运行 不建议
.fallbackToDestructiveMigration() //破坏式: 当数据库版本变化时不做数据迁移
.build();
4.2 保留原有数据迁移
当Entity字段改变时调用.addMigrations(Migration... migrations),指定从某个版本迁移到某个版本需要做的某项操作,具体操作定义在Migration中
MyDatabase myDatabase = Room.databaseBuilder(this, MyDatabase.class, "user")
.allowMainThreadQueries()//强制在主线程运行 不建议
.addMigrations(migration) //保留原有数据
.build();
- 表中增加type字段时的addMigrations 实参 migration如下,需要自行写增加字段语句
static final Migration migration = new Migration(2,3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE user ADD COLUMN type INTEGER NOT NULL DEFAULT 1");
}
};
- 删除某个字段,sqlLet没有删除字段语句,只能创建新的数据库定义需要的字段,将原有数据库数据复制过去,删除旧数据库后再将新数据库重命名
new Migration(3,4) 表示从版本3升级到版本4
static final Migration migration = new Migration(3,4) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE user_temp (id INTEGER PRIMARY KEY NOT NULL , user_name TEXT," +
"user_gender TEXT)");
database.execSQL("INSERT INTO user_temp (id, user_name, user_gender) " +
"SELECT id, user_name, user_gender FROM user");
database.execSQL("DROP TABLE user");
database.execSQL("ALTER TABLE user_temp RENAME to user");
}
};