JetPack--Room數(shù)據(jù)庫

JetPack提供了Room數(shù)據(jù)庫,和GreenDAO等開源庫一樣黔牵,在SQLite做了封裝

Room主要使用三個注解:

1.Entity:實體類斗蒋,對應(yīng)一張表
2.Dao:包含操作表的一些列方法
3.Database:數(shù)據(jù)庫持有者,數(shù)據(jù)庫驅(qū)動戈稿。需要滿足:定義的類是一個繼承RoomDatabase的抽象類西土,注解中定義包含實體類列表,包含一個沒有參數(shù)的抽象方法并返回Dao對象

一鞍盗、Room上手

首先添加依賴:

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

定義一個實體類需了,在class上使用 @Entity注解 跳昼,還需要一個構(gòu)造方法,Room會根據(jù)這個構(gòu)造將表里的數(shù)據(jù)轉(zhuǎn)化為實體類肋乍,對于其他我們代碼里使用的構(gòu)造方法鹅颊,可以使用@Ignore注解表示Room將忽略它,屬性也可以使用這個注解墓造,表示這個屬性將不會生成數(shù)據(jù)庫字段
使用@PrimaryKey注解指定主鍵并且是自增長的
屬性還可以指定在數(shù)據(jù)庫的字段等堪伍,使用@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表進(jìn)行操作,對接口使用@Dao注解
@Query觅闽、@Insert帝雇、@Delete、@Update注解蛉拙,分別表示:查詢尸闸、新增、刪除孕锄、更新
增刪改操作內(nèi)部會自動使用主鍵進(jìn)行操作

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();

    //根據(jù)id查詢
    @Query("SELECT * FROM user WHERE id = :id")
    User getUserById(int id);

    //插入一條數(shù)據(jù)
    @Insert
    void insertUser(User user);

    //刪除一條數(shù)據(jù)
    @Delete
    void deleteUser(User user);

    //更新一條數(shù)據(jù)
    @Update
    void updateUser(User user);
}

定義抽象類吮廉,繼承于RoomDatabase,并使用@Database注解硫惕,注解中指定表的實體類茧痕、數(shù)據(jù)庫版本、是否輸出日志
使用單例模式時恼除,構(gòu)造方法不能私有化踪旷,因為Room內(nèi)部會調(diào)用構(gòu)造方法
定義獲取Dao對象的抽象函數(shù)

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表內(nèi)的數(shù)據(jù),并使用四個按鈕分別進(jìn)行查詢豁辉,新增令野,刪除,修改操作徽级。
效果:



不過每次我們做了操作后气破,還需要手動查詢下,有沒有可以自動刷新數(shù)據(jù)的方法呢餐抢?

二现使、ViewModel+LiveData+Room

Room支持返回LiveData類型,結(jié)合ViewModel旷痕、DataBinding碳锈,就可以改造成一個非常棒的MVVM架構(gòu)


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();

    //根據(jù)id查詢
    @Query("SELECT * FROM user WHERE id = :id")
    User getUserById(int id);

    //插入一條數(shù)據(jù)
    @Insert
    void insertUser(User user);

    //刪除一條數(shù)據(jù)
    @Delete
    void deleteUser(User user);

    //刪除所有數(shù)據(jù)
    @Query("DELETE  FROM user")
    void deleteAllUser();

    //更新一條數(shù)據(jù)
    @Update
    void updateUser(User user);
}

首先定義Repository層,里面實現(xiàn)對數(shù)據(jù)庫的操作

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升級數(shù)據(jù)庫

定義Migration售碳,構(gòu)造時需要低版本號和高版本號,初始化數(shù)據(jù)庫時,通過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贸人,但是沒有寫相應(yīng)的Migration间景,那么會出現(xiàn)一個IIlegalStateException異常,使用fallbackToDestructiveMigration方法艺智,出現(xiàn)異常時倘要,會重新構(gòu)造表,當(dāng)然以前的數(shù)據(jù)會丟失

3.Schema文件

我們在使用@Database注解exportSchema指定為true十拣,那么每次升級時碗誉,都會導(dǎo)出一個Schema文件,里面包含的數(shù)據(jù)庫的創(chuàng)建信息父晶,方便排查問題
同時我們也需要在gradle里指定下導(dǎo)出文件夾位置

    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中修改表結(jié)構(gòu)比較麻煩哮缺,如果想要將sex字段從INTEGER改為TEXT,最好的方式是采用銷毀與重建策略甲喝,將數(shù)據(jù)復(fù)制到一個臨時表尝苇,在刪除原表,再將臨時表重命名成原表名埠胖,可以參考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)",
            //根據(jù)schema文件創(chuàng)建新表糠溜,注意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 )"
            );
            //數(shù)據(jù)導(dǎo)入臨時表
            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.預(yù)填充數(shù)據(jù)庫

我們可以將數(shù)據(jù)庫文件放入assets目錄下,初始化數(shù)據(jù)庫時直撤,通過createFromAsset方法或createFromFile方法導(dǎo)入

    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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末非竿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谋竖,更是在濱河造成了極大的恐慌红柱,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蓖乘,死亡現(xiàn)場離奇詭異锤悄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)嘉抒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門零聚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人些侍,你說我怎么就攤上這事隶症。” “怎么了岗宣?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵蚂会,是天一觀的道長。 經(jīng)常有香客問我狈定,道長颂龙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任纽什,我火速辦了婚禮措嵌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘芦缰。我一直安慰自己企巢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布让蕾。 她就那樣靜靜地躺著浪规,像睡著了一般。 火紅的嫁衣襯著肌膚如雪探孝。 梳的紋絲不亂的頭發(fā)上笋婿,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音顿颅,去河邊找鬼缸濒。 笑死,一個胖子當(dāng)著我的面吹牛粱腻,可吹牛的內(nèi)容都是我干的庇配。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼绍些,長吁一口氣:“原來是場噩夢啊……” “哼捞慌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柬批,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤啸澡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后氮帐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锻霎,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年揪漩,在試婚紗的時候發(fā)現(xiàn)自己被綠了旋恼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡奄容,死狀恐怖冰更,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昂勒,我是刑警寧澤蜀细,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站戈盈,受9級特大地震影響奠衔,放射性物質(zhì)發(fā)生泄漏谆刨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一归斤、第九天 我趴在偏房一處隱蔽的房頂上張望痊夭。 院中可真熱鬧,春花似錦脏里、人聲如沸她我。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽番舆。三九已至,卻和暖如春矾踱,著一層夾襖步出監(jiān)牢的瞬間恨狈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工呛讲, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留拴事,地道東北人。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓圣蝎,卻偏偏與公主長得像刃宵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子徘公,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容