三三要成為安卓糕手
引入
Room是一個抽象層,對SQLite進行了封裝,簡化了SQLite數據庫的操作,讓開發者能以更加對象化的方式進行數據庫操作;Room解決了SQLite操作繁瑣,容易產生錯誤的問題,讓開發者能以更加對象化的方式進行數據庫操作。
一:添加依賴
Room屬于Jetpack中關于數據庫操作的庫,不屬于安卓原生SDK開發包,所以需要單獨添加依賴
// app/build.gradle
dependencies {//添加room運行時依賴庫implementation "androidx.room:room-runtime:2.6.1"// Room 編譯時注解處理器annotationProcessor "androidx.room:room-compiler:2.6.1"// 可選:RxJava2 支持 (如果需要使用RxJava2的異步機制配合Room使用,需要添加這個依賴)implementation "androidx.room:room-rxjava2:2.6.1"// 可選:RxJava3 支持 (如果需要)implementation "androidx.room:room-rxjava3:2.6.1"
}
二:Room 的核心組件
Room 數據庫主要有三個核心組件:
-
Entity(實體類):用于表示數據庫表的數據結構。
-
DAO(Data Access Object):用于定義數據庫操作的方法。
-
Database(數據庫類):用于創建數據庫實例,并將 Entity 和 DAO 關聯起來
1:Entity層
定義與作用:Entity
代表數據庫中的一張表,它是一個 Java 類,通過 @Entity
注解來標識。每個 Entity
類的實例對應表中的一行數據,類中的屬性對應表中的列
@Entity(tableName = "user-room")
public class MyUser {@PrimaryKey(autoGenerate = true)private int id;@ColumnInfo(name = "user_age")private int age;@ColumnInfo(name = "user_name")private String name;@ColumnInfo(name = "user_email")private String email;public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public int getId() {return id;}public void setId(int id) {this.id = id;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
-
Entity [?ent?ti] 實體
理解:把注解轉化成編譯器能夠看懂的東西
-
tableName = "user-room"
指定了該實體類對應的數據庫表名為user - room
。 -
@ColumnInfo(name = "user_age")
當前的字段對應我們數據庫中那一列的字段 -
@PrimaryKey(autoGenerate = true)
自增主鍵,設置為fasle的話就需要程序員自己設置id了
2:DAO層(數據訪問對象)
定義與作用:Dao
接口定義了對數據庫進行增刪改查(CRUD,Create、Read、Update、Delete)操作的方法。它是應用代碼與數據庫之間交互的橋梁,通過定義不同的方法來執行具體的 SQL 語句或 Room 封裝的操作
@Dao
public interface MyUserDao {@Insertvoid insertUser(MyUser user);/*** 通過MyUser類型直接更新數據庫,Room會根據MyUser中的主鍵來查找對應的數據,并且更新* @param user*/@Updateint updateUser(MyUser user);/*** 更新user-room這個表中,名字是等于name(第一個參數)的數據,把這條數據的user_age更新為age(第二個參數)*/@Query("update `user-room` set user_age = :age where user_name = :name")int updateUser(String name , int age);/*** 通過name查詢user-room表中的某個數據* @param name* @return*/@Query("select * from `user-room` where user_name = :name")List<MyUser> getUserByName(String name);@Deleteint delUser(MyUser user);/*** 通過name刪除對應的用戶* @param name* @return*/@Query("delete from `user-room` where user_name = :name ")int delete(String name);
}
- 這里設置的插入方法沒有返回值;有返回值的情況:當插入單個實體對象時,返回值是一個 long 類型,表示插入數據后自動生成的主鍵值(前提是自增主鍵)
3:Database層
它是一個抽象類,通過 @Database 注解來標識,通常會包含一個或者多個抽象方法),用于返回定義好的 Dao 接口實例,Room 會在運行時自動生成該抽象類的實現;(可以簡單理解成一個方法對應找庫中的一個表table)
@Database(entities = {MyUser.class} , version = 1)
public abstract class MyUserDatabase extends RoomDatabase {public abstract MyUserDao userDao();static final Migration MIGRATION_1_2 = new Migration(1,2) {@Overridepublic void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {supportSQLiteDatabase.execSQL("alter table `user-room` add column user_email text");}};
}
- entities = {MyUser.class}關聯一張表的名字,可以關聯多張table,在花括號中填寫即可;前提這張表一定是被entity標注過的
(1)版本遷移
如果要實現從版本 1 遷移到版本 5,不能直接寫 Migration(1, 5)
來 “一步到位” 完成所有中間版本(1→2→3→4→5)的遷移。Room 要求必須為每一個相鄰的版本跨度都定義對應的 Migration
,也就是需要分別定義:
- 從版本 1 到版本 2 的
Migration
; - 從版本 2 到版本 3 的
Migration
; - 從版本 3 到版本 4 的
Migration
; - 從版本 4 到版本 5 的
Migration
。
然后在構建數據庫時,通過 addMigrations()
方法把這些所有相鄰版本的 Migration
都添加進去,Room 會在數據庫升級時,按順序執行這些 Migration
,逐步完成從版本 1 到版本 5 的遷移。
(2)addMigration
MyUserDatabase userDatabase = Room.databaseBuilder(ContentProviderActivity.this, MyUserDatabase.class, "user-database.db").addMigrations(MyUserDatabase.MIGRATION_1_2).build();
(3)數據庫升級的時機
前提:創建數據庫時add了對應的版本升級方法,這個遷移方法內部的具體規則需要再Database層中進行聲明
如果當前版本為1,由于要給表添加新的列或者修改某個字段等等,想要升級到版本2;
那么這里就要修改為2@Database(entities = {MyUser.class} , version = 2)
,再次運行代碼的時候,MyUserDatabase.MIGRATION_1_2
這個遷移規則,它會告訴 Room 如何將版本 1 的數據庫轉換為版本 2 的數據庫,這樣在數據庫版本升級時,就能按照這個規則來執行相應的操作
static final Migration MIGRATION_1_2 = new Migration(1,2) {@Overridepublic void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {supportSQLiteDatabase.execSQL("alter table `user-room` add column user_email text");}};
migration 信息遷移 alter 修改
效果如下,我們在Entity層中新增加的字段user_email成功添加進來了,現在就是版本2
4:創建數據庫
MyUserDatabase userDatabase = Room.databaseBuilder(ContentProviderActivity.this, MyUserDatabase.class, "user-database.db").addMigrations(MyUserDatabase.MIGRATION_1_2).build();
- 參數一:上下文
- 參數二:數據庫的class對象
- 參數三:表示要創建的 SQLite 數據庫文件的名稱。
三:增刪查改
1:增
@Insertvoid insertUser(MyUser user);
/*** 插入數據*/findViewById(R.id.btn_insert).setOnClickListener(view -> {new Thread(new Runnable() {@Overridepublic void run() {//可以理解成操作table表的對象MyUserDao myUserDao = userDatabase.userDao();MyUser user = new MyUser();user.setName(etUserName.getText().toString().trim());user.setAge(Integer.valueOf(etUserAge.getText().toString().trim()));myUserDao.insertUser(user);userDatabase.close();}}).start();});
數據庫操作是一個非常耗時的操作,如果是在主線程操作會引起卡頓和崩潰,Room中插入數據需要指定在非主線程中進行,使用SQLite不用指定新線程,因為它內部已經幫我們處理好了
(1)插入結果
Room數據庫會把我們的插入或者查詢操作進行分段讀寫,所以就有三個文件,沒有db后綴,Room也會自動幫我們轉化為db文件的
為了測試,我們手動去添加后綴;并且想要整合三個文件,就需要手動close一次數據庫操作
效果如下
2:查 & 改
(1)修改數據
/*** 通過MyUser類型直接更新數據庫,Room會根據MyUser中的主鍵來查找對應的數據,并且更新* @param user*/@Updateint updateUser(MyUser user);
/*** 更新數據*/findViewById(R.id.btn_update).setOnClickListener(view -> {new Thread(new Runnable() {@Overridepublic void run() {MyUserDao myUserDao = userDatabase.userDao();MyUser user = new MyUser();user.setId(1);user.setName(etUserName.getText().toString().trim());user.setAge(Integer.valueOf(etUserAge.getText().toString().trim()));myUserDao.updateUser(user);//通過名字查詢到對應的信息,修改這個用戶的年齡userDatabase.userDao().updateUser("lisi",100);}}).start();});
(2)根據主鍵查找
/*** 通過name查詢user-room表中的某個數據* @param name* @return*/@Query("select * from `user-room` where user_name = :name")List<MyUser> getUserByName(String name);
/*** 查詢數據*/findViewById(R.id.btn_select).setOnClickListener(view -> {new Thread(new Runnable() {@Overridepublic void run() {MyUserDao myUserDao = userDatabase.userDao();String queryName = etUserName.getText().toString().trim();List<MyUser> users = myUserDao.getUserByName(queryName);Log.i(TAG, "run:users.size" + users.size());if(users.size() > 0){runOnUiThread(new Runnable() {@Overridepublic void run() {//顯示查詢到的用戶數據MyUser user = users.get(0);tvMessage.setText(tvMessage.getText() +"\n" + user.getName() + ":" + user.getAge());}});}}}).start();});
效果如下
(3)根據字段查找
通過別的數據進行user的查找;不想通過User實體類和主鍵,進行查找
自定義查詢規則,sql語句
/*** 更新user-room這個表中,名字是等于name(第一個參數)的數據,把這條數據的user_age更新為age(第二個參數)*/@Query("update `user-room` set user_age = :age where user_name = :name")int updateUser(String name , int age);
3:刪
多嘗試
@Deleteint delUser(MyUser user);
/*** 通過name刪除對應的用戶* @param name* @return*/@Query("delete from `user-room` where user_name = :name ")int delete(String name);
findViewById(R.id.btn_delete).setOnClickListener(view -> {new Thread(new Runnable() {@Overridepublic void run() {
// MyUser user = new MyUser();
// user.setId(1);
// userDatabase.userDao().delUser(user);userDatabase.userDao().delete("wangwu");}}).start();});