1. 程式人生 > >實現一個小型資料庫--記一次中級軟體設計實作浮沉歷程

實現一個小型資料庫--記一次中級軟體設計實作浮沉歷程

說在前頭:本篇文章主要是記錄這次專案的過程,不全是貼程式碼,具體的程式移步這裡,不喜歡的同學請輕噴。

事件起因:大三狗一枚,專業是軟體應用。這學期的中級軟體設計實作題目是實現一個小型的資料庫,具體的題目要求如下:
建立一個類似Oracle\SQL Server的小型資料庫系統:

  • 可建立表,表有欄位、型別
  • 表中可通過SQL語句填入資料(只能是特定型別)和查詢資料;
  • 對主鍵欄位,應自動建立索引;
  • 當進行查詢時,在主鍵上應是基於索引的高效查詢,而不能是字串匹配這樣的原始查詢。

覺得題目似曾相識的同學,應該猜到我是哪所大學的了吧。一番Google、百度下來,最終確定了實現成一個Android的應用,畢竟這學期剛學的Android,這知識還滾燙著呢,而且Android的各種框架已經很成熟了,不用重複造輪子了。

前期準備:既然需求已經如此明確,接下來就是進行介面設計。然而自己並沒有高大上的審美,也沒有美術的基礎,所以並不敢有太高的要求,但是業務邏輯要通順,其他只能說長得不醜就行了。我用的是Mac系統下的一個Sketch的軟體繪製的原型圖,在這裡就不貼出來了,具體的應用介面下面有。
接下來就是技術準備了,實現一個小型資料庫,我總結了以下幾點主要的技術難點:

  • 其次也是最難的部分是B+樹,當前的很多資料庫系統都是用B+樹。關於B+樹的總結,可以參看我的另一篇部落格
  • 最後就是Android的相關知識。這個需要長期的積累,遇到不會的我都會Google,或者檢視技術部落格,stackoverflow,github等等也有很多輪子可以直接用。

實際編碼:終於來到真正的打大怪階段了。這一部分內容我直接按介面(Activity)進行劃分。

  • 主介面(Main Activity):首先是介面展示
    主介面1
    主介面2
    主介面上就是所有已經建好的表的列表,點選列表項則彈出對話方塊進行刪除表、新增資料、查詢資料操作,這三個操作具體實現在後面會詳細說;其次右上角是新建表的按鈕,點選跳轉到對應的介面。主介面邏輯都很簡單,值得一提的是重新整理列表的操作我是放在onResume函式中,所以每次進入主介面就會重新整理列表。程式碼片段如下:
@Override
protected void onResume()
{
    super.onResume();
    refreshSharedPreferencesList();  // 重新整理列表
}
  • 新建表介面(AddTable Activity):介面展示如下:
    新建表介面展示
    介面上一個是編輯框輸入欄位名,一個是對應資料型別的下拉列表,填好一個欄位資訊後按右上角的”+”按鈕再生成一對新的欄位名框和型別列表,所有的編輯框和下拉列表儲存在ArrayList中,且預設第一個是主鍵,最後按下面的確定按鈕新建表。
    考慮到資料量小,而且是鍵值對應的形式,我將表的資訊儲存SharedPreference中,以使用者輸入的表名命名該檔案。相關程式碼如下:
private List<EditText> editTextList = new ArrayList<>();
private List<Spinner> spinnerList = new ArrayList<>();

// 將欄位名和對應的型別儲存到SharedPreferences中
private void saveInSharedPreferences(String tableName)
{
    SharedPreferences.Editor editor = getSharedPreferences(tableName, MODE_PRIVATE).edit();
    int index = -1;
    for (EditText editText : editTextList) {
        index++;
        String fieldName = editText.getText().toString();
        Spinner spinner = spinnerList.get(index);
        String type = spinner.getSelectedItem().toString();
        editor.putString(String.valueOf(index), fieldName+"&"+type);
    }
    editor.commit();
}

因為SharedPreference內部是用HashMap儲存的,所以欄位是無序的,為了取出的時候保持有序我只能將所以的鍵依次設為遞增的整數,值通過’&’符號複合字段名和對應的型別,等取出的時候在解析出來。另外,剛剛主介面有刪除表的操作,現在可以補上具體操作:找出SharedPreference儲存的位置(haredPreferences 檔案都是存放在/data/data/packagename/shared_prefs/目錄下的),根據檔名刪除對應的檔案即可,程式碼如下:

// 刪除表
private void deleteTableAction(String tableName)
{
    File prefsdir = new File(getApplicationInfo().dataDir,"shared_prefs");
    if(prefsdir.exists() && prefsdir.isDirectory()){
        String[] list = prefsdir.list();
        for (String fileNameWithEx : list) {
            String fileName = getFileNameNoEx(fileNameWithEx);
            if (fileName.equals(tableName)) {
                File deleteFile = new File(prefsdir, fileName+".xml");
                deleteFile.delete();
                refreshSharedPreferencesList(); //重新整理列表
            }
        }
    }
}
  • 插入介面(ExecSQL Activity):國際慣例展示介面先:
    插入介面
    考慮到插入介面和搜尋介面都是輸入SQL語句然後執行操作,所以共用了同一個介面(ExecSQL Activity),下面不再說明。因為輸入插入語句然後按”執行”按鈕然後在當前介面並不能立刻進行查詢操作,同時需要永久儲存插入的資料,所以考慮將所有的插入語句儲存到檔案中,然後在需要執行查詢的時候在取出插入語句插入到B+樹上,然後進行查詢。相關程式碼如下:
// 將插入語句寫進檔案中
private void writeInsertSqlToFile(String fileName)
{
    FileOutputStream out = null;
    BufferedWriter writer = null;
    try {
        out = openFileOutput(fileName, Context.MODE_APPEND);        // 追加模式
        writer = new BufferedWriter(new OutputStreamWriter(out));
        String insertSQLStatement = sqlEdit.getText().toString();
        String[] lines = insertSQLStatement.split(System.getProperty("line.separator"));    // 按行擷取
        int length = lines.length;
        // 將所有的插入語句寫入檔案中
        for (int i = 2; i < length; i++) {
            String oneLine = lines[i];
            //Todo: 此處應該檢查每一行資料的正確性
            writer.write(oneLine);
            writer.newLine();   // 換行
            Log.d("write oneLine", oneLine);
        }
        sqlEdit.setText(null);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (writer != null) {
                writer.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 查詢介面(ExecSQL Activity):介面展示如下:
    查詢介面
    查詢的時候先取出where語句,然後解析出查詢的時候具體的限定條件,具體程式碼如下:
// 從表中查詢資料
private void queryFromTable()
{
    BPlusTree tree = new BPlusTree(tableName, BPLUSTREE_DEGREE);
    String insertSql = readInsertSqlFromFile(tableName);
    // 將資料逐條插到B+樹上
    if (insertDataInTree(tree, insertSql)) {
        String querySql = sqlEdit.getText().toString();
        String[] twoStr = analyseQuerySqlStatement(querySql);
        ArrayList<String> resultList = new ArrayList<String>();

        if (twoStr[0].equals(fieldNameArray.get(0))) {
            // 查詢主鍵
            Object result = tree.search(twoStr[1]);
            if (result != null) {
                String resultString = formatResultMapToString((Map) result, twoStr[1]);
                resultList.add(resultString);
            }
        } else {
            // 查詢其他屬性
            Node head = tree.getHead();
            resultList = traverseTreeToSearch(head, twoStr);
        }
    }
}

取出查詢結果存放到ArrayList中,跳轉到查詢結果介面並putExtra把查詢結果傳過去。

  • 查詢結果介面(QueryResult Activity):最後就是展示查詢的結果了:
    查詢結果介面
    這裡只需展示查詢結果到列表中即可。

後記:當做完整個專案之後回頭看看,似乎做了什麼了不得的事,又似乎什麼都沒有做的樣子。通過這次浮沉歷程,我深刻體會到做好前期準備是多麼的重要,一遍遍改資料結構、資料型別是很痛苦的……還有一點體會就是應該把model層(MVC框架)寫的健壯,具有魯棒性,得益於我花了4、5天的B+樹,我後期的時候只管組織好介面等其他部分即可,不用擔心我的B+樹會鬧彆扭。看來設計模式果然灰常重要的,《大話設計模式》、《設計模式之禪》……你們等著我,我交完這次作業就過來找你們!!!
PS: 我將這次的完整程式放在了這裡,這是初期版本,可能會有更新,下了初期版本想要更新版本的可以私信我。也歡迎各位大牛拍磚,你們的意見是我前進最好的指導,謝謝。