Jetpack学习之----ROOM

介绍

官方学习文档

Jetpack 的ROOM就是在android支持的SQLite的基础上进行了一个抽象的封装,这样方便用户更好的利用SQLite的强大功能,进行更加强健的数据库访问机制。


ROOM 是以注解的方式在编译期帮助我们生成很多代码,这样就可以将我们的重心放在业务逻辑的处理上了。

笔者对ROOM的理解:

通过注解的方式将数据库操作、表操作等比较繁琐的工作交给注解处理器工作,我们只需要创建数据模型和对数据库的操作即可。


ROOM的三大组件

1) DataBase: 他所标记的类就是一个数据库类,

2) Entity:表示数据库中的表

3) Dao:表示一个可以对数据库进行操作(增删改查)的接口规范(不包括对数据库的创建)

基本使用

1、引入依赖库

    def room_version = "2.3.0"

    compile "android.arch.persistence.room:runtime:$room_version"
    kapt "android.arch.persistence.room:compiler:$room_version"

    //如果您已迁移到androidx
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

2、数据库

数据库的操作不允许允许在主线程中,但是可以通过配置使其在主线程中允许(allowMainThreadQueries)

  1. 数据库使用@Database注解,必须继承RoomDatabase ,而且是一个抽象类,数据库用于提供表操作Dao实例对象的获取,这里都是抽象方法,编译时,APT会生成其相关的实现类
  2. entities 是指数据库中的表的类class,如果有多个表,则用逗号隔开即可
  3. version 代表数据库的版本号,用于数据库升级,迁移等使用
  4. exportSchema 需要指明是否导出到文件中去
@Database(entities = {Student.class, FamilyAddress.class}, version = 1, exportSchema = true)
public abstract class StudentDataBase extends RoomDatabase {

    private static StudentDataBase mInstance;

    //单例
    public static synchronized StudentDataBase getInstance(Context context) {
        if (mInstance == null) {
            synchronized (StudentDataBase.class) {
                if (mInstance == null) {
                    mInstance = Room.databaseBuilder(context.getApplicationContext(),//应用上下文
                            StudentDataBase.class,//数据库类型
                            "StudentDataBse")//数据库的名字
                            .allowMainThreadQueries()//允许在主线程中允许
                            .build();
                }
            }
        }
        return mInstance;
    }

    //获取dao的实例对象
    public abstract StudentDao getStudentDao();
}

2.1、数据库强制升级(不建议)

在创建数据库实例对象的build()之前,调用fallbackToDestructiveMigration(),但是这种方式不建议使用,因为这种强制升级会导致数据库的结构发生变化,并且会导致数据库中的数据全部丢失。

  public static synchronized StudentDataBase getInstance(Context context) {
        if (mInstance == null) {
            synchronized (StudentDataBase.class) {
                if (mInstance == null) {
                    mInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDataBase.class,
                            "StudentDataBse")
                            .fallbackToDestructiveMigration()//强制升级
                            .build();
                }
            }
        }
        return mInstance;
    }

2.2、MIGRATION保留数据升级

1)创建MIGRATION变量(需要依次传入两个版本的版本号)

在给表增加或者修改列信息时,同时也需要将@Entity对应的实体进行修改

    private static Migration MIGRATION_1_to_2 = new Migration(1,2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            //这里通过SQL语句来进行升级,下面是给表增加列信息的升级
            database.execSQL("ALTER TABLE Student ADD COLUMN sex CHAR NOT NULL DEFAULT 'm'");
        }
    };
  1. 添加MIGRATION变量
 public static synchronized StudentDataBase getInstance(Context context) {
        if (mInstance == null) {
            synchronized (StudentDataBase.class) {
                if (mInstance == null) {
                    mInstance = Room.databaseBuilder(context.getApplicationContext(),
                            StudentDataBase.class,
                            "StudentDataBse")
                            .addMigrations(MIGRATION_1_to_2)
                            .build();
                }
            }
        }
        return mInstance;
    }

3)修改原数据库版本信息

//这里需要将version的值由1 修改到 2
@Database(entities = {Student.class, FamilyAddress.class}, version = 2, exportSchema = true)

2.3、 数据库迁移

数据库迁移的思路:

创建新的数据库,并将表结构及其数据复制到新的数据库中去,删除原数据库,重命名新数据库为原数据库名,这样就达到数据库迁移的目标了。

3、表结构(实体)

  1. @Entity表示数据库的表结构;
  2. @PrimaryKey 表示是一个主键,参数autoGenerate 为true时,表示主键是自动生成的,这里不能给默认值;
  3. @ColumnInfo 表示表中的一个属性列信息,参数name 就是标的列的名称,与定义的变量名一致;
  4. @Embedded 对象注解
  5. @Ignore 忽略注解,用在方法或字段上;
@Entity
public class Student {

    @PrimaryKey(autoGenerate = true)
    private int id;

    @Ignore
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }

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

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

    public Student(String name, int age, char sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    @ColumnInfo(name = "sex")
    private char sex;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


@Entity
public class Teacher {

    public int getTeacherId() {
        return teacherId;
    }

    public void setTeacherId(int teacherId) {
        this.teacherId = teacherId;
    }

    @PrimaryKey(autoGenerate = true)
    private int teacherId;

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

    public Teacher(String name) {
        this.name = name;

    }

    public String getName() {
        return name;
    }

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

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

}

4、Dao层

Dao层其实就是定义一套操作数据库的标准(增删改查等),这样编译器编译生成Dao层的实例类,对数据库的操作就可以直接使用Dao层对象操作了。

4.1、 使用@Dao注解来表示Dao层,且是一个接口

@Dao
public interface StudentDao {

    //插入
    @Insert
    void insert(Student student);

    //删除
    @Delete
    void delete(Student student);

    //修改
    @Update
    void update(Student student);

    //查询
    @Query("select * from Student")
    List getAll();
    }

4.2、 条件查询:

1)方法的参数name 就是在SQL语句like :冒号后面的参数name

2)多个参数查询时使用and来连接参数,每个参数使用like+冒号+参数名

3)集合查询使用 in (: 参数名)

 @Query("select * from Student where name like :name and age like :age")
    Student queryStudent(String name,int age);

    //集合查询
    @Query("select * from Student where name in (:userNames)")
    List queryStudents(List userNames);

4.3、字段查询

查询的结果需要重新定义一个数据结构来存放。

    @Query("select name ,age From Student")
    List queryFields();

4.4、多表查询

1) 多表查询,如果表中的有字段是重复时,需要使用as来指定对应关系

2) 多表查询的结果需要保存在一个新的实体中

    @Query("select Student.name as name ,Student.age as age ,Teacher.name as teacherName from student,teacher")
    List queryMutilTable();

5、使用:

5.1、获取数据库Dao实例对象

 StudentDao studentDB;
 RoomThread roomThread;

 studentDB = StudentDataBase.getInstance(RoomMainActivity.this).getStudentDao();

 roomThread = new RoomThread();

5.2、创建子线程

    class RoomThread extends Thread {
        @Override
        public void run() {
            switch (type) {
                case 0:
                    insertDataBase();
                    break;
                case 1:
                    queryAll();
                    break;
                case 2:
                    querySingle();
                    break;
                case 3:
                    queryMutil();
                    break;
                case 4:
                    queryMutilTable();
                    break;
                case 5:
                    queryFields();
                    break;
                case 6:
                    insertDataBase();
                    break;
                default:
                    break;
            }
        }
    }

5.3、调用Dao的方法操作数据库

 private void queryFields() {
        List partFieldResults = studentDB.queryFields();
        Log.e(TAG, "字段查询:\n" + partFieldResults.toString());

        Message message = new Message();
        message.obj = "\n\n\n字段查询:\n" + partFieldResults.toString();
        mHandler.sendMessage(message);
    }

    private void queryMutilTable() {
        List queryMutilTable = studentDB.queryMutilTable();
        Log.e(TAG, "多表查询:\n" + queryMutilTable.toString());

        Message message = new Message();
        message.obj = "\n\n\n多表查询:\n" + queryMutilTable.toString();
        mHandler.sendMessage(message);
    }

    void queryMutil() {
        List temp = new ArrayList();
        temp.add("张三");
        temp.add("王五");

        List students = studentDB.queryStudents(temp);
        Log.e(TAG, "多条件查询:\n" + students.toString());

        Message message = new Message();
        message.obj = "\n\n\n多条件查询:\n" + students.toString();
        mHandler.sendMessage(message);
    }

    void querySingle() {
        Student student = studentDB.queryStudent("马六",15);
        Log.e(TAG, "单条件查询:\n" + student.toString());
        Message message = new Message();
        message.obj = "\n\n\n单条件查询:\n" + student.toString();
        mHandler.sendMessage(message);
    }

    void queryAll() {
        List all = studentDB.getAll();
        Log.e(TAG, "查询全部:\n" + all);
        Message message = new Message();
        message.obj = "查询全部:\n" + all;
        mHandler.sendMessage(message);
    }

    void insertDataBase() {
        studentDB.insert(new Student("张三", 12));
        studentDB.insert(new Student("李四", 13));
        studentDB.insert(new Student("王五", 14));
        studentDB.insert(new Student("马六", 15));
    }

原理分析

在编译期通过注解处理器生成对应的Dao层和抽象数据库的实例类,

当开始调用Room的build()时,通过反射技术反射数据库实现类的实例对象,同时创建对应的数据库表;

当通过数据库实例对象获取Dao的实例对象时,就会进行数据库表结构的初始化工作,

至此数据库及表结构就都已经创建好了,接下来通过Dao实例对象调用对应的接口标准进行相应的数据库操作。

1、RoomDataBase类的build()

工作:新建创建数据库的工厂,准备数据库需要的相关配置信息等。

 public T build() {
           
     //下面都是创建对应的创建数据库的工厂
            SupportSQLiteOpenHelper.Factory factory;
            if (mFactory == null) {
                factory = new FrameworkSQLiteOpenHelperFactory();
            } else {
                factory = mFactory;
            }

            if (mAutoCloseTimeout > 0) {
                factory = new AutoClosingRoomOpenHelperFactory(factory, autoCloser);
            }
            if (mCopyFromAssetPath != null
                    || mCopyFromFile != null
                    || mCopyFromInputStream != null) {
                factory = new SQLiteCopyOpenHelperFactory(mCopyFromAssetPath, mCopyFromFile,
                        mCopyFromInputStream, factory);
            }

            if (mQueryCallback != null) {
                factory = new QueryInterceptorOpenHelperFactory(factory, mQueryCallback,
                        mQueryCallbackExecutor);
            }
     
        
//配置创建数据库对应的参数
            DatabaseConfiguration configuration =
                    new DatabaseConfiguration(
                            mContext,
                            mName,
                            factory,
                            mMigrationContainer,
                            mCallbacks,
                            mAllowMainThreadQueries,
                            mJournalMode.resolve(mContext),
                            mQueryExecutor,
                            mTransactionExecutor,
                            mMultiInstanceInvalidation,
                            mRequireMigration,
                            mAllowDestructiveMigrationOnDowngrade,
                            mMigrationsNotRequiredFrom,
                            mCopyFromAssetPath,
                            mCopyFromFile,
                            mCopyFromInputStream,
                            mPrepackagedDatabaseCallback,
                            mTypeConverters);
        //下面是反射获取生成的数据库实例
            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
        
     //对数据库进行配置
            db.init(configuration);
            return db;
        }
    }

2、Room类的getGeneratedImplementation()

通过反射技术反射出来数据库实现类的实例对象

static  T getGeneratedImplementation(Class klass, String suffix) {
        final String fullPackage = klass.getPackage().getName();
        String name = klass.getCanonicalName();
        final String postPackageName = fullPackage.isEmpty()
                ? name
                : name.substring(fullPackage.length() + 1);
        final String implName = postPackageName.replace('.', '_') + suffix;
        //noinspection TryWithIdenticalCatches
        try {
            //反射获取抽象数据库类的实例对象
            final String fullClassName = fullPackage.isEmpty()
                    ? implName
                    : fullPackage + "." + implName;
            @SuppressWarnings("unchecked")
            final Class aClass = (Class) Class.forName(
                    fullClassName, true, klass.getClassLoader());
            return aClass.newInstance();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("cannot find implementation for "
                    + klass.getCanonicalName() + ". " + implName + " does not exist");
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot access the constructor"
                    + klass.getCanonicalName());
        } catch (InstantiationException e) {
            throw new RuntimeException("Failed to create an instance of "
                    + klass.getCanonicalName());
        }
    }

3、Dao层的实现类

实现数据库的操作:表结构初始化、数据库操作SQL调用执行等。

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(__db) {
      @Override
      public String createQuery() {
        return "INSERT OR ABORT INTO `Student` (`id`,`teachId`,`name`,`age`,`sex`) VALUES (nullif(?, 0),?,?,?,?)";
      }

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

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

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

  @Override
  public void update(final Student student) {
    __db.assertNotSuspendingTransaction();
    __db.beginTransaction();
    try {
      __updateAdapterOfStudent.handle(student);
      __db.setTransactionSuccessful();
    } finally {
      __db.endTransaction();
    }
  }
//其他的方法省略
}

4、数据库抽象类的实现类

1)创建数据库,包括数据库中的表

2)创建Dao层的实例对象

public final class StudentDataBase_Impl extends StudentDataBase {
  private volatile StudentDao _studentDao;

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

总结

1、技术点:

反射+APT+单例模式+工厂模式

2、ROOM 坑:

在使用Room时,会出现如下的错误:

java.lang.RuntimeException: cannot find implementation for com.xx.xx.db.xxDatabase. xxDatabase_Impl does not exist

解决方法:

    def room_version = "2.3.0"

    compile "android.arch.persistence.room:runtime:$room_version"
    kapt "android.arch.persistence.room:compiler:$room_version"

    //如果您已迁移到androidx
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

切记:一定要在所有使用到room的module中都添加一下“ kapt "androidx.room:room-compiler:$room_version"

3、遗留问题

1)关于外键注解的使用???

2)@Embedded 注解的作用????

你可能感兴趣的:(Jetpack学习之----ROOM)