Room
Room是Jetpack组件库一员,属于ORM库,Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
Room 包含 3 个主要组件:
- Database: 你可以使用这个组件创建一个数据库holder。注解定义了一系列entities并且类的内容提供了一系列DAOs,它也是下层的主要连接 的访问点。Database注解的类应该是一个抽象的继承 RoomDatabase的类。在运行时,你能获得一个实例通过调用Room.databaseBuilder()或者 Room.inMemoryDatabaseBuilder()
- Entity:这个组件代表了一个持有数据行的类。实际上也是一个bean类,对于每个entity,一个数据库表被创建用于持有items。你必须引用entity类通过Database类中的entities数组。每个entity字段被持久化为数据库中的列,使用@Ignore注解可以不添加到数据库中.
- DAO:包含用于访问数据库的方法。DAOs 是Room中的主要组件,并且负责定义访问数据库的方法。被注解为@Database的类必须包含一个没有参数的抽象方法并且返回注解为@Dao的类。当在编译时生成代码,Room创建一个这个类的实现。
官网对三者的说明,可以清楚的看到它们的职责。
导入
在model的build.gradle中添加,androidx版本:
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
看到annotationProcessor就知道它肯定是用的APT技术替我们生成了一些文件。
使用
第一步,先新建一个bean类,因为我比较喜欢车,就命名为Car吧:
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
//默认表名为类名
@Entity(tableName = "cars")
public class Car {
//主键
@PrimaryKey(autoGenerate = true)
//列名 _id
@ColumnInfo(name = "_id")
private int id;
@ColumnInfo(name = "_brand")
private String brand;
//不指定类名 默认就是model
private String model;
@ColumnInfo(name = "_price")
private int price;
//后面的set get toString都省略不贴了
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
//set get .....
}
这个类需要用@Entity注解,tableName 就是表名,不加tableName 默认就是类名。而且需要添加到数据库中的字段需要时public,如果不用public修饰就需要提供set get方法。如果某个成员不需要添加到数据库中,可以使用@Ignore注解它。
第二步,然后创建一个Dao类:
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface CarDao {
//如果冲突--替换
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insetCar(Car... car);
//查询所有的car
@Query("SELECT * FROM cars")
List<Car> getAll();
//查询指定品牌的car
@Query("SELECT * FROM cars WHERE _brand == :brand")
List<Car> getBrand(String brand);
//查询价格在min和max的car
@Query("SELECT * FROM cars WHERE _price BETWEEN :min AND :max")
List<Car> getCarBetweenPrices(int min,int max);
//可以返回void 返回int代表删除了多少行
@Delete
int deleteCar(Car car);
//更新 返回更新的个数
@Update(onConflict = OnConflictStrategy.REPLACE)
int updateCars(Car... cars);
}
CarDao就是对数据库的增删改查操作,这里就简单写了一些。
第三步,创建一个DataBase抽象类:
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = Car.class,version = 1)
public abstract class CarDatabase extends RoomDatabase {
public abstract CarDao carDao();
}
entities:数据库相关的所有Entity实体类,他们会转化成数据库里面的表。version:数据库版本。运行时,通过调用Room.databaseBuilder()或者Room.inMemoryDatabaseBuilder()获取实例。因为每次创建Database实例都会产生比较大的开销,所以应该将Database设计成单例的,或者直接放在Application中创建。
- Room.databaseBuilder()是创建一个存在文件系统中的数据库。
- Room.inMemoryDatabaseBuilder():创建一个存在内存中的数据库。当应用退出的时候(应用进程关闭)数据库也消失。
接下来就是去操作数据库了,做了个简单的封装:
import android.content.Context;
import androidx.room.Room;
import java.util.List;
import honeywell.com.androidstudy.DataBase.room.Car;
import honeywell.com.androidstudy.DataBase.room.CarDao;
import honeywell.com.androidstudy.DataBase.room.CarDatabase;
public class CarMode implements ICarMode{
private static volatile CarMode carMode;
private CarDatabase carDatabase;
private CarDao carDao;
private CarMode(Context context) {
//创建数据库
carDatabase = Room.databaseBuilder(context,CarDatabase.class,"Car.db")
.allowMainThreadQueries()
.build();
//获取到carDao
carDao = carDatabase.carDao();
}
public static CarMode getInstance(Context context) {
if (carMode == null) {
synchronized (CarMode.class) {
if (carMode == null){
carMode = new CarMode(context);
}
}
}
return carMode;
}
@Override
public void addItem(Car car) {
carDao.insetCar(car);
}
@Override
public int deleteItem(Car car) {
return carDao.deleteCar(car);
}
@Override
public List<Car> queryAll() {
return carDao.getAll();
}
}
通过这个单利类就可以去操作数据了。
看下效果:
因为是学习Room数据库的,请忽略UI。在看下数据库:
因为model我们没有手动指定他的ColumnInfo,所以就是原来对象的属性名。
升级
假如我们需要把model的列名改成_model,先在Entity类中修改:
@ColumnInfo(name = "_model")
private String model;
然后将版本号改成2:
@Database(entities = Car.class,version = 2)
public abstract class CarDatabase extends RoomDatabase {
public abstract CarDao carDao();
}
然后运行时就会挂掉:
java.lang.RuntimeException: Unable to start activity ComponentInfo{honeywell.com.androitstudy/honeywell.com.androidstudy.DataBase.CarActivity}: java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
因为还没提供迁移策略。
//表名是从版本1升级到版本2的策略
Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
// 将旧表重命名cars_temp
database.execSQL("ALTER TABLE cars RENAME TO cars_temp");
//创建一个新表
database.execSQL("CREATE TABLE IF NOT EXISTS `cars` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `_brand` TEXT, `_model` TEXT, `_price` INTEGER NOT NULL)");
//将旧表中的数据插入新表
database.execSQL("INSERT INTO cars SELECT `_id`,`_brand`,`model`,`_price` FROM cars_temp");
//删除旧表
database.execSQL("DROP TABLE cars_temp");
}
};
private CarMode(Context context) {
carDatabase = Room.databaseBuilder(context,CarDatabase.class,"Car.db")
.allowMainThreadQueries()
//添加Migrations
.addMigrations(MIGRATION_1_2)
.build();
carDao = carDatabase.carDao();
}
因为sqlite没有提供直接修改列名的方法,所以只能采用这种方法。其他比如加一列直接用alter table add column就行了,这里就不再演示了。下面是升级后的数据库,注意看model的列名:
此时已经改成了_model了,而且之前的数据依旧存在。
如果你有一个非常老的数据库版本为1,而现在线上最新的为4。当前我们已经定义了以下几种迁移:
版本1 —> 版本2
版本2 —> 版本3
版本3 —> 版本4
那么 Room 将会触发所有的迁移策略,一个接一个去执行。
另外 Room 可以处理夸版本更新:直接可以定义一个迁移,直接一步从版本1到版本4,从而使迁移的进程更快。这里就不再演示了。
关于Room数据库的使用就写这么多吧,希望对大家有所帮助,如果有需要看源码的同学可以留言。