Android數據庫操作(二)——建表、升級表、表關聯

前言

本文參考轉發摘自:郭霖博客http://blog.csdn.net/guolin_blog?viewmode=contents 強烈建議進入原博客查看學習

1、建表

  • 操作數據庫的第一步當然是創建表了,傳統創建表的方法相信大多數人都知道,那么今天我除了會展示傳統的建表方法之外,還會講解LitePal這個框架的基本用法,并使用它來完成同樣的建表操作,讓大家體會到使用框架來操作數據庫的魅力。
  • 那么先來簡單介紹一下吧,LitePal是一款開源的Android數據庫框架,它采用了對象關系映射(ORM)的模式,并將我們平時開發時最常用到的一些數據庫功能進行了封裝,使得不用編寫一行SQL語句就可以完成各種建表、増刪改查的操作。并且LitePal很“輕”,jar包只有100k不到,而且近乎零配置,這一點和hibernate這類的框架有很大區別。目前LitePal的源碼已經托管到了GitHub上,地址是https://github.com/LitePalFramework/LitePal

1.1 傳統的建表方式

  • 為了方便我們對數據庫表進行管理,Android本身就提供了一個幫助類: SQLiteOpenHelper。這個類集創建和升級數據庫于一身,并且自動管理了數據庫版本,算是一個非常好用的工具。

  • 那我們現在就來試試 SQLiteOpenHelper 的用法吧。首先你要知道 SQLiteOpenHelper是一個抽象類,這意味著如果我們想要使用它的話,就需要創建一個自己的幫助類去繼承它。SQLiteOpenHelper中有兩個抽象方法,分別是 onCreate()onUpgrade() ,我們必須在自己的幫助類里面重寫這兩個方法,然后分別在這兩個方法中去 實現創建、升級數據庫的邏輯

  • 新建一個MySQLiteHelper類并讓它 繼承SQLiteOpenHelper

public class MySQLiteHelper extends SQLiteOpenHelper {

    public MySQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    //實現創建的邏輯
    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    //實現升級數據庫的邏輯
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
  • 其中,當數據庫創建的時候會調用onCreate()方法,在這里去執行建表操作就可以了。比如說我們想新建一張news表,其中有title,content,publishdate,commentcount這幾列,分別代表著新聞標題、新聞內容、發布時間和評論數,那么代碼就可以這樣寫:
public class MySQLiteHelper extends SQLiteOpenHelper {

    //建news表語句
    public static final String NEWS_TABLE = "create table news("
            + "id integer primary key autoincrement,"
            + "title text,"
            + "content text,"
            + "publishdata integer,"
            + "commentcount integer)";

    public MySQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    //實現創建的邏輯
    @Override
    public void onCreate(SQLiteDatabase db) {
        //創建名為 “news” 的表
        db.execSQL(NEWS_TABLE);
    }

    //實現升級數據庫的邏輯
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

可以看到,我們把建表語句定義成了一個常量,然后在 onCreate()方法中去執行了這條建表語句,news表也就創建成功了。這條建表語句雖然簡單,但是里面還是包含了一些小的細節,我來解釋一下。首先,根據數據庫的范式要求,任何一張表都應該是有主鍵的,所以這里我們添加了一個自增長的id列,并把它設為主鍵。然后title列和content列都是字符串類型的,commentcount列是整型的,這都很好理解,但是publishdate列該怎么設計呢?由于SQLite中并不支持存儲日期這種數據類型,因此我們需要將日期先轉換成UTC時間(自1970年1月1號零點)的毫秒數,然后再存儲到數據庫中,因此publishdate列也應該是整型的。

  • 接著,我們只需要獲取到SQLiteDatabase的實例,數據庫表就會自動創建了,如下所示:
SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 1);
// "demo.db" 就是數據庫名
        
 SQLiteDatabase db = dbHelper.getWritableDatabase();
  • 感覺很簡單很方便是嗎?那你就太容易滿足了,下面我們就來學習一下LitePal的基本用法,看一看使用這個框架是如何實現同樣的功能的。

1.2 使用LitePal建表

  • 雖說LitePal宣稱是近乎零配置,但也只是“近乎”而已,它還是需要進行一些簡單配置才可以使用的,那么我們第一步就先快速學習一下LitePal的配置方法。

1.2.1 快速配置

1. 引入Jar包或源碼

  • 首先我們需要將LitePal的jar包引入到項目當中,可以點擊這里查看LitePal的最新版本,選擇你需要的下載即可。下載好了jar包之后,把它復制到項目的libs目錄中就算是引入成功了,如下圖所示:
    引入jar包
  • 如果不想用jar包的話,也可以把LitePal的源碼下載下來,然后作為一個library庫導入到Eclipse當中,再讓我們的項目去引用這個library庫就可以了。

2. 配置litepal.xml

  • 接著在項目的 assets目錄 (assets在main下)下面新建一個litepal.xml文件,并將以下代碼拷貝進去:
<?xml version="1.0" encoding="utf-8"?>  
<litepal>  
    <dbname value="demo" ></dbname>  
  
    <version value="1" ></version>  
  
    <list>  
    </list>  
</litepal>  
  • 配置文件相當簡單,<dbname>用于設定數據庫的名字,<version>用于設定數據庫的版本號,<list>用于設定所有的映射模型,我們稍后就會用到。

3. 配置LitePalApplication

  • 由于操作數據庫時需要用到 Context,而我們顯然不希望在每個接口中都去傳一遍這個參數,那樣操作數據庫就顯得太繁瑣了。因此,LitePal使用了一個方法來簡化掉Context這個參數,只需要在 AndroidManifest.xml 中配置一下 LitePalApplication,所有的數據庫操作就都不用再傳Context了,如下所示:
<manifest>  
    <application  
        android:name="org.litepal.LitePalApplication"  
        ...  
    >  
    ...  
    </application>  
</manifest>  
  • 當然,有些程序可能會有自己的Application,并在這里配置過了。比如說有一個MyApplication,如下所示:
<manifest>  
    <application  
        android:name="com.example.MyApplication"  
        ...  
    >  
    ...  
    </application>  
</manifest>  
  • 沒有關系,這時只需要修改一下MyApplication的繼承結構,讓它不要直接繼承Application類,而是繼承LitePalApplication類,就可以使用一切都能正常工作了,代碼如下所示:
public class MyApplication extends LitePalApplication {  
    ...  
}  
  • 但是,有些程序可能會遇到一些更加極端的情況,比如說MyApplication需要繼承另外一個AnotherApplication,并且這個AnotherApplication還是在jar包當中的,不能修改它的代碼。這種情況應該算是比較少見了,但是如果你遇到了的話也不用急,仍然是有解釋方案的。你可以把LitePal的源碼下載下來,然后把src目錄下的所有代碼直接拷貝到你項目的src目錄下面,接著打開LitePalApplication類,將它的繼承結構改成繼承自AnotherApplication,再讓MyApplication繼承自LitePalApplication,這樣所有的Application就都可以在一起正常工作了。
  • 僅僅三步,我們就將所有的配置工作全部完成了,并且這是一件一本萬利的事情,自此以后,你就可以開心地體驗LitePal提供的各種便利了,就讓我們從建表開始吧。

1.2.2 LitePal建表

  • 前面在介紹的時候已經說了,LitePal采取的是對象關系映射(ORM)的模式,那么什么是對象關系映射呢?簡單點說,我們使用的編程語言是面向對象語言,而我們使用的數據庫則是關系型數據庫,那么將面向對象的語言和面向關系的數據庫之間建立一種映射關系,這就是對象關系映射了。

但是我們為什么要使用對象關系映射模式呢?這主要是因為大多數的程序員都很擅長面向對象編程,但其中只有少部分的人才比較精通關系型數據庫。而且數據庫的SQL語言晦澀難懂,就算你很精通它,恐怕也不喜歡經常在代碼中去寫它吧?而對象關系映射模式則很好地解決了這個問題,它允許我們使用面向對象的方式來操作數據庫,從而可以從晦澀難懂的SQL語言中解脫出來。

  • 那么接下來我們就看一看LitePal中是如何建表的吧。根據對象關系映射模式的理念,每一張表都應該對應一個 模型(Model),也就是說,如果我們想要建一張news表,就應該有一個對應的News模型類。新建一個 News類,如下所示:
package com.example.databasetest.model;  
public class News {  
}  
  • 然后,表中的每一列其實就是對應了模型類中的一個字段,比如news表中有id、title、content、publishdate、commentcount這幾個列,那么在News類中就也應該有這幾個字段,代碼如下所示:
public class News {  
      
    private int id;  
      
    private String title;  
      
    private String content;  
      
    private Date publishDate;  
      
    private int commentCount;  
      
    // 自動生成get、set方法  
    ...  
}  

其中id這個字段可寫可不寫,因為即使不寫這個字段,LitePal也會在表中自動生成一個id列,畢竟每張表都一定要有主鍵的嘛。

  • 這里要特別說明一下,LitePal的映射規則是非常輕量級的,不像一些其它的數據庫框架,需要為每個模型類單獨配置一個映射關系的XML,LitePal的所有映射都是自動完成的。根據LitePal的數據類型支持,可以進行對象關系映射的數據類型一共有8種,int、short、long、float、double、boolean、String和Date。只要是聲明成這8種數據類型的字段都會被自動映射到數據庫表中,并不需要進行任何額外的配置。

  • 現在模型類已經建好了,我們還差最后一步,就是將它配置到映射列表當中。編輯assets目錄下的litepal.xml文件,在<list>標簽中加入News模型類的聲明:

<?xml version="1.0" encoding="utf-8"?>  
<litepal>  
    <dbname value="demo" ></dbname>  
  
    <version value="1" ></version>  
  
    <list>  
        <mapping class="com.example.databasetest.model.News"></mapping>  
    </list>  
</litepal>  

注意這里一定要填入News類的完整類名。

  • OK,這樣所有的工作就都已經完成了,現在只要你對數據庫有任何的操作,news表就會被自動創建出來。比如說LitePal提供了一個便捷的方法來獲取到SQLiteDatabase的實例,如下所示:
 //LitePal 獲取實例
 SQLiteDatabase db = Connector.getDatabase();
  • 調用一下上述代碼,news表就應該已經創建成功了。我們使用SQLite命令來查看一下,打開demo.db數據庫,輸入.table命令,結果如下圖所示:


    20140907222357271.png
  • 可以看到,news表已經存在了。另外兩張android_metadata和table_schema表是自動生成的,我們不用理。接下來我們還可以再查詢一下news表的建表語句,如下圖所示:


    20140907222630369.png
  • 這就是LitePal根據News類中的字段自動幫我們生成的建表語句,由此也說明,建表操作已經成功完成了。

2、升級表

  • 創建表只是數據庫操作中最基本的一步而已,我們在一開始創建的表結構,隨著需求的變更,到了后期是極有可能需要修改的。因此,升級表的操作對于任何一個項目也是至關重要的,那么今天我們就一起來學習一下,在Android傳統開發當中升級表的方式,以及使用LitePal來進行升級表操作的用法。

2.1 傳統的升級表方式

  • 上一節文章中我們借助MySQLiteHelper已經創建好了news這張表,這也是demo.db這個數據庫的第一個版本。然而,現在需求發生了變更,我們的軟件除了能看新聞之外,還應該允許用戶評論,所以這時就需要對數據庫進行升級,添加一個comment表。

  • 該怎么做呢?添加一個comment表的建表語句,然后在onCreate()方法中去執行它?沒錯,這樣的話,兩張表就會同時創建了,代碼如下所示:

public class MySQLiteHelper extends SQLiteOpenHelper {

    //建news表語句
    public static final String NEWS_TABLE = "create table news("
            + "id integer primary key autoincrement,"
            + "title text,"
            + "content text,"
            + "publishdata integer,"
            + "commentcount integer)";

    //建comment表語句
    public static final String COMMENT_TABLE = "create table comment("
            + "id integer primary key autoincrement,"
            + "content text)";

    public MySQLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    //實現創建的邏輯
    @Override
    public void onCreate(SQLiteDatabase db) {
        //創建名為 “news” 的表
        db.execSQL(NEWS_TABLE);

        //創建名為 “comment” 的表
        db.execSQL(COMMENT_TABLE);
    }

    //實現升級數據庫的邏輯
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
  • 這對于第一次安裝我們軟件的用戶來說是完全可以正常工作的,但是如果有的用戶已經安裝過上一版的軟件,那么很遺憾,comment表是創建不出來的,因為之前數據庫就已經創建過了,onCreate()方法是不會重新執行的。

  • 對于這種情況我們就要用升級的方式來解決了,看到MySQLiteHelper構造方法中的第四個參數了嗎,這個就是數據庫版本號的標識,每當版本號增加的時候就會調用onUpgrade()方法,我們只需要在這里處理升級表的操作就行了。比較簡單粗暴的方式是將數據庫中現有的所有表都刪除掉,然后重新創建,代碼如下所示:

public class MySQLiteHelper extends SQLiteOpenHelper {

    .....

    //實現創建的邏輯
    @Override
    public void onCreate(SQLiteDatabase db) {
        //創建名為 “news” 的表
        db.execSQL(NEWS_TABLE);

        //創建名為 “comment” 的表
        db.execSQL(COMMENT_TABLE);
    }

    //實現升級數據庫的邏輯
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //刪除 “news” 表
        db.execSQL("drop table if exists news");
        
        //調用onCreate重新建表
        onCreate(db);
    }
}

可以看到,當數據庫升級的時候,我們先把news表刪除掉,然后重新執行了一次onCreate()方法,這樣就保證數據庫中的表都是最新的了。

  • 但是,如果news表中本來已經有數據了,使用這種方式升級的話,就會導致表中的數據全部丟失,所以這并不是一種值得推薦的升級方法。那么更好的升級方法是什么樣的呢?這就稍微有些復雜了,需要在onUpgrade()方法中根據版本號加入具體的升級邏輯,我們來試試來吧。比如之前的數據庫版本號是1,那么在onUpgrade()方法中就可以這樣寫:
public class MySQLiteHelper extends SQLiteOpenHelper {

    ......

    //實現創建的邏輯
    @Override
    public void onCreate(SQLiteDatabase db) {
        //創建名為 “news” 的表
        db.execSQL(NEWS_TABLE);

        //創建名為 “comment” 的表
        db.execSQL(COMMENT_TABLE);
    }

    //實現升級數據庫的邏輯
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion) {
            case 1:
                db.execSQL(COMMENT_TABLE);
            default:
        }
    }
}
  • 可以看到,這里在 onUpgrade() 方法中加入了一個switch判斷,如果oldVersion等于1,就再創建一個comment表。現在只需要調用如下代碼,表就可以得到創建或升級了:
SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 2);
        
 SQLiteDatabase db = dbHelper.getWritableDatabase();

這里我們將版本號加1,如果用戶是從舊版本升級過來的,就會新增一個comment表,而如果用戶是直接安裝的新版本,就會在onCreate()方法中把兩個表一起創建了。

  • OK,現在軟件的第二版本也發布出去了,可是就在發布不久之后,突然發現 comment表中少了一個字段,我們并沒有記錄評論發布的時間。沒辦法,只好在第三版中修復這個問題了,那我們該怎么樣去添加這個字段呢?主要需要修改comment表的建表語句,以及onUpgrade()方法中的邏輯,代碼如下所示:
public class MySQLiteHelper extends SQLiteOpenHelper {

    ......

    //建comment表語句
    public static final String COMMENT_TABLE = "create table comment("
            + "id integer primary key autoincrement,"
            + "content text,"
            + "publishdate integer)";   //增加字段

    ......

    //實現升級數據庫的邏輯
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion) {
            case 1:
                db.execSQL(COMMENT_TABLE);
                //case中別用break
            case 2:
                //增加字段
                db.execSQL("alter table comment add column publishdate integer");
                //case中別用break
            default:
            break;//這這里break,防止從1~3的情況()
        }
    }
}
  • 可以看到,在建表語句當中我們新增了publishdate這一列,這樣當執行onCreate()方法去創建表的時候,comment表中就會有這一列了。那么如果是從舊版本升級過來的呢?也沒有問題,我們在onUpgrade()方法中已經把升級邏輯都處理好了,當oldVersion等于2的時候,會執行alter語句來添加publishdate這一列。現在調用以下代碼來創建或升級數據庫:
SQLiteOpenHelper dbHelper = new MySQLiteHelper(this, "demo.db", null, 3);

  SQLiteDatabase db = dbHelper.getWritableDatabase();

將數據庫版本號設置成3,這樣就可以保證數據庫中的表又是最新的了。

  • 現在我們已經學習了新增表和新增列這兩種升級方式,那么如果是某張表中的某一列已經沒有用了,我想把這一列刪除掉該怎么寫呢?很遺憾,SQLite并不支持刪除列的功能,對于這情況,多數軟件采取的作法是無視它,反正以后也用不到它了,留著也占不了什么空間,所以針對于這種需求,確實沒什么簡單的解決辦法。

  • 這大概就是傳統開發當中升級數據庫表的方式了,雖說能寫出這樣的代碼表示你已經對數據庫的升級操作理解的比較清楚了,但隨著版本越來越多,onUpgrade()方法中的邏輯也會變得愈發復雜,稍微一不留神,也許就會產生錯誤。因此,如果能讓代碼自動控制升級邏輯,而不是由人工來管理,那就是再好不過了,那么下面我們就來學習一下怎樣使用LitePal來進行升級表的操作。

2.2 使用LitePal升級表

  • 通過上文的學習,我們已經知道LitePal是一款ORM模式的框架了,已經熟悉創建表流程的你,相信對于升級表也一定會輕車熟路的。那么為了模仿傳統升級表方式中的需求,現在我們也需要創建一張comment表。第一步該怎么辦呢?相信你也許早就已經猜到了,那當然是先創建一個Comment類了,如下所示:
package com.example.databasetest.model;  
public class Comment {  
      
    private int id;  
      
    private String content;  
      
    // 自動生成get、set方法   
    ...  
}  

OK,Comment類中有id和content這兩個字段,也就意味著comment表中會有id和content這兩列。

  • 接著修改litepal.xml中的配置,在映射列表中新增Comment類,并將版本號加1,如下所示:
<?xml version="1.0" encoding="utf-8"?>  
<litepal>  
    <dbname value="demo" ></dbname>  
  
    <version value="2" ></version>  
  
    <list>  
        <mapping class="com.example.databasetest.model.News"></mapping>  
        <mapping class="com.example.databasetest.model.Comment"></mapping>  
    </list>  
</litepal>  
  • 沒錯,就是這么簡單,僅僅兩步,升級的操作就已經完成了,現在我們只需要操作一下數據庫,comment表就會自動生成了,如下所示:
//LitePal 獲取實例
 SQLiteDatabase db = Connector.getDatabase();
  • 那么我們還是通過.table命令來查看一下結果,如下圖所示:


    20140914155958796.png
  • OK,comment表已經出來了,那么再通過pragma命令來查看一下它的表結構吧:


    20140914160140852.png

沒有問題,comment表中目前有id和content這兩列,和Comment模型類中的字段是保持一致的。

  • 那么現在又來了新的需求,需要在 comment表中添加一個publishdate列,該怎么辦呢?不用懷疑,跟著你的直覺走,相信你已經猜到應該在Comment類中添加這樣一個字段了吧,如下所示:
public class Comment {  
      
    private int id;  
      
    private String content;  
      
    private Date publishDate;  
      
    // 自動生成get、set方法   
    ...  
}  
  • 然后呢?剩下的操作已經非常簡單了,只需要在litepal.xml中對版本號加1就行了,如下所示:
<litepal>  
    <dbname value="demo" ></dbname>  
  
    <version value="3" ></version>  
    ...  
</litepal>  
  • 這樣當我們下一次操作數據庫的時候,publishdate列就應該會自動添加到comment表中。調用Connector.getDatabase()方法,然后重新查詢comment表結構,如下所示:


    20140914161913495.png

可以看到,publishdate這一列確實已經成功添加到comment表中了。

2.3 兩種方式總結

  • 通過這兩種升級方式的對比,相信你已經充分體會到了使用LitePal進行升級表操作所帶來的便利了吧。我們不需要去編寫任何與升級相關的邏輯,也不需要關心程序是從哪個版本升級過來的,唯一要做的就是確定好最新的Model結構是什么樣的,然后將litepal.xml中的版本號加1,所有的升級邏輯就都會自動完成了。LitePal確實將數據庫表的升級操作變得極度簡單,使很多程序員可以從維護數據庫表升級的困擾中解脫出來。

  • 然而,LitePal卻明顯做到了更好。前面我們提到過關于刪除列的問題,最終的結論是無法解決,因為SQLite是不支持刪除列的命令的。但是如果使用LitePal,這一問題就可以簡單地解決掉,比如說publishdate這一列我們又不想要了,那么只需要在Comment類中把它刪除掉,然后將版本號加1,下次操作數據庫的時候這個列就會不見了。

  • 那么有的朋友可能會問了,不是說SQLite不支持刪除列的命令嗎?那LitePal又是怎樣做到的呢?其實LitePal并沒有刪除任何一列,它只是先將comment表重命名成一個臨時表,然后根據最新的Comment類的結構生成一個新的comment表,再把臨時表中除了publishdate之外的數據復制到新的表中,最后把臨時表刪掉。因此,看上去的效果好像是做到了刪除列的功能。

  • 這也是使用框架的好處,如果沒有框架的幫助,我們顯然不會為了刪除一個列而大廢周章地去寫這么多的代碼,而使用框架的話,具體的實現邏輯我們已經不用再關心,只需要控制好模型類的數據結構就可以了。

  • 另外,如果你想刪除某一張表的話,操作也很簡單,在litepal.xml中的映射列表中將相應的類刪除,表自然也就不存在了。其它的一些升級操作也都是類似的,相信你已經能舉一反三,這里就不再贅述了。

3、建立表關聯

3.1 關聯關系的基礎知識

  • 喜歡把所有的代碼都寫在一個類里的程序員肯定是個新手。沒錯,任何一個像樣的程序都不可能僅僅只有一個類的,同樣地,任何一個像樣的數據庫也不可能僅僅只有一張表。我們都知道,在面向對象的編程語言中,多個類之間可以相互關聯引用,共同完成某項功能。那么在數據庫當中,多個表之間可以相互關聯嗎?當然可以!只不過表與表之間的關聯關系要比對象之間的關聯關系復雜一些,也更加難懂,但是作為數據庫的基本功,還是應該了解清楚的,那么我們就先來學習一下數據庫表關聯的基礎知識。

  • 表與表之間的關聯關系一共有三種類型,一對一、多對一、和多對多,下面我們分別對這三種類型展開進行討論。

一對一

  • 表示兩個表中的數據必須是一一對應的關系。這種場景其實并不是很常見,我們還是通過例子來直觀地體會一下,例子仍然是在之前文章的基礎上展開的。

  • 現在我們已經創建好了news這張表,里面主要記錄了新聞的標題和內容,那么除了標題和內容之外,有些新聞還可能帶有一些導語和摘要,我們把這兩個字段放在一張introduction表中,作為新聞的簡介。那么很顯然,news表和introduction表就是一對一的關系了,因為一條新聞只能對應一個簡介,一個簡介也只能屬于一條新聞。它們之間的對應關系大概如下圖描述的一樣:


    一對一

可以看到,News1對應了Introduction2,News2對應了Introduction3,News3對應了Introduction1,但不管怎么樣,它們都是一對一的關系。

  • 那么這種一對一的關系,在編程語言中該怎么體現出來呢?相信熟悉面向對象設計的你,一定很輕松就能想出來吧,只需要在News類中持有一個Introduction類的引用,然后在Introduction類中也持有一個News類的引用,這樣它們之間自然就是一對一的關系了。

  • 沒錯,對象之間的一對一關系非常簡單易懂,那么難點就在于,如何在數據庫表中建立這樣的一對一關系了。由于數據庫并不像面向對象的語言一樣支持相互引用,如果想讓兩張表之間建立一對一的關系,一般就只能通過外鍵的方式來實現了。因此,一對一關系的表結構就可以這樣設計:


    一對一表結構
  • 請注意,introduction表中有一個news_id列,這是一個外鍵列,里面應該存放一個具體的新聞id,這樣一條introduction就能對應一條news,也就實現一對一的關系了,如下圖所示:


  • 由此我們就能夠看出,id為1的introduction對應著id為2的news,id為2的introduction對應著id為3的news,id為3的introduction對應著id為1的news。需要注意的是,一對一的關系并沒有強制要求外鍵必須加在哪一張表上,你可以在introduction表中加一個news_id作為外鍵,也可以在news表中加一個introduction_id作為外鍵,不管使用哪一種,都可以表示出它們是一對一的關聯關系。

多對一

  • 表示一張表中的數據可以對應另一張表中的多條數據。這種場景比起一對一關系就要常見太多了,在我們平時的開發工作中多對一關系真的是比比皆是。比如說現在我們的數據庫中有一個news表,還有一個comment表,它們兩個之間就是典型的多對一關系,一條新聞可以有很多條評論,但是一條評論只能是屬于一條新聞的。它們的關系如下圖所示:


    多對一
  • 而這種多對一的關系在編程語言中是非常容易體現出來的,比如Java中就有專門集合類,如List、Set等,使用它們的話就能輕松簡單地在對象之間建立多對一的關系,我們稍后就會看到。那么,這里的難點仍然是在數據庫表中如何建立這樣的多對一關系。現在說難點其實已經不難了,因為前面我們已經學會了一對一關系的建立方法,而多對一也是類似的。沒錯,數據庫表中多對一的關系仍然是通過外鍵來建立的,只不過一對一的時候外鍵加在哪一張表上都可以,但多對一的時候關鍵必須要加在多方的表中。因此,多對一關系的表結構就可以這樣設計:

    表結構

  • 在comment表中有一個news_id列,這是一個外鍵列,里面應該存放一個具體的新聞id,并且允許多條comment都存放同一個新聞id,這樣一條評論就只能對應一條新聞,但一條新聞卻可以有多條評論,也就實現多對一的關系了,如下圖所示:


  • 由此我們就可以看出,id為1、2、3的三條評論是屬于第一條新聞的,而id為4、5的兩條評論是屬于第二條新聞的。

多對多

  • 表示兩張關聯表中的數據都可以對應另一張表中的多條數據。這種場景也不算是很常見,但比一對一關系要稍微更加常用一些。舉個例子,我們都知道新聞網站是會將新聞進行種類劃分的,這樣用戶就可以選擇自己喜歡的那一類新聞進行瀏覽,比如說網易新聞中就會有頭條、科技、娛樂、手機等等種類。每個種類下面當然都會有許多條新聞,而一條新聞也可能是屬于多個種類的,比如iPhone6發布的新聞既可以屬于手機種類,也可以屬于科技種類,甚至還可以上頭條。因此,新聞和種類之間就是一種多對多的關系,如下圖所示:


    多對多
  • 可以看到,News1是屬于Category1的,而News2和News3都是既屬于Category1也屬于Category2,如此復雜的關聯關系該如何表示呢?在面向對象的編程語言中一切都是那么的簡單,只需要在News類中使用集合類聲明擁有多個Category,然后在Category類中也使用集合類聲明擁有多個News就可以了,我們稍后就會看到。而難點仍然是留在了數據庫上,兩張表之間如何建立多對多的關聯關系呢,還是用外鍵嗎?肯定不行了,多對多的情況只能是借助中間表來完成了。也就是說,我們需要多建立一張表,這張表沒什么其它作用,就是為了存放news表和category表之間的關聯關系的,如下圖所示:


    表結構
  • 注意這里我們建立一張名為category_news的中間表,中間表的命名并沒有什么強制性的約束,但一個良好的命名規范可以讓你一眼就明白這張表是用來做什么的。中間表里面只有兩列,而且也只需要有兩列,分別是news表的外鍵和category表的外鍵,在這里存放新聞和種類相應的id,就可以讓它們之間建立關聯關系了,如下圖所示:


  • 由此我們就可以看出,第一條新聞是屬于第一個種類的,而第二和第三條新聞,則既屬于第一個種類,也屬于第二個種類。反過來也可以這樣看,第一個種類下面有第一、第二、第三這三條新聞,而第二個種類下面只有第二、第三這兩條新聞。不管怎么看,多對多的關系都是成立的。

  • 好了,三種關聯關系都講完了,那我們來簡單總結一下吧。雖說上面介紹了花了很大的篇幅講解數據庫的表關聯知識,但其實最后的結論是非常簡單的,大家可以當成口訣一樣背下來。即一對一關聯的實現方式是用外鍵,多對一關聯的實現方式也是用外鍵,多對多關聯的實現方式是用中間表。記下了這個口訣,在很多數據庫設計的時候,你都可以發揮得更加游刃有余。

3.2 使用LitePal建立表關聯

  • 雖說口訣就是這個樣子,但牽扯到表關聯的時候畢竟增加了建表的難度,建表語句會更加復雜,你也需要格外地小心以防止出現什么錯誤。因此,使用LitePal來自動建立表關聯又是一個非常不錯的選擇,我們不需要關心什么外鍵、中間表等實現的細節,只需要在對象中聲明好它們相互之間的引用關系,LitePal就會自動在數據庫表之間建立好相應的關聯關系了,下面我們就來嘗試一下吧。

  • 首先確定一下一共涉及到了哪些實體類,News和Comment,這兩個類我們在前兩篇文章中就已經建好了,然后還需要有Introduction和Category這兩個類,新建Introduction類,代碼如下所示:

public class Introduction {  
      
    private int id;  
      
    private String guide;  
      
    private String digest;  
      
    // 自動生成get、set方法
    ......  
}  
  • 接著新建Category類,代碼如下所示:
public class Category {  
      
    private int id;  
      
    private String name;  
      
    // 自動生成get、set方法 
    ...... 
}  
  • 現在四個類都已經建好了,但目前它們都還是各自獨立的,互相之間沒有任何聯系,那么我們現在就開始用極為簡單易懂的方式來給它們建立關聯吧。

a、一對一關系:

首先,News和Introduction是一對一的關系,那就可以在News類中添加如下引用:

public class News {  
    ...  
    //News 和 Intorduction 是一對一的關系
    private Introduction introduction;  
      
    // 自動生成get、set方法  
}  
  • 就是這么簡單,在News類中可以得到一個對應的Introduction的實例,那么它們之間就是 一對一關系 了。

b、多對一關系

  • 接著 Comment和News是多對一的關系,因此 News中應該包含多個Comment,而Comment中應該只有一個News,所以就可以這樣寫:
public class News {  
    ......

    //News 和 Intorduction 是一對一的關系
    private Intorduction intorduction;

    //News 和 Comment 是一對多的關系
    private List<Comment> commentList=new ArrayList<>();
      
    // 自動生成get、set方法  
    ......
}  
  • 先使用 一個泛型為Comment的List集合來表示News中包含多個Comment,然后修改Comment類的代碼,如下所示:
public class Comment {  
    ......

    //Comment 和 News 是多對一的關系
    private News news;  
      
    // 自動生成get、set方法   
    ......
}  
  • 在Comment類中聲明了一個News的實例,這樣就清楚地表示出了News中可以包含多個Comment,而Comment中只能有一個News,也就是多對一的關系了。

c、多對多關系

  • 最后 News和Category是多對多的關系,相信聰明的你一定已經知道該怎么寫了。News中可以包含多個Category,所以仍然應該使用List集合來表示:
public class News {  
    ......

    //News 和 Introduction是一對一關系
    private Introduction introduction;  
     
    //News 和 Comment是多對一關系
    private List<Comment> commentList = new ArrayList<Comment>();  
      
    //News 和 Category是多對多關系
    private List<Category> categoryList = new ArrayList<Category>();  
      
    // 自動生成get、set方法 
    ...... 
}  
  • 而Category中也可以包含多個News,因此Category類也應該使用相同的寫法,如下所示:
public class Category {  
    ......

    //Category 和 News 是多對多關系  
    private List<News> newsList = new ArrayList<News>();  
      
    // 自動生成get、set方法
    ......  
} 
  • 這樣就清楚地表達出它們之間是多對多的關聯了。

  • 關聯關系都聲明好了之后,我們只需要將所有的實體類都添加到映射列表當中,并將數據庫版本號加1就可以了。修改litepal.xml的代碼,如下所示:

<?xml version="1.0" encoding="utf-8"?>  
<litepal>  
    <dbname value="demo" ></dbname>  
  
    <version value="4" ></version>  
  
    <list>  
        <mapping class="com.example.databasetest.model.News"></mapping>  
        <mapping class="com.example.databasetest.model.Comment"></mapping>  
        <mapping class="com.example.databasetest.model.Introduction"></mapping>  
        <mapping class="com.example.databasetest.model.Category"></mapping>  
    </list>  
</litepal>  
  • 基本上到這里就可以輕松地說結束了,現在只需要任意操作一下數據庫,表之間的關聯關系就將會自動建立,比如說調用一下Connector.getDatabase()方法。

  • 下面我們來驗證一下吧,輸入.table命令查看一下當前數據庫中的表,如下所示:


  • OK,news、comment、category、introduction這幾張表全都有了,除此之外還有一張category_news中間表。那我們要來一一檢查一下了,先查看一下introduction表的結構吧,如下所示:


可以看到,多了一個news_id列,說明introduction表和news表之間的一對一關系已經建立好了。

  • 然后再檢查一下comment表的結構,如下所示:


OK,comment表中也有一個news_id的列,那么comment表和news表之間的多對一關系也已經建立好了。

  • 最后檢查一下category_news這張中間表的結構,如下所示:


一共只有兩列,一列是news_id,一列是category_id,分別對應著兩張表的外鍵,這樣news表和category表的多對多關系也建立好了。

  • 借助LitePal的幫助,即使你并不熟悉數據庫的表關聯設計,只要你會面向對象編程,都可以輕松地將表與表之間的關聯建立起來。創建表、升級表、表關聯,這就是LitePal在數據庫表管理方面給我們帶來的巨大便利,相信大家都能體會到它的魅力所在了。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容