JetPack--Room数据库

JetPack提供了Room数据库,和GreenDAO等开源库一样,在SQLite做了封装

Room主要使用三个注解:

1.Entity:实体类,对应一张表
2.Dao:包含操作表的一些列方法
3.Database:数据库持有者,数据库驱动。需要满足:定义的类是一个继承RoomDatabase的抽象类,注解中定义包含实体类列表,包含一个没有参数的抽象方法并返回Dao对象

一、Room上手

首先添加依赖:

    implementation 'androidx.room:room-runtime:2.3.0-rc01'
    annotationProcessor 'androidx.room:room-compiler:2.3.0-rc01'

定义一个实体类,在class上使用 @Entity注解 ,还需要一个构造方法,Room会根据这个构造将表里的数据转化为实体类,对于其他我们代码里使用的构造方法,可以使用@Ignore注解表示Room将忽略它,属性也可以使用这个注解,表示这个属性将不会生成数据库字段
使用@PrimaryKey注解指定主键并且是自增长的
属性还可以指定在数据库的字段等,使用@ColumnInfo注解

package com.aruba.room;

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

/**
 * Created by aruba on 2021/9/12.
 */
@Entity
public class User {
    @PrimaryKey(autoGenerate = true)
    public int id;
    public String name;
    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    public int age;
    @Ignore
    public boolean flag;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

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

    @Ignore
    public User() {
        
    }
}

定义Dao接口来对刚刚的User表进行操作,对接口使用@Dao注解
@Query、@Insert、@Delete、@Update注解,分别表示:查询、新增、删除、更新
增删改操作内部会自动使用主键进行操作

package com.aruba.room;

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

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
@Dao
public interface UserDao {
    //查询
    @Query("SELECT * FROM user")
    List<User> getUsers();

    //根据id查询
    @Query("SELECT * FROM user WHERE id = :id")
    User getUserById(int id);

    //插入一条数据
    @Insert
    void insertUser(User user);

    //删除一条数据
    @Delete
    void deleteUser(User user);

    //更新一条数据
    @Update
    void updateUser(User user);
}

定义抽象类,继承于RoomDatabase,并使用@Database注解,注解中指定表的实体类、数据库版本、是否输出日志
使用单例模式时,构造方法不能私有化,因为Room内部会调用构造方法
定义获取Dao对象的抽象函数

package com.aruba.room;

import android.content.Context;

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

/**
 * Created by aruba on 2021/9/12.
 */
@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class MyDataBase extends RoomDatabase {
    private static final String DBNAME = MyDataBase.class.getSimpleName();
    private static MyDataBase instance;

    public static MyDataBase getInstance() {
        if (instance == null) throw new NullPointerException("database not init!!");
        return instance;
    }

    public static synchronized MyDataBase init(Context context) {
        if (instance == null)
            instance = Room.databaseBuilder(context.getApplicationContext()
                    , MyDataBase.class, DBNAME)
                    .build();

        return instance;
    }

    //获取Dao对象
    public abstract UserDao getUserDao();
}

界面中使用一个RecyclerView展示User表内的数据,并使用四个按钮分别进行查询,新增,删除,修改操作。
效果:



不过每次我们做了操作后,还需要手动查询下,有没有可以自动刷新数据的方法呢?

二、ViewModel+LiveData+Room

Room支持返回LiveData类型,结合ViewModel、DataBinding,就可以改造成一个非常棒的MVVM架构


package com.aruba.room;

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

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
@Dao
public interface UserDao {
    //查询
    @Query("SELECT * FROM user")
    LiveData<List<User>> getUsers();

    //根据id查询
    @Query("SELECT * FROM user WHERE id = :id")
    User getUserById(int id);

    //插入一条数据
    @Insert
    void insertUser(User user);

    //删除一条数据
    @Delete
    void deleteUser(User user);

    //删除所有数据
    @Query("DELETE  FROM user")
    void deleteAllUser();

    //更新一条数据
    @Update
    void updateUser(User user);
}

首先定义Repository层,里面实现对数据库的操作

package com.aruba.room;

import android.content.Context;
import android.os.AsyncTask;
import android.view.View;

import androidx.lifecycle.LiveData;

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
public class UserRepository {
    private UserDao userDao;

    public UserRepository(Context context) {
        this.userDao = MyDataBase.init(context).getUserDao();
    }

    public void insert(User user) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.insertUser(user);
                return null;
            }
        }.execute();
    }

    public void update(User user) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.updateUser(user);
                return null;
            }
        }.execute();
    }

    public void delete(User user) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.deleteUser(user);
                return null;
            }
        }.execute();
    }

    public void deleteAllUser(){
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                userDao.deleteAllUser();
                return null;
            }
        }.execute();
    }
    
    public LiveData<List<User>> query() {
        return userDao.getUsers();
    }
}

定义ViewModel

package com.aruba.room;

import android.app.Application;
import android.view.View;

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

import java.util.List;

/**
 * Created by aruba on 2021/9/12.
 */
public class UserViewModel extends AndroidViewModel {
    private UserRepository userRepository;

    public UserViewModel(@NonNull Application application) {
        super(application);
        userRepository = new UserRepository(application);
    }

    public void insert(View v) {
        userRepository.insert(new User("张三", 12));
    }

    public void update(View v) {
        User user = new User("赵四", 18);
        user.id = 1;
        userRepository.update(user);
    }

    public void delete(View v) {
        User user = new User();
        user.id = 2;
        userRepository.delete(user);
    }

    public void deleteAll(View v) {
        userRepository.deleteAllUser();
    }
    
    public LiveData<List<User>> getUsers() {
        return userRepository.query();
    }
}

Activity中使用:

package com.aruba.room;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Bundle;

import com.aruba.room.databinding.ActivityMainBinding;

import java.util.List;

public class MainActivity extends AppCompatActivity {

    private RecyclerViewAdapter recyclerViewAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        UserViewModel userViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(UserViewModel.class);
        activityMainBinding.setUserViewModel(userViewModel);
        activityMainBinding.setLifecycleOwner(this);

        userViewModel.getUsers().observe(this, new Observer<List<User>>() {
            @Override
            public void onChanged(List<User> users) {
                recyclerViewAdapter.setDatas(users);
                recyclerViewAdapter.notifyDataSetChanged();
            }
        });
        recyclerViewAdapter = new RecyclerViewAdapter();
        activityMainBinding.recyclerview.setAdapter(recyclerViewAdapter);
        activityMainBinding.recyclerview.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
    }
}

效果:


三、升级与填充

1.使用Migration升级数据库

定义Migration,构造时需要低版本号和高版本号,初始化数据库时,通过addMigrations方法传入

package com.aruba.room;

import android.content.Context;

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;

/**
 * Created by aruba on 2021/9/12.
 */
@Database(entities = {User.class}, version = 3, exportSchema = false)
public abstract class MyDataBase extends RoomDatabase {
    private static final String DBNAME = MyDataBase.class.getSimpleName();
    private static MyDataBase instance;

    public static MyDataBase getInstance() {
        if (instance == null) throw new NullPointerException("database not init!!");
        return instance;
    }

    public static synchronized MyDataBase init(Context context) {
        if (instance == null)
            instance = Room.databaseBuilder(context.getApplicationContext()
                    , MyDataBase.class, DBNAME)
                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                    .build();

        return instance;
    }

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE user ADD COLUMN sex INTEGER NOT NULL DEFAULT 1");
        }
    };

    static final Migration MIGRATION_2_3 = new Migration(2, 3) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            database.execSQL("ALTER TABLE user ADD COLUMN height INTEGER NOT NULL DEFAULT 1");
        }
    };

    //获取Dao对象
    public abstract UserDao getUserDao();
}

2.异常处理

如果我们将版本升级到3,但是没有写相应的Migration,那么会出现一个IIlegalStateException异常,使用fallbackToDestructiveMigration方法,出现异常时,会重新构造表,当然以前的数据会丢失

3.Schema文件

我们在使用@Database注解exportSchema指定为true,那么每次升级时,都会导出一个Schema文件,里面包含的数据库的创建信息,方便排查问题
同时我们也需要在gradle里指定下导出文件夹位置

    defaultConfig {
        applicationId "com.aruba.room"
        minSdk 21
        targetSdk 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        dataBinding {
            enabled = true
        }
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }
{
  "formatVersion": 1,
  "database": {
    "version": 3,
    "identityHash": "5a971aace7f8ede39ea6eb469ab90b10",
    "entities": [
      {
        "tableName": "User",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `age` INTEGER NOT NULL, `sex` INTEGER NOT NULL, `height` INTEGER NOT NULL)",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "name",
            "columnName": "name",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "age",
            "columnName": "age",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "sex",
            "columnName": "sex",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "height",
            "columnName": "height",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id"
          ],
          "autoGenerate": true
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "views": [],
    "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, '5a971aace7f8ede39ea6eb469ab90b10')"
    ]
  }
}
4.销毁与重建策略

SQLite中修改表结构比较麻烦,如果想要将sex字段从INTEGER改为TEXT,最好的方式是采用销毁与重建策略,将数据复制到一个临时表,在删除原表,再将临时表重命名成原表名,可以参考schema文件

    static final Migration MIGRATION_3_4 = new Migration(3, 4) {
        @Override
        public void migrate(@NonNull SupportSQLiteDatabase database) {
            //"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` 
            // (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT
            // , `age` INTEGER NOT NULL, `sex` INTEGER NOT NULL, `height` INTEGER NOT NULL)",
            //根据schema文件创建新表,注意TEXT不需要加上NOT NULL
            database.execSQL(
                    "CREATE TABLE temp_user (" +
                            "id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
                            "name TEXT, " +
                            "age INTEGER NOT NULL, " +
                            "sex TEXT DEFAULT 'M', " +
                            "height INTEGER NOT NULL )"
            );
            //数据导入临时表
            database.execSQL("INSERT INTO temp_user (name,age,sex,height)" +
                    "SELECT name,age,sex,height FROM user");
            //丢弃原表
            database.execSQL("DROP TABLE user");
            //临时表重命名
            database.execSQL("ALTER TABLE temp_user RENAME TO user");
        }
    };
5.预填充数据库

我们可以将数据库文件放入assets目录下,初始化数据库时,通过createFromAsset方法或createFromFile方法导入

    public static synchronized MyDataBase init(Context context) {
        if (instance == null)
            instance = Room.databaseBuilder(context.getApplicationContext()
                    , MyDataBase.class, DBNAME)
                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
                    .fallbackToDestructiveMigration()
                    .createFromAsset("mem.db")
                    .build();

        return instance;
    }
Demo地址:https://gitee.com/aruba/my-jetpack-application.git
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容