本篇要來闡述一下關於所謂的"資料庫遷移"
一般你搜尋關鍵字的話,可以使用RoomData migrate等相關字眼搜尋
而資料庫遷移是什麼?又是什麼時候應用呢?
我這邊給個案例,你大概就會懂了(-‿◦)
所謂的資料庫遷移...應該用"資料庫升級"可能會比較好懂一點(因為單字Migrate的意思是遷徙)
Google官方在說明此項技術時的用詞也是用Migrate這個詞,所以中翻大家都直接講遷移
那回歸正題,資料庫遷移的意思其實就是修改資料庫內的結構,非內行的可以當個長知識看就好
譬如你今天想要做民調,調查個年齡層喜歡的政黨
那麼你會怎麼設計問卷?按照常聽到的大概就是
1. 年齡
2. 學歷
3. 哪裡人
4. 喜歡的政黨
這四項吧?
Okay,那麼這些資料總是要建檔在電腦裡面,於是這時資料庫工程師就會照著你的表單輸入這四項
但若有一天你還想要在表單上加入一個選項叫做行業別
那麼資料庫的內容就要修正為
1. 年齡
2. 學歷
3. 哪裡人
4. 喜歡的政黨
5. 行業別
以上五項
這時候軟體工程師那邊就需要針對你的要求去修改他所建立的表單
而這"修改"的行為我們在軟體工程上就是稱呼他為"遷移"
...所以我覺得就是個文字遊戲而已...
好啦,那麼就來開始今天的內容
⚠️請注意⚠️
本篇的內容將延續以前寫的一篇文章
->碼農日常-『Android studio』使用Room資料庫以及資料庫監視工具Stetho
這邊建議如果是不會Room DataBase設置方式的朋友
建議可以從這篇看起
當然,最後我也會統整一下資料庫升級需要注意哪些事項
如果是熟悉使用Room的朋友也不至於還要從新適應我的Coding Style
跟上回輸入內容比較一下
主要就是多了一個欄位(・ω・)b
再來是Github,這邊基本上是沿用之前上傳的哦
->https://github.com/thumbb13555/RoomDataBaseExample
1. 資料結構
來吧,首先講一下關於文件的擺放
專注在RoomDataBase資料夾裡面的內容就好
共有三個檔案,分別為
1. DataBase.java(抽象類)
2. DataUao.java(interface類)
3. MyData.java(實體類)
這三即是組成Room資料庫的基礎
再來看一下至上次為止的資料庫內容
這時候的資料內容為
1. ID
2. Name
3. phone
4. hobby
5. elseinfo
以上五個欄位
那麼今天的目標就是要新增一個欄位叫做"age"年齡的欄位
而最後的結果將會如下
變成6個欄位
1. ID
2. Name
3. phone
4. hobby
5. elseinfo
6. age
Okay, 那以下開始(・ωー)~☆
2. 開始修改
本文從這邊開始修改,修改順序為
1. 介面
2. MyData.java
3. DataBase.java
4. DataUao.java
5. MainActivity.java
當然啦,也不一定要按照我的順序
不過由於Android studio在你輸入的過程中就會逐步編譯
所以有些東西就是一開始寫上去會是紅字,看了不舒服罷了
那麼,開始吧
2-1. 修改介面
首先當然從最好改的開始XD
就結果論就是要像下圖所示
→
直接幫你標注改動的部分即可
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginTop="32dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button_Modify" /> <Button android:id="@+id/button_Create" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="32dp" android:text="新增" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/linearLayout5" /> <Button android:id="@+id/button_Modify" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:layout_marginEnd="8dp" android:text="修改" app:layout_constraintEnd_toStartOf="@+id/button_Clear" app:layout_constraintStart_toEndOf="@+id/button_Create" app:layout_constraintTop_toBottomOf="@+id/linearLayout5" /> <LinearLayout android:id="@+id/linearLayout5" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:orientation="vertical" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <LinearLayout android:id="@+id/linearLayout2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="姓名:" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <EditText android:id="@+id/editText_Name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:ems="10" android:inputType="textPersonName" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout4" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="電話:" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <EditText android:id="@+id/editText_Phone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:ems="10" android:inputType="textPersonName" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout3" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="興趣:" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <EditText android:id="@+id/editText_Hobby" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:ems="10" android:inputType="textPersonName" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="其他:" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <EditText android:id="@+id/editText_else" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:ems="10" android:inputType="textPersonName" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout6" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="20dp" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="年齡:" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <EditText android:id="@+id/editText_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:ems="10" android:inputType="textPersonName" /> </LinearLayout> </LinearLayout> <Button android:id="@+id/button_Clear" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:layout_marginEnd="16dp" android:text="清空顯示" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/linearLayout5" /> </androidx.constraintlayout.widget.ConstraintLayout>
2-2. 修改實體類MyData.java
再來是實體類,加入一個age的欄位
@Entity(tableName = "MyTable") public class MyData { @PrimaryKey(autoGenerate = true)//設置是否使ID自動累加 private int id; private String name; private String phone; private String hobby; private String elseInfo; private int age; public MyData(String name, String phone, String hobby, String elseInfo,int age) { this.name = name; this.phone = phone; this.hobby = hobby; this.elseInfo = elseInfo; this.age = age; } @Ignore//如果要使用多形的建構子,必須加入@Ignore public MyData(int id,String name, String phone, String hobby, String elseInfo,int age) { this.id = id; this.name = name; this.phone = phone; this.hobby = hobby; this.elseInfo = elseInfo; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getHobby() { return hobby; } public void setHobby(String hobby) { this.hobby = hobby; } public String getElseInfo() { return elseInfo; } public void setElseInfo(String elseInfo) { this.elseInfo = elseInfo; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
2-3. 修改抽象類別DataBase.java, 加入Migrate程式
然後修改關於抽象類別那個部分
要注意到,這個地方改的內容要特別特別注意我標註的地方,否則不會執行
(修改前)
@Database(entities = {MyData.class},version = 1,exportSchema = true) public abstract class DataBase extends RoomDatabase { public static final String DB_NAME = "RecordData.db"; private static volatile DataBase instance; public static synchronized DataBase getInstance(Context context){ if(instance == null){ instance = create(context);//創立新的資料庫 } return instance; } private static DataBase create(final Context context){ return Room.databaseBuilder(context,DataBase.class,DB_NAME).build(); } public abstract DataUao getDataUao();//設置對外接口 }
(修改後)
@Database(entities = {MyData.class},version = 2,exportSchema = true) public abstract class DataBase extends RoomDatabase { public static final String DB_NAME = "RecordData.db"; private static volatile DataBase instance; public static synchronized DataBase getInstance(Context context){ // if(instance == null){ // instance = create(context);//創立新的資料庫 // } if (instance == null){ instance = Room.databaseBuilder( context.getApplicationContext(), DataBase.class,DB_NAME).addMigrations(MARGIN_1to2).build(); } return instance; } private static DataBase create(final Context context){ return Room.databaseBuilder(context,DataBase.class,DB_NAME).build(); } public static Migration MARGIN_1to2 = new Migration(1,2) { @Override public void migrate(@NonNull SupportSQLiteDatabase database) { database.execSQL("ALTER TABLE \"MyTable\" ADD COLUMN age INTEGER NOT NULL DEFAULT 18"); } }; public abstract DataUao getDataUao();//設置對外接口 }
把兩個比較一下,就能知道重點在哪裡囉:D
特別注意一下這裡
database.execSQL("ALTER TABLE \"MyTable\" ADD COLUMN age INTEGER NOT NULL DEFAULT 18");
實際在編輯器上,他是長這樣的
這行是關鍵中的關鍵,這個SQL指令就是告訴資料表要新增一個欄位的意思
ADD COLUMN 是指欄位名稱,以及欄位屬性(INTEGER為數字,要一般文字請輸入TEXT)
後面的NOT NULL DEFAULT 則意思是"欄位不可空,預設值為.."
所以在後面,我統一設置為18歲
而如果是TEXT欄位的話,請輸入'18'即可←連同單引號都要加入
2-4. 修改Interface DataUao.java
再來是修改其相關SQL語法,因為DataUao中本來就是放置SQL語法的地方
如下
@Dao public interface DataUao { String tableName = "MyTable"; /**=======================================================================================*/ /**簡易新增所有資料的方法*/ @Insert(onConflict = OnConflictStrategy.REPLACE)//預設萬一執行出錯怎麼辦,REPLACE為覆蓋 void insertData(MyData myData); /**複雜(?)新增所有資料的方法*/ @Query("INSERT INTO "+tableName+"(name,phone,hobby,elseInfo,age) VALUES(:name,:phone,:hobby,:elseData,:age)") void insertData(String name,String phone,String hobby,String elseData,int age); /**=======================================================================================*/ /**撈取全部資料*/ @Query("SELECT * FROM " + tableName) List<MyData> displayAll(); /**撈取某個名字的相關資料*/ @Query("SELECT * FROM " + tableName +" WHERE name = :name") List<MyData> findDataByName(String name); /**=======================================================================================*/ /**簡易更新資料的方法*/ @Update void updateData(MyData myData); /**複雜(?)更新資料的方法*/ @Query("UPDATE "+tableName+" SET name = :name,phone=:phone,hobby=:hobby,elseInfo = :elseInfo,age= :age WHERE id = :id" ) void updateData(int id,String name,String phone,String hobby,String elseInfo,int age); /**=======================================================================================*/ /**簡單刪除資料的方法*/ @Delete void deleteData(MyData myData); /**複雜(?)刪除資料的方法*/ @Query("DELETE FROM " + tableName + " WHERE id = :id") void deleteData(int id); }
修改這個地方,就是要細心再細心👍🏻
不要出錯了
2-5. 修改MainActivity.java內容
最後是外部接口,這部分就是如果你以上步驟都有改好,編輯器就會以紅字提醒了
(因為有200行,所以沒有修改的部份省略,全貌請參考Github)
public class MainActivity extends AppCompatActivity { MyAdapter myAdapter; MyData nowSelectedData;//取得在畫面上顯示中的資料內容 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Stetho.initializeWithDefaults(this);//設置資料庫監視 (略) EditText edAge = findViewById(R.id.editText_age); (略) /**=======================================================================================*/ /**設置修改資料的事件*/ btModify.setOnClickListener((v) -> { new Thread(() -> { if(nowSelectedData ==null) return;//如果目前沒前台沒有資料,則以下程序不執行 (略) int age = Integer.parseInt(edAge.getText().toString());/*遷移後新增*/ MyData data = new MyData( nowSelectedData.getId(), name, phone, hobby, elseInfo,age/*遷移後新增*/); DataBase.getInstance(this).getDataUao().updateData(data); runOnUiThread(() -> { (略) edAge.setText("");/*遷移後新增*/ nowSelectedData = null; myAdapter.refreshView(); Toast.makeText(this, "已更新資訊!", Toast.LENGTH_LONG).show(); }); }).start(); }); /**=======================================================================================*/ /**清空資料*/ btClear.setOnClickListener((v -> { (略) edAge.setText("");/*遷移後新增*/ nowSelectedData = null; })); /**=======================================================================================*/ /**新增資料*/ btCreate.setOnClickListener((v -> { new Thread(() -> { (略) int age = Integer.parseInt(edAge.getText().toString());/*遷移後新增*/ if (name.length() == 0) return;//如果名字欄沒填入任何東西,則不執行下面的程序 MyData data = new MyData(name, phone, hobby, elseInfo,age/*遷移後新增*/); DataBase.getInstance(this).getDataUao().insertData(data); runOnUiThread(() -> { myAdapter.refreshView(); edName.setText(""); edPhone.setText(""); edHobby.setText(""); edElseInfo.setText(""); edAge.setText("");/*遷移後新增*/ }); }).start(); })); /**=======================================================================================*/ /**初始化RecyclerView*/ new Thread(() -> { List<MyData> data = DataBase.getInstance(this).getDataUao().displayAll(); myAdapter = new MyAdapter(this, data); runOnUiThread(() -> { recyclerView.setAdapter(myAdapter); /**===============================================================================*/ (略) /**===============================================================================*/ /**取得被選中的資料,並顯示於畫面*/ myAdapter.setOnItemClickListener((myData)-> {//匿名函式(原貌在上方) nowSelectedData = myData; (略) edAge.setText(String.valueOf(myData.getAge()));/*遷移後新增*/ }); /**===============================================================================*/ }); }).start(); /**=======================================================================================*/ } private static class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> { (略) } /**設置RecyclerView的左滑刪除行為*/ private void setRecyclerFunction(RecyclerView recyclerView){ I(略) } }
這時候執行並打開Steoth(不知道這是什麼的罰你回去上一篇看),看到有新欄位就代表成功遷移囉!
3. 整理
看我上面寫了那麼多,你一定覺得重點太多了有點霧煞煞( ´_ゝ`)
這邊幫你整理一下我大概做了什麼
1. 去實體類新增欄位
2. 撰寫資料庫升級之程式
3. 到Uao調整SQL語法
4. 修正輸入的內容
花了這麼大的篇幅,便是以上這些重點
4. 補充
對了,由於這次的範例的Github是有再次Commit的,所以或許你想看一看在前一版的程式碼
你可以如以下操作
1.到我這篇的Gi,並點擊紅色方塊部分
2. 找到想要看的版本,點擊<>符號
3.這時候網頁會跳轉到原介面,但是這時的內容已經變更到上一版的狀態囉
今天的這個主題也是我很早之前就想寫的內容
不過我一直很猶豫到底要重寫一個專案還是拿之前做的東西來改
後來想了很久後還是決定拿之前的範例Code來改,因為感覺這樣比較符合平常會遇到的情況XD
那麼,這篇到此
如果文章對你有幫助的話...
怎麼辦,每次看你文章都覺得自己很笨耶
想太多了~ 我每次看你的文章才在思考我什麼時候才能旅居國外...
專業 ! 繼續努力
謝謝爺爺~
分享 新週快樂!
新周愉快(^^)/
感覺厲害欸
還好啦~熟能生巧!
實在太專業了 直接拍手五下鼓勵!!!
感謝你的來訪鼓勵^^
感謝分享~~~拍手
多謝多謝
今天冷氣團的影響,全台中午高溫不超過20度 台北站甚至在午後到11.9度 外出除了要保暖、攜帶雨具,也要保護呼吸道 謝曉賢關心你~
謝謝:D
專業專業 一定要過來鼓勵鼓勵
感謝感謝~
天氣變涼,住一包暖呀~來回拍^^
多謝!
來拜訪大神 日常崇拜 哈哈 推+拍手拍滿
別啦~我只是個平凡人! 多謝阿!
你有發文了嗎??? 我沒看到~~~~~
還沒哦~ 我一發文一定會用力標註爺爺的!
直接拍五下唷:)
感謝:D
限水還是來了! 苗栗、台中已從《橙燈》轉為《紅燈》 4月6日起從啟動「供5停2」分區供水 苗栗與台中各自分甲區 跟 乙區 ●甲區 每週二、三停供 ●乙區 每週四、五停供 至於分區供水實施到何時 「要看老天賞不賞臉」
希望水情可以好轉...
剛失戀的俺飄過~ 不知為啥一失戀就想昭(轟)告(炸)天(網)下(友)哈哈哈 可能是覺得不能只有自己痛苦(? 嗚嗚嗚人生第一次失戀現在也不知道怎麼辦然後工作又沒事做只好來胡言亂語了(´;ω;`) 太久沒碰AndroidStudio,所以我已經看不懂你在幹嘛了(? 想當年我還會打開一個新專案照著你的教學做呢哈哈哈 時光飛逝我已經看不懂然後又變成單身狗了嗚嗚嗚(此處應有刪除線 打算假日去拜個月老 看月老能不能看在我很可憐的份上賞我一個小鮮肉( ;∀;)
哪尼!! 這真是..痾..我想對廣大的男性同胞而言是個好消息吧!!((喂== 失戀阿..不知不覺我也失戀好多年了 這種感覺就是一時間整個世界都天崩地裂一樣,但是客觀來看又發現其實什麼都沒有發生 其實我到現在多少還是會想念跟前女友在一起的美好片刻,人家說雙魚座會一輩子忘不了愛情還真的不是亂說的== 現在還是在繼續寫Flutter嗎?還是已經在寫別的了呢XD 不論如何還是真的非常感謝你一直以來的支持啦~對我而言像你這樣會回饋我的算是心靈上一個很大的支柱:D 然後寫著寫著我從單身寫到還是單身QQ 好窩~祝福你可以得到小鮮肉 我就算了,老肥肉一枚,看到我會想報警叫FBI的那種XD((叔叔就是這個人!ㄛㄧㄛㄧ.. 不過想聊聊天的話歡迎常來抬槓~不好公開的可以用悄悄話哦
*****
*****
其實我有點自作自受 因為他說他本來還沒打算這麼早提 下個月原本要出去玩,行程都安排好飯店也定好了,他想說等出去玩回來再提 但我是覺得該來的還是要來(? 加上既然到這個地步了出去玩還蠻浪費錢的(? 還不如趁飯店可以免費取消的時候講清楚 所以我就要他當下說了 結果我高估我的承受能力直接爆哭一整晚哈哈哈 隔天本來想請假但是因為特休剩一天不想浪費 請事假又掰不出理由(加上為了錢錢 所以就硬著頭皮腫著眼睛去上班 然後因為心情不好公司又沒事做所以這兩天一直瘋狂找(散)人(播)談(憂)心(鬱)(啊對你也成為目標了XD) 喔對啊我還在寫Flutter 我的人生也只剩下Flutter了嗚嗚嗚嗚嗚......
不會啦,我覺得如果是我我也是選擇早點提出比較好 出去玩完後才提感覺太悲傷了,以後還會觸景傷情(像我後來都很不想經過前女友家附近.. 我那時候也是高估了自己的承受能力,被提分手後從大半夜12點多從台中市區哭著騎回豐原 隔天連我媽都很驚訝我怎麼會在家XD 沒問題的,分手也算是人生大事之一 我這邊當然樂意聽你講講,然後再拍拍告訴你這些經歷不是只有你會經歷 人生第一次被分手總是難過+不知所措的 總之慢慢療傷吧~雖然我連你哪裡人長什麼樣子都不知道 不過如果我的空間能夠讓你感到一絲絲覺得舒坦,那多來我也很歡迎:D(感謝你幫我衝流量~ㄎㄎ 然後哪天我就成為煩惱諮詢中心了,恩..XD
*****
*****
*****
*****
新增夥伴喔 !
收到~
*****
*****
*****
*****
收到 感謝 你可以看 我的/ 我的錢包 /交易紀錄 就知道有沒有漏了支持 或者我漏了支持 萬一我漏了 真的要講喔 你漏了 我也會提醒你的
Ok! 謝謝爺~
嗨~ 我是新村民 已幫你拍手5次喔♥
好喔~歡迎你!!
專業!!!
謝謝:D
就是專程來拍手的
好喔好喔,不論如何都很感謝
很專業,讚!
謝謝🙏
感謝好友分享
謝謝來訪