AndroidandroidAndroid开发经验谈

快速掌握Room数据库框架(附Demo)

2018-04-18  本文已影响886人  af83084249b7

介绍

安卓开发个各位小伙伴,或多或少的都会用到数据库框架。为了帮助支持各位开发者,google推出了自己的数据库框架Room。

定义

官方介绍:The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
翻译过来就是,Room持久型类库在SQLite的基础上提供了一个抽象层,方便大家流利的访问数据库。并且,利用了SQlite的全部强力功能。
好吧,说的很流弊。其实,也真的是很流弊。使用起来特别nice。

Demo效果(CURD还是很快的)

主要动作有:

插入一条、
插入多条、
更新记录通过主键index匹配、
查找一条记录通过主键index匹配、
查找所有的记录、
删除一条记录通过主键index匹配、
删除所有记录。


roomgif1.gif

引用

首先我们需要在项目的build.gradlle文件添加google的maven仓库

allprojects {
repositories {
    jcenter()
    //添加google
    google()
 }
 }

接着在主module的build.gradle文件添加依赖

implementation 'android.arch.persistence.room:runtime:1.0.0'
annotationProcessor 'android.arch.persistence.room:compiler:1.0.0'
//添加测试支持,我们可以对数据库进行androidTest(后面会介绍)
implementation 'android.arch.persistence.room:testing:1.0.0'

概念

Room设计到的概念有以下几个:

Entity:具体的bean实体,会与数据库表column进行映射
Dao:数据库访问对象,实现具体的增删改查。
RoomDatabase:提供直接访问底层数据库实现,我们应该从里面拿到具体的Dao,进行数据库操作。

了解上面的具体概念,我们就可以进行开发了,下面是具体细节处理。

配置过程

首先我们要创建具体的实体Entity

 //entity声明定义,并且指定了映射数据表明
 @Entity(tableName = "user")
 public class User {
 //设置主键,并且定义自增增
 @PrimaryKey(autoGenerate = true)
 //字段映射具体的数据表字段名
 @ColumnInfo(name = "uid")
 private int uid;
 //字段映射具体的数据表字段名
 @ColumnInfo(name = "first_name")
 private String firstName;
 //字段映射具体的数据表字段名
 @ColumnInfo(name = "last_name")
 private String lastName;

//必须指定一个构造方法,room框架需要。并且只能指定一个
//,如果有其他构造方法,则其他的构造方法必须添加@Ignore注解
 public User() {
 }
 //其他构造方法要添加@Ignore注解
 @Ignore
 public User(int uid) {
    this.uid = uid;
 }
//Setter、Getter方法是需要添加的,为了支持room工作
 public int getUid() {
    return uid;
 }

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

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

public String getLastName() {
    return lastName;
}

 public void setLastName(String lastName) {
    this.lastName = lastName;
 }

接着我们要创建具体的Dao

//注解配置sql语句
@Dao
public interface UserDao {
//所有的CURD根据primary key进行匹配
//------------------------query------------------------
//简单sql语句,查询user表所有的column
@Query("SELECT * FROM user")
List<User> getAll();

//根据条件查询,方法参数和注解的sql语句参数一一对应
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);

//同上
@Query("SELECT * FROM user WHERE first_name LIKE :first AND "
        + "last_name LIKE :last LIMIT 1")
User findByName(String first, String last);

//同上
@Query("SELECT * FROM user WHERE uid = :uid")
User findByUid(int uid);

//-----------------------insert----------------------

// OnConflictStrategy.REPLACE表示如果已经有数据,那么就覆盖掉
//数据的判断通过主键进行匹配,也就是uid,非整个user对象
//返回Long数据表示,插入条目的主键值(uid)
@Insert(onConflict = OnConflictStrategy.REPLACE)
Long insert(User user);

  //返回List<Long>数据表示被插入数据的主键uid列表
 @Insert(onConflict = OnConflictStrategy.REPLACE)
 List<Long> insertAll(User... users);
//返回List<Long>数据表示被插入数据的主键uid列表
@Insert(onConflict = OnConflictStrategy.REPLACE)
List<Long> insertAll(List<User> users);

//---------------------update------------------------
//更新已有数据,根据主键(uid)匹配,而非整个user对象
//返回类型int代表更新的条目数目,而非主键uid的值。
//表示更新了多少条目
@Update()
int update(User user);
//同上
@Update()
int updateAll(User... user);
//同上
@Update()
int updateAll(List<User> user);

//-------------------delete-------------------
//删除user数据,数据的匹配通过主键uid实现。
//返回int数据表示删除了多少条。非主键uid值。
@Delete
int delete(User user);
//同上
@Delete
int deleteAll(List<User> users);
//同上
@Delete
int deleteAll(User... users);
}

然后自定义类继承RoomDatabase

//注解指定了database的表映射实体数据以及版本等信息
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
//RoomDatabase提供直接访问底层数据库实现,我们通过定义抽象方法返回具体Dao
//然后进行数据库增删该查的实现。
 public abstract UserDao userDao();
 }

最后创建对象并访问数据库

  //得到AppDatabase 对象
  AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "roomDemo-database")
            //下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
            //他可能造成主线程lock以及anr
            //所以我们的操作都是在新线程完成的
            // .allowMainThreadQueries()
            .build();
    //得到userDao对象
    mUserDao = db.userDao();
    
    //具体操作,非重点代码,不再展示细节(只展示 insertOne()),下载demo了解整个体系
     public void onClick(View view) {
    switch (view.getId()) {
        case R.id.insert_one:
            insertOne();
            break;
        case R.id.insert_some:
            insertSome();
            break;
        case R.id.update_one:
            updataOne();
            break;
        case R.id.delete_one:
            deleteOne();
            break;
        case R.id.find_one:
            findOne();
            break;
        case R.id.find_all:
            findAll();
            break;
        case R.id.delete_all:
            deleteAll();
            break;
      }
    }    
    //非核心代码
    private void insertOne() {
    //防止UI线程阻塞以及ANR,所有的数据库操作,推荐开启新的线程访问。
    new Thread(new Runnable() {
        @Override
        public void run() {
            //返回的是插入元素的primary key index
            Long aLong = mUserDao.insert(new User("t" + System.currentTimeMillis() / 1000, "allen"));
            if (aLong > 0) {
                String msg = "insert one success, index is " + aLong.toString();
                mBuffer.append(msg + "\n");
                Log.i(TAG, msg);
            } else {
                String msg = "insert one fail ";
                mBuffer.append(msg + "\n");
                Log.i(TAG, msg);
            }
            MainActivity.this.setMsg();
        }
    }).start();
  }

-------------------------------你已经掌握基本的增删改查--------------------------------

那么,我们还需要掌握数据库的更新。毕竟,我们的数据库表以及结构不是一成不变的。

更新数据库

首先,我们需要提升数据库版本号并定义Migration对象,指明数据库的变动迁移。

//指定version = 2(之前为1)
@Database(entities = {User.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();

//数据库变动添加Migration
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        //数据库的具体变动,我是在之前的user表中添加了新的column,名字是age。
        //类型是integer,不为空,默认值是0
        database.execSQL("ALTER TABLE user "
                + " ADD COLUMN age INTEGER NOT NULL DEFAULT 0");
    }
};
}

接着,在数据库对象创建时候添加该Migration对象。

    AppDatabase db = Room.databaseBuilder(getApplicationContext(),
            AppDatabase.class, "roomDemo-database")
            //添加数据库变动迁移
            .addMigrations(AppDatabase.MIGRATION_1_2)
            //下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
            //他可能造成主线程lock以及anr
            // .allowMainThreadQueries()
            .build();

-----------------------------你已经完成了数据库的迁移-----------------------------

任何版本的数据库sql以及迁移,我们都需要记录下来。这样,我们数据库就有了一个完整的历史体系。后续也容易修改和维护。怎么记录呢?电子笔记?Out啦!

记录数据库sql

在主module的build.gradle里面添加如下代码

    defaultConfig {
    ...
    javaCompileOptions {
        annotationProcessorOptions {
            //room的数据库概要、记录
            arguments = ["room.schemaLocation":
                                 "$projectDir/schemas".toString()]
        }
    }
}     
sourceSets {
    //数据库概要、记录存放位置
    androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}

配置完毕之后,我们在构建项目之后会在对应路径生成schemas文件夹。


TIM图片20180417220107.png

1.json和2.json分别是数据库版本1、2对应的概要、记录(json文件格式存储)

1.json文件数据如下:
{
"formatVersion": 1,
"database": {
 "version": 1,
  //身份哈希
 "identityHash": "7fcc959eb0e5d9c2cd52cf58f7a05392",
"entities": [
  {
    "tableName": "user",
     //建表语句
    "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_name` TEXT, `last_name` TEXT)",
    "fields": [
      {
        "fieldPath": "uid",
        "columnName": "uid",
        "affinity": "INTEGER",
        "notNull": true
      },
      {
        "fieldPath": "firstName",
        "columnName": "first_name",
        "affinity": "TEXT",
        "notNull": false
      },
      {
        "fieldPath": "lastName",
        "columnName": "last_name",
        "affinity": "TEXT",
        "notNull": false
      }
    ],
    "primaryKey": {
      "columnNames": [
        "uid"
      ],
      "autoGenerate": true
    },
    "indices": [],
    "foreignKeys": []
  }
],
"setupQueries": [
  "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
  "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7fcc959eb0e5d9c2cd52cf58f7a05392\")"
]
}
}

----------------你已经成功拿到了数据库各个版本的概要、记录数据------------------

接下来,我们应该进行数据库的测试,保证我们程序的稳健。写入如下:

 @RunWith(AndroidJUnit4.class) 
 public class MigrationTest {
  private static final String TEST_DB = "migration-test";

 @Rule
  public MigrationTestHelper helper;

  public MigrationTest() {
    helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
            AppDatabase.class.getCanonicalName(),
            new FrameworkSQLiteOpenHelperFactory());
  }

@Test
public void migrate1To2() throws IOException {
    SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);

    //你可以通过sql语句写入一些测试数据(旧版本数据库)
  //        db.execSQL(...);


    // 关闭数据库,为了打开新的数据库
  //        db.close();

    //打开新的数据库测试迁移等
    db = helper.runMigrationsAndValidate(TEST_DB, 2, true, AppDatabase.MIGRATION_1_2);

  }
   }

调试migrate1To2()方法,即可测试数据库操作了。

----------------------------------完成数据库测试--------------------------------------

后续,我会抽时间加上数据表的多表查询、RxJAVA支持等。。。

总结

Room是google官方推出的一个好用的数据库框架。文章带领大家了解思路以及快速上手。
下面是Demo地址,方便大家测试学习~

地址:https://github.com/HoldMyOwn/RoomDemo.git

注:github留下了两个commit(分别对应数据库版本1和2)。大家可以通过先跑数据库版本1代码,拿到版本1数据库。然后,跑数据库版本2代码,体验数据库版本迁移。

上一篇下一篇

猜你喜欢

热点阅读