Room的基本使用

参考:jetpack之Room数据库

引言

在这里插入图片描述
  Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。
  Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良好的开发体验,大大降低了SQLite的使用门槛。

  
相对于SQLiteOpenHelper等传统方法,使用Room操作SQLite有以下优势:

  • 编译器的SQL语法检查
  • 开发高效,避免大量模板代码
  • API设计友好,容易理解
  • 可以LiveData关联,具备LiveData Lifecycle的所有魅力

  
Room的使用,主要涉及以下3个组件:

  • Database:访问底层数据库的入口
  • Entity:代表数据库中的表(table),一般用注解
  • Data Access Object(DAO):数据库访问者

  通过Database获取DAO,然后通过DAO查询并获取entities,最终通过entities对数据库table中数据进行读写。

Room是SQLite数据库的抽象

一、基本使用

项目整体架构层级:
在这里插入图片描述

1. 导入相关引用

app的build.gradle中添加如下配置:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.example.myroom2"
        minSdkVersion 26
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

   // 导入Room相关依赖
    def room_version = "2.0.0-beta01"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // use kapt for Kotlin

    // 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"

    //ViewModel
    implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
}

2. 建表Entity

Student.java

package com.example.myroom.entity;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
// @Entity注解在类之上,代表是一张数据库表,tableName属性可以指定表的名称,默认为类名
// @Entity(tableName = "Student")
@Entity
public class Student  {

    // 主键  autoGenerate=true 自增长 从1开始
    @PrimaryKey(autoGenerate = true)
    private int uid;

    // 字段名称,name属性可以指定字段名称,默认为变量名
    // 默认不写@ColumnInfo注解,也会在数据库根据变量名新建一个字段。
    @ColumnInfo(name = "name")
    private String name;

    @ColumnInfo(name = "pwd")
    private String password;

    @ColumnInfo(name = "address")
    private int address;

    public Student(String name, String password, int address) {
        this.name = name;
        this.password = password;
        this.address = address;
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAddress() {
        return address;
    }

    public void setAddress(int address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
                "uid=" + uid +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", address=" + address +
                '}';
    }
}

StudentTuple.java

package com.example.myroom.entity;

import androidx.room.ColumnInfo;

// @Entity 不能加,不然加了就是一张表了
// 并且 StudentTuple类中的name属性和pwd属性必须要和Student的name属性和pwd属性一致
public class StudentTuple {

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "pwd")
    public String password;

    public StudentTuple(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "StudentTuple{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

  • @Entity:注解在类之上,代表是一张数据库表,tableName属性可以指定表的名称,默认为类名。
  • @PrimaryKey:表的主键,autoGenerate =true代表自增长。
  • @ColumnInfo:字段名称,name属性可以指定字段名称,默认为变量名。
  • @Ignore:该注解表示当前数据库忽略当前属性,不会创建该字段。
    默认不写@ColumnInfo注解,也会在数据库根据变量名新建一个字段。

3. 数据库操作类Dao

StudentDao.java

package com.example.myroom.dao;

import com.example.myroom.entity.Student;
import com.example.myroom.entity.StudentTuple;

import java.util.List;

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

// 当前类必须是interface
// @Dao注解在类之上,提供给外部操作数据库的方法
@Dao
public interface StudentDao {

    // Student...等价于Student[]
    @Insert
    void insert(Student... students);

    @Delete
    void delete(Student student);

    @Update
    void update(Student student);

    @Query("select * from Student")
    List<Student> getAll();

    // 查询一条记录
    @Query("select * from Student where name like:name")
    Student findByName(String name);

    // 数组查询  多个记录
    @Query("select * from Student where uid in(:userIds)")
    List<Student> getAllId(int[] userIds);

    // 只查询name 和 pwd 结果数据  给StudentTuple类接收
    // StudentTuple类中的name属性和pwd属性必须要和Student的name属性和pwd属性一致
    @Query("select name, pwd from Student")
    StudentTuple getRecord();
}

  • 当前类必须是interface
  • @Dao 注解在类之上,提供给外部操作数据库的方法
  • @Insert 添加,可以添加单个对象,亦可以是List、Array等集合类型。
  • @Query ,可以根据sql语句进行一系列查询、更新、删除操作。

4. 数据库RoomDatabase

AppDataBase.java

package com.example.myroom.db;

import com.example.myroom.dao.StudentDao;
import com.example.myroom.entity.Student;

import androidx.room.Database;
import androidx.room.RoomDatabase;

/**
 * @Dataase : 注解在类之上,抽象类,对外提供每张表的操作类
 * entities : 所有的数据库表代表的类
 * version  : 数据库版本,升级数据库需要修改版本号,一般和Migration配合使用
 * exportSchems : 存储展示数据库的结果信息,如果不设置的话,需要在database类上配置 exportSchema = false,要不然编译的时候会出现警告
 */
@Database(entities = {Student.class}, version = 1, exportSchema = false)
public abstract class AppDataBase extends RoomDatabase {

    // 暴露dao
    public abstract StudentDao userDao();

}
  • @Database: 注解在类之上,抽象类,对外提供每张表的操作类
  • entities:所有的数据库表代表的类
  • version:数据库版本,升级数据库需要修改版本好,一般和Migration配合使用
  • exportSchema :存储展示数据库的结构信息,如果不设置的话,需要再database类上配置exportSchema = false,要不然编译的时候会出现警告。

5. 简单使用

MainActivity.java

package com.example.myroom.ui;

import androidx.appcompat.app.AppCompatActivity;
import androidx.room.Room;

import android.os.Bundle;
import android.util.Log;

import com.example.myroom.R;
import com.example.myroom.dao.StudentDao;
import com.example.myroom.db.AppDataBase;
import com.example.myroom.entity.Student;
import com.example.myroom.entity.StudentTuple;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 数据库的操作都放到子进程中执行
        DBTest t = new DBTest();
        t.start();
    }

    public class DBTest extends Thread {
        @Override
        public void run() {
            // 数据库的操作都在这里
            AppDataBase db = Room.databaseBuilder(getApplicationContext(),
                    AppDataBase.class,
                    "MyRoomDB")

                    // 可以设置强制主线程,默认是让你用子线程
                    //.allowMainThreadQueries()

                    .build();

            StudentDao dao = db.userDao();

            // U
            dao.insert(new Student("test1", "111", 1));
            dao.insert(new Student("test2", "222", 2));
            dao.insert(new Student("test3", "333", 3));
            dao.insert(new Student("test4", "444", 4));
            dao.insert(new Student("test5", "555", 5));

            // 查询全部数据
            List<Student> all = dao.getAll();
            Log.d("abc", "run getAll(): all.toString()=" + all.toString());

            // 查询名字为test2的一条数据
            Student test2 = dao.findByName("test2");
            Log.d("abc", "run findByName(): test2.toString()=" + test2.toString());

            // 查询uid为2, 3, 4的三条数据
            List<Student> allId = dao.getAllId(new int[]{2, 3, 4});
            Log.d("abc", "run getAllId(): allId.toString()=" + allId.toString());

            // 查询Student包里面的数据 到 StudentTuple里面去
            StudentTuple record = dao.getRecord();
            Log.d("abc", "run getRecord(): record.toString()=" + record.toString());
        }
    }
}

log输出结果:

2023-08-31 11:52:37.856 3988-4016/com.example.myroom D/abc: run getAll(): all.toString()=[Student{uid=1, name='test1', password='111', address=1}, Student{uid=2, name='test2', password='222', address=2}, Student{uid=3, name='test3', password='333', address=3}, Student{uid=4, name='test4', password='444', address=4}, Student{uid=5, name='test5', password='555', address=5}]
2023-08-31 11:52:37.862 3988-4016/com.example.myroom D/abc: run findByName(): test2.toString()=Student{uid=2, name='test2', password='222', address=2}
2023-08-31 11:52:37.866 3988-4016/com.example.myroom D/abc: run getAllId(): allId.toString()=[Student{uid=2, name='test2', password='222', address=2}, Student{uid=3, name='test3', password='333', address=3}, Student{uid=4, name='test4', password='444', address=4}]
2023-08-31 11:52:37.882 3988-4016/com.example.myroom D/abc: run getRecord(): record.toString()=StudentTuple{name='test1', password='111'} record.toString()=StudentTuple{name='test1', password='111'}

二、ViewModel + LiveData + Room 的结合开发

在这里插入图片描述

项目整体框架结构:
在这里插入图片描述

1. 建表Entity

Student.java

package com.example.myroom2.entity;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity
public class Student {

    @PrimaryKey(autoGenerate = true)
    private int uid;

    @ColumnInfo(name = "name")
    private String name;

    @ColumnInfo(name = "pwd")
    private String password;

    @ColumnInfo(name = "addressId")
    private int addressId;
    
    public Student(int uid, String name, String password, int addressId) {
        this.uid = uid;
        this.name = name;
        this.password = password;
        this.addressId = addressId;
    }

    public Student(String name, String password, int addressId) {
        this.name = name;
        this.password = password;
        this.addressId = addressId;
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAddressId() {
        return addressId;
    }

    public void setAddressId(int addressId) {
        this.addressId = addressId;
    }

    @Override
    public String toString() {
        return "Student{" +
                "uid=" + uid +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", addressId=" + addressId +
                '}';
    }
}

2. 数据库操作类Dao

StudentDao.java

package com.example.myroom2.dao;

import com.example.myroom2.entity.Student;

import java.util.List;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

@Dao
public interface StudentDao {

    @Insert
    void insert(Student... students);

    @Delete
    void delete(Student student);

    @Update
    void update(Student student);

    @Query("select * from Student")
    List<Student> getAll();

    // 使用LiveData 关联Room
    @Query("select * from Student order by uid")
    LiveData<List<Student>> getAllLiveDataStudent();
}

3. 数据库RoomDatabase

package com.example.myroom2.db;

import android.content.Context;

import com.example.myroom2.dao.StudentDao;
import com.example.myroom2.entity.Student;

import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;

// exportSchema = false 尽量写,内部需要检测,如果没有写,会抛出异常,因为内部要记录升级的所有副本
@Database(entities = {Student.class}, version = 2, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {

    private static AppDatabase instance;

    public static synchronized AppDatabase getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(context.getApplicationContext(),
                    AppDatabase.class,
                    "MyRoomDB")
                    // 可以强制在主线程运行数据库操作
                    .allowMainThreadQueries()
                    .build();
        }
        return instance;
    }

    // 暴露dao
    public abstract StudentDao studentDao();
}

4. 仓库Repository

StudentRepository.java

package com.example.myroom2.repository;

import android.content.Context;

import com.example.myroom2.dao.StudentDao;
import com.example.myroom2.db.AppDatabase;
import com.example.myroom2.entity.Student;

import java.util.List;

import androidx.lifecycle.LiveData;

// 仓库
public class StudentRepository {

    private LiveData<List<Student>> liveDataAllStudent;  // 触发 改变的 LiveData的数据
    private StudentDao studentDao;  // 用户操作,只面向Dao

    public StudentRepository(Context context) {
        AppDatabase database = AppDatabase.getInstance(context);
        studentDao = database.studentDao();

        if (liveDataAllStudent == null) {
            liveDataAllStudent = studentDao.getAllLiveDataStudent();  // 使用LiveData关联Room
        }
    }

    // 下面的代码是:提供一些API给ViewModel使用
    // 增
    public void insert(Student... students){
        studentDao.insert(students);
    }

    // 删
    public void delete(Student student) {
        studentDao.delete(student);
    }

    // 改
    public void update(Student student) {
        studentDao.update(student);
    }

    // 查 普通的查询
    public List<Student> getAll() {
        return studentDao.getAll();
    }

    // 查  关联LiveData 暴露环节
    public LiveData<List<Student>> getLiveDataAllStudent() {
        return studentDao.getAllLiveDataStudent();
    }

}

5. ViewModel

StudentViewModel.java

package com.example.myroom2.viewmodel;

import android.app.Application;

import com.example.myroom2.entity.Student;
import com.example.myroom2.repository.StudentRepository;

import java.util.List;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

// AndroidViewModel 保证数据的稳定性
// AndroidViewModel与ViewMOdel的区别是AndroidViewModel多了一个application环境
public class StudentViewModel extends AndroidViewModel {

    private StudentRepository studentRepository;

    public StudentViewModel(@NonNull Application application) {
        super(application);
        studentRepository = new StudentRepository(application);
    }

    // 增
    public void insert(Student... students) {
        studentRepository.insert(students);
    }

    // 删
    public void delete(Student student) {
        studentRepository.delete(student);
    }

    // 改
    public void update(Student student) {
        studentRepository.update(student);
    }

    // 查 普通的查
    public List<Student> getAll() {
        return studentRepository.getAll();
    }

    // 查  关联LiveData
    public LiveData<List<Student>> getAllLiveDataStudent() {
        return studentRepository.getLiveDataAllStudent();
    }
}

6. 数据适配器

这里简单采用ListView来显示列表数据

GoodsAdapter.java

package com.example.myroom2.adapter;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.example.myroom2.R;
import com.example.myroom2.entity.Student;

import java.util.List;

// ListView 的 Adapter
public class GoodsAdapter extends BaseAdapter {
    private LayoutInflater inflater;
    private List<Student> students;

    public GoodsAdapter(Context context, List<Student> students) {
        this.inflater = LayoutInflater.from(context);
        this.students = students;
    }

    @Override
    public int getCount() {
        return students.size();
    }

    @Override
    public Object getItem(int position) {
        return students.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = inflater.inflate(R.layout.item, null);
        Student student = students.get(position);

        TextView tv_id = view.findViewById(R.id.tv_id);
        tv_id.setText("number:" + student.getUid());

        TextView tv_name = view.findViewById(R.id.tv_name);
        tv_name.setText("name:"+student.getName());
        return view;
    }
}

7. UI布局

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/listView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        tools:ignore="MissingConstraints" />

</LinearLayout>

ListView列表中对应的item布局
item.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- 配合 ListView用的 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tv_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#666"
        android:textSize="28sp" />

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dip"
        android:textColor="#666"
        android:textSize="25sp"
        android:layout_marginLeft="30dp"/>

</LinearLayout>


8. Activity的简单运用

package com.example.myroom2.ui;

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;

import android.os.Bundle;
import android.widget.ListView;

import com.example.myroom2.R;
import com.example.myroom2.adapter.GoodsAdapter;
import com.example.myroom2.entity.Student;
import com.example.myroom2.repository.StudentRepository;
import com.example.myroom2.viewmodel.StudentViewModel;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    ListView listView;
    StudentViewModel studentViewModel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.listView);

        // 如果MainViewModel extends ViewModel
        // mainViewModel = new ViewModelProvider(this,
        //      new ViewModelProvider.NewInstanceFactory()).get(MainViewModel.class);

        // 如果MainViewModel extends AndroidViewModel
        studentViewModel = new ViewModelProvider(getViewModelStore(), new ViewModelProvider.AndroidViewModelFactory(getApplication()))
                .get(StudentViewModel.class);

        // 观察者
        studentViewModel.getAllLiveDataStudent().observe(this, new Observer<List<Student>>() {
            @Override
            public void onChanged(List<Student> students) {
                // 更新UI
                listView.setAdapter(new GoodsAdapter(MainActivity.this, students));
            }
        });

        // 模拟 仓库
        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 默认给数据库Room增加数据
                for (int i = 0; i < 50; i++) {
                    studentViewModel.insert(new Student("test", "123", 1));
                }
            }
        }.start();

        // 模拟仓库 数据库数据被修改了,一旦数据库被修改了,那么数据会驱动UI的发生改变
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    studentViewModel.update(new Student(4, "test" + i, "123", 1));
                }
            }
        }.start();
    }
}

三、数据库升级

  往往在实际的项目开发阶段会有这样的需求:后台服务器返回的实体字段信息一天一个样。如果我们想之前的数据信息不被覆盖,但是要把新的字段信息添加到数据库表中,就需要借助Migration了。

1. 新增字段

(1)第一步,在表中添加字段属性,比如在Student表中新增flag属性字段

// 注意:升级增加的字段
@ColumnInfo(name="flag")
private int flag;

Student.java

package com.example.myroom2.entity;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity
public class Student {

    @PrimaryKey(autoGenerate = true)
    private int uid;

    @ColumnInfo(name = "name")
    private String name;

    @ColumnInfo(name = "pwd")
    private String password;

    @ColumnInfo(name = "addressId")
    private int addressId;


    // 注意:升级增加的字段
    @ColumnInfo(name="flag")
    private int flag;

    public int getFlag() {
        return flag;
    }

    public void setFlag(int flag) {
        this.flag = flag;
    }

    public Student(int uid, String name, String password, int addressId) {
        this.uid = uid;
        this.name = name;
        this.password = password;
        this.addressId = addressId;
    }

    public Student(String name, String password, int addressId) {
        this.name = name;
        this.password = password;
        this.addressId = addressId;
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAddressId() {
        return addressId;
    }

    public void setAddressId(int addressId) {
        this.addressId = addressId;
    }

    @Override
    public String toString() {
        return "Student{" +
                "uid=" + uid +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", addressId=" + addressId +
                '}';
    }
}

(2)第二步,修改数据库版本,在RoomDatabase的实现类AppDatabase中修改version版本将之前的1改成2

// exportSchema = false 尽量写,内部需要检测,如果没有写,会抛出异常,因为内部要记录升级的所有副本
@Database(entities = {Student.class}, version = 2, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
	... ...
}

(3) 第三步,新建升级类MIGRATION_1_2,重写migrate()方法,在migrate()方法中进行sql语句操作:

    // 下面是稳定升级的方式
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            // 这里用SQL脚本完成数据的变化
            database.execSQL("alter table Student add column flag integer not null default 1");
        }
    };

(4)第四步,添加数据库升级配置,在Room.databaseBuilder()中进行操作

instance = Room.databaseBuilder(context.getApplicationContext(),
                    AppDatabase.class,
                    "MyRoomDB")
                    // 可以强制在主线程运行数据库操作
                    .allowMainThreadQueries()

                    // 暴力升级 强制执行(数据会丢失 慎用)
                    // .fallbackToDestructiveMigration()

                    // 稳定的方式升级
                    .addMigrations(MIGRATION_1_2)
                    .build();

  其中(2)、(3)、(4)这三个步骤都是在RoomDatabase的实现类AppDatabase中进行操作,代码修改如下:

AppDatabase.java

package com.example.myroom2.db;

import android.content.Context;

import com.example.myroom2.dao.StudentDao;
import com.example.myroom2.entity.Student;

import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;

// exportSchema = false 尽量写,内部需要检测,如果没有写,会抛出异常,因为内部要记录升级的所有副本
@Database(entities = {Student.class}, version = 2, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {

    private static AppDatabase instance;

    public static synchronized AppDatabase getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(context.getApplicationContext(),
                    AppDatabase.class,
                    "MyRoomDB")
                    // 可以强制在主线程运行数据库操作
                    .allowMainThreadQueries()

                    // 暴力升级 强制执行(数据会丢失 慎用)
                    // .fallbackToDestructiveMigration()

                    // 稳定的方式升级
                    .addMigrations(MIGRATION_1_2)
                    .build();
        }
        return instance;
    }

    // 暴露dao
    public abstract StudentDao studentDao();

    // 下面是稳定升级的方式
    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            // 这里用SQL脚本完成数据的变化
            database.execSQL("alter table Student add column flag integer not null default 1");
        }
    };
}

2. 删除字段

  这里要注意Room是不能降级的,我非要删除一个字段,却要保证数据的稳定性,这个是特殊情况,需采用特殊手法进行降级。

  这个相对来说,比新增字段要复杂的多。比如我在上面的基础上再把flag字段删除掉
(1)第一步,将实体类中的flag字段删掉
(2)第二步,修改数据库版本,在RoomDatabase的实现类AppDatabase中修改version版本由原来的2修改为3。
(3)第三步,新建升级类MIGRATION_2_3,重写migrate()方法,在migrate方法中进行sql语句操作。

    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            // SQL 四步法
            // 1.先建立临时表
            database.execSQL("create table student_temp (uid integer primary key not null, name text, pwd text, addressId integer)");

            // 2.把之前表的数据插入到新表中
            database.execSQL("insert into student_temp (uid, name, pwd, addressId) " + "select uid, name, pwd, addressid from student");

            // 3.删除student 旧表
            database.execSQL("drop table student");

            // 4.修改 临时表为新表student
            database.execSQL("alter table student_temp rename to student");
        }
    };

其实这一步可以新建升级类MIGRATION_2_3继承Migration

public class MIGRATION_2_3 extends Migration {

    /**
     * 这个版本2要和对应的数据库版本对应
     */
    public MIGRATION_2_3() {
        super(2, 3);
    }
    @Override
    public void migrate(@NonNull SupportSQLiteDatabase database) {

        //TODO 1、创建一个备份表
        database.execSQL("create table student_temp (uid integer primary key not null, name text, pwd text, addressId integer)");

        //TODO 2、把数据拷贝到备份表中
        database.execSQL("insert into student_temp (uid, name, pwd, addressId) " + "select uid, name, pwd, addressid from student");

        //TODO 3、删除旧表
        database.execSQL("drop table student");

        //TODO 4、将备份表名称改为原来的表名称
        database.execSQL("alter table student_temp rename to student");

    }
}

注意,构造方法中版本是从2到3,需要在migrate方法中执行4个步骤才能将age字段移除。

  • 创建备份表,包含所有字段,除了age字段
  • 把原始数据拷贝到新的备份表中
  • 删除原始表
  • 修改备份表名称为原始表名称

(4)第四步,添加数据库升级配置,在Room.databaseBuilder中进行操作

instance = Room.databaseBuilder(context.getApplicationContext(),
                    AppDatabase.class,
                    "MyRoomDB")
                    // 可以强制在主线程运行数据库操作
                    .allowMainThreadQueries()

                    // 暴力升级 强制执行(数据会丢失 慎用)
                    // .fallbackToDestructiveMigration()

                    // 稳定的方式升级
                    .addMigrations(MIGRATION_2_3)
                    .build();

四. Room的源码原理了解

APT注解处理器生成代码
在这里插入图片描述
AppDataBase_Impl.java

package com.example.myroom.db;

import androidx.room.DatabaseConfiguration;
import androidx.room.InvalidationTracker;
import androidx.room.RoomOpenHelper;
import androidx.room.RoomOpenHelper.Delegate;
import androidx.room.util.TableInfo;
import androidx.room.util.TableInfo.Column;
import androidx.room.util.TableInfo.ForeignKey;
import androidx.room.util.TableInfo.Index;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
import androidx.sqlite.db.SupportSQLiteOpenHelper.Callback;
import androidx.sqlite.db.SupportSQLiteOpenHelper.Configuration;
import com.example.myroom.dao.StudentDao;
import com.example.myroom.dao.StudentDao_Impl;
import java.lang.IllegalStateException;
import java.lang.Override;
import java.lang.String;
import java.lang.SuppressWarnings;
import java.util.HashMap;
import java.util.HashSet;

@SuppressWarnings("unchecked")
public final class AppDataBase_Impl extends AppDataBase {
  private volatile StudentDao _studentDao;

  @Override
  protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
    final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
      @Override
      public void createAllTables(SupportSQLiteDatabase _db) {
        _db.execSQL("CREATE TABLE IF NOT EXISTS `Student` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `pwd` TEXT, `address` INTEGER NOT NULL)");
        _db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
        _db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"228d349e23ba02cda67e8bc5ffa53bf0\")");
      }

      @Override
      public void dropAllTables(SupportSQLiteDatabase _db) {
        _db.execSQL("DROP TABLE IF EXISTS `Student`");
      }

      @Override
      protected void onCreate(SupportSQLiteDatabase _db) {
        if (mCallbacks != null) {
          for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
            mCallbacks.get(_i).onCreate(_db);
          }
        }
      }

      @Override
      public void onOpen(SupportSQLiteDatabase _db) {
        mDatabase = _db;
        internalInitInvalidationTracker(_db);
        if (mCallbacks != null) {
          for (int _i = 0, _size = mCallbacks.size(); _i < _size; _i++) {
            mCallbacks.get(_i).onOpen(_db);
          }
        }
      }

      @Override
      protected void validateMigration(SupportSQLiteDatabase _db) {
        final HashMap<String, TableInfo.Column> _columnsStudent = new HashMap<String, TableInfo.Column>(4);
        _columnsStudent.put("uid", new TableInfo.Column("uid", "INTEGER", true, 1));
        _columnsStudent.put("name", new TableInfo.Column("name", "TEXT", false, 0));
        _columnsStudent.put("pwd", new TableInfo.Column("pwd", "TEXT", false, 0));
        _columnsStudent.put("address", new TableInfo.Column("address", "INTEGER", true, 0));
        final HashSet<TableInfo.ForeignKey> _foreignKeysStudent = new HashSet<TableInfo.ForeignKey>(0);
        final HashSet<TableInfo.Index> _indicesStudent = new HashSet<TableInfo.Index>(0);
        final TableInfo _infoStudent = new TableInfo("Student", _columnsStudent, _foreignKeysStudent, _indicesStudent);
        final TableInfo _existingStudent = TableInfo.read(_db, "Student");
        if (! _infoStudent.equals(_existingStudent)) {
          throw new IllegalStateException("Migration didn't properly handle Student(com.example.myroom.entity.Student).\n"
                  + " Expected:\n" + _infoStudent + "\n"
                  + " Found:\n" + _existingStudent);
        }
      }
    }, "228d349e23ba02cda67e8bc5ffa53bf0", "7b5888a424cc9ab1174dfe15e25efa2c");
    final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
        .name(configuration.name)
        .callback(_openCallback)
        .build();
    final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
    return _helper;
  }

  @Override
  protected InvalidationTracker createInvalidationTracker() {
    return new InvalidationTracker(this, "Student");
  }

  @Override
  public void clearAllTables() {
    super.assertNotMainThread();
    final SupportSQLiteDatabase _db = super.getOpenHelper().getWritableDatabase();
    try {
      super.beginTransaction();
      _db.execSQL("DELETE FROM `Student`");
      super.setTransactionSuccessful();
    } finally {
      super.endTransaction();
      _db.query("PRAGMA wal_checkpoint(FULL)").close();
      if (!_db.inTransaction()) {
        _db.execSQL("VACUUM");
      }
    }
  }

  @Override
  public StudentDao userDao() {
    if (_studentDao != null) {
      return _studentDao;
    } else {
      synchronized(this) {
        if(_studentDao == null) {
          _studentDao = new StudentDao_Impl(this);
        }
        return _studentDao;
      }
    }
  }
}

StudentDao_Impl.java

package com.example.myroom.dao;

import android.database.Cursor;
import androidx.room.EntityDeletionOrUpdateAdapter;
import androidx.room.EntityInsertionAdapter;
import androidx.room.RoomDatabase;
import androidx.room.RoomSQLiteQuery;
import androidx.room.util.StringUtil;
import androidx.sqlite.db.SupportSQLiteStatement;
import com.example.myroom.entity.Student;
import com.example.myroom.entity.StudentTuple;
import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.lang.SuppressWarnings;
import java.util.ArrayList;
import java.util.List;

@SuppressWarnings("unchecked")
public final class StudentDao_Impl implements StudentDao {
  private final RoomDatabase __db;

  private final EntityInsertionAdapter __insertionAdapterOfStudent;

  private final EntityDeletionOrUpdateAdapter __deletionAdapterOfStudent;

  private final EntityDeletionOrUpdateAdapter __updateAdapterOfStudent;

  public StudentDao_Impl(RoomDatabase __db) {
    this.__db = __db;
    this.__insertionAdapterOfStudent = new EntityInsertionAdapter<Student>(__db) {
      @Override
      public String createQuery() {
        return "INSERT OR ABORT INTO `Student`(`uid`,`name`,`pwd`,`address`) VALUES (nullif(?, 0),?,?,?)";
      }

      @Override
      public void bind(SupportSQLiteStatement stmt, Student value) {
        stmt.bindLong(1, value.getUid());
        if (value.getName() == null) {
          stmt.bindNull(2);
        } else {
          stmt.bindString(2, value.getName());
        }
        if (value.getPassword() == null) {
          stmt.bindNull(3);
        } else {
          stmt.bindString(3, value.getPassword());
        }
        stmt.bindLong(4, value.getAddress());
      }
    };
    this.__deletionAdapterOfStudent = new EntityDeletionOrUpdateAdapter<Student>(__db) {
      @Override
      public String createQuery() {
        return "DELETE FROM `Student` WHERE `uid` = ?";
      }

      @Override
      public void bind(SupportSQLiteStatement stmt, Student value) {
        stmt.bindLong(1, value.getUid());
      }
    };
    this.__updateAdapterOfStudent = new EntityDeletionOrUpdateAdapter<Student>(__db) {
      @Override
      public String createQuery() {
        return "UPDATE OR ABORT `Student` SET `uid` = ?,`name` = ?,`pwd` = ?,`address` = ? WHERE `uid` = ?";
      }

      @Override
      public void bind(SupportSQLiteStatement stmt, Student value) {
        stmt.bindLong(1, value.getUid());
        if (value.getName() == null) {
          stmt.bindNull(2);
        } else {
          stmt.bindString(2, value.getName());
        }
        if (value.getPassword() == null) {
          stmt.bindNull(3);
        } else {
          stmt.bindString(3, value.getPassword());
        }
        stmt.bindLong(4, value.getAddress());
        stmt.bindLong(5, value.getUid());
      }
    };
  }

  @Override
  public void insert(Student... students) {
    __db.beginTransaction();
    try {
      __insertionAdapterOfStudent.insert(students);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public void delete(Student student) {
    __db.beginTransaction();
    try {
      __deletionAdapterOfStudent.handle(student);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public void update(Student student) {
    __db.beginTransaction();
    try {
      __updateAdapterOfStudent.handle(student);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }

  @Override
  public List<Student> getAll() {
    final String _sql = "select * from Student";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    final Cursor _cursor = __db.query(_statement);
    try {
      final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
      final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
      final int _cursorIndexOfPassword = _cursor.getColumnIndexOrThrow("pwd");
      final int _cursorIndexOfAddress = _cursor.getColumnIndexOrThrow("address");
      final List<Student> _result = new ArrayList<Student>(_cursor.getCount());
      while(_cursor.moveToNext()) {
        final Student _item;
        final String _tmpName;
        _tmpName = _cursor.getString(_cursorIndexOfName);
        final String _tmpPassword;
        _tmpPassword = _cursor.getString(_cursorIndexOfPassword);
        final int _tmpAddress;
        _tmpAddress = _cursor.getInt(_cursorIndexOfAddress);
        _item = new Student(_tmpName,_tmpPassword,_tmpAddress);
        final int _tmpUid;
        _tmpUid = _cursor.getInt(_cursorIndexOfUid);
        _item.setUid(_tmpUid);
        _result.add(_item);
      }
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }

  @Override
  public Student findByName(String name) {
    final String _sql = "select * from Student where name like?";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
    int _argIndex = 1;
    if (name == null) {
      _statement.bindNull(_argIndex);
    } else {
      _statement.bindString(_argIndex, name);
    }
    final Cursor _cursor = __db.query(_statement);
    try {
      final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
      final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
      final int _cursorIndexOfPassword = _cursor.getColumnIndexOrThrow("pwd");
      final int _cursorIndexOfAddress = _cursor.getColumnIndexOrThrow("address");
      final Student _result;
      if(_cursor.moveToFirst()) {
        final String _tmpName;
        _tmpName = _cursor.getString(_cursorIndexOfName);
        final String _tmpPassword;
        _tmpPassword = _cursor.getString(_cursorIndexOfPassword);
        final int _tmpAddress;
        _tmpAddress = _cursor.getInt(_cursorIndexOfAddress);
        _result = new Student(_tmpName,_tmpPassword,_tmpAddress);
        final int _tmpUid;
        _tmpUid = _cursor.getInt(_cursorIndexOfUid);
        _result.setUid(_tmpUid);
      } else {
        _result = null;
      }
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }

  @Override
  public List<Student> getAllId(int[] userIds) {
    StringBuilder _stringBuilder = StringUtil.newStringBuilder();
    _stringBuilder.append("select * from Student where uid in(");
    final int _inputSize = userIds.length;
    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
    _stringBuilder.append(")");
    final String _sql = _stringBuilder.toString();
    final int _argCount = 0 + _inputSize;
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, _argCount);
    int _argIndex = 1;
    for (int _item : userIds) {
      _statement.bindLong(_argIndex, _item);
      _argIndex ++;
    }
    final Cursor _cursor = __db.query(_statement);
    try {
      final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid");
      final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
      final int _cursorIndexOfPassword = _cursor.getColumnIndexOrThrow("pwd");
      final int _cursorIndexOfAddress = _cursor.getColumnIndexOrThrow("address");
      final List<Student> _result = new ArrayList<Student>(_cursor.getCount());
      while(_cursor.moveToNext()) {
        final Student _item_1;
        final String _tmpName;
        _tmpName = _cursor.getString(_cursorIndexOfName);
        final String _tmpPassword;
        _tmpPassword = _cursor.getString(_cursorIndexOfPassword);
        final int _tmpAddress;
        _tmpAddress = _cursor.getInt(_cursorIndexOfAddress);
        _item_1 = new Student(_tmpName,_tmpPassword,_tmpAddress);
        final int _tmpUid;
        _tmpUid = _cursor.getInt(_cursorIndexOfUid);
        _item_1.setUid(_tmpUid);
        _result.add(_item_1);
      }
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }

  @Override
  public StudentTuple getRecord() {
    final String _sql = "select name, pwd from Student";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    final Cursor _cursor = __db.query(_statement);
    try {
      final int _cursorIndexOfName = _cursor.getColumnIndexOrThrow("name");
      final int _cursorIndexOfPassword = _cursor.getColumnIndexOrThrow("pwd");
      final StudentTuple _result;
      if(_cursor.moveToFirst()) {
        final String _tmpName;
        _tmpName = _cursor.getString(_cursorIndexOfName);
        final String _tmpPassword;
        _tmpPassword = _cursor.getString(_cursorIndexOfPassword);
        _result = new StudentTuple(_tmpName,_tmpPassword);
      } else {
        _result = null;
      }
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值