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;
}