1. 程式人生 > >Android GreenDao實現CRUD和升級詳解

Android GreenDao實現CRUD和升級詳解

我們不論在學習Android還是在開發應用的過程中或多或少的會接觸到一些SQLite。增(insert)、刪(delete)、改(update)、查(query),當然如果我們在使用的過程中想要新增欄位的話,離不了資料庫的升級(onUpgrade)。下面我們就使用GreenDao來實現我們的增刪改查以及資料庫的升級。
點選前往greenDAO官網
GreenDao的有以下優點:
效能最大化
記憶體開銷最小化
易於使用的 APIs
對 Android 進行高度優化
支援資料庫加密 greendao支援SQLCipher進行資料庫加密
要想使用GreenDao需要進行一些配置
下面就以我的demo為例:
在app的src/main 目錄下新建一個與 java 同層級的(java-gen)目錄,用於存放由 greenDAO 生成的 Bean、DAO、DaoMaster、DaoSession 等類。
這裡寫圖片描述


需要新建一個Moudle,操作順序為:File -> New -> New Module -> Java Library -> 填寫相應的包名與類名 -> Finish。一定要注意是Java Library Java Library Java Library !!!
在新建Moudle的build.gradle檔案裡面需要配置
這裡寫圖片描述
程式碼為

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'de.greenrobot:greendao-generator:2.1.0'
}

在app的build.gradle檔案裡面也需要配置如下內容
這裡寫圖片描述
程式碼如下

sourceSets{
     main {
             java.srcDirs = ['src/main/java', 'src/main/java-gen']
         }
        }
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:25.1.1'
    compile 'de.greenrobot:greendao:2.1.0'
}

配置好以後我們就在新建的Module 的java工程裡面進行操作,這個java工程其實只有一個類,就是你在新建Module 的時候填寫的類名,在這個類裡面,它的內容決定了(GreenDao Generator)的輸出,你可以在這個類中通過物件、關係等建立資料庫結構。
關於java類裡面的內容請看下面的程式碼以及註釋

public class LyxDaoMasker {
    public static void main(String[]args){
        //兩個引數分別代表:資料庫版本號與自動生成程式碼的包路徑。
        Schema schema = new Schema(1,"com.lyx.bean");
        schema.setDefaultJavaPackageDao("com.lyx.dao");
        addBean(schema);
        try {
            /**
             * 最後我們將使用DAOGenerator類的generateAll()方法自動生成程式碼,
             * 此處你需要根據自己的情況更改輸出目錄(既之前建立的java-gen)。
             * 其實,輸出目錄的路徑可以在build.gradle中設定。請看上面的截圖
             */
            new DaoGenerator().generateAll(schema,"D:/work/GreenDao/app/src/main/java-gen");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static  void addBean(Schema schema){
        Entity entity = schema.addEntity("Student");//一個實體(類)就關聯到資料庫中的一張表,表名為Student
        //以下配置為資料庫表中的欄位
        entity.addIdProperty();
        entity.addStringProperty("name");
        entity.addStringProperty("address");
        entity.addIntProperty("age");
    }
}

點選滑鼠右鍵執行如下操作
這裡寫圖片描述
當你的控制檯輸出以下日誌內容表示成功
這裡寫圖片描述
當我們再次回到我們的java-gen這個資料夾檢視時,可以看到裡面多了一些資料夾和類,是不是感覺很熟悉啊?沒錯,這些資料夾和類就是我們在java工程裡面通過程式碼設定的。
這裡寫圖片描述
別看到類爆紅就害怕,這些類生成後雖然爆紅但是影響並不大,我們可以在main資料夾下面的java下面新建一個包,然後我們只需要把這些類移動到我們新建的包下就OK了。
僅僅只有上面的一些類還是遠遠不夠的,我們還需要通過上面的類進行一系列的操作

public class DaoManager {
    private static final String DB_NAME="lyx.sqlite";
    private volatile static DaoManager daoManager;
    private static DaoMaster.DevOpenHelper helper;
    private static DaoMaster daoMaster;
    private static DaoSession daoSession;
    private Context context;
    public static synchronized DaoManager getInstance(){
            if (daoManager == null) {
                daoManager = new DaoManager();
            }
        return daoManager;
    }
    public void initContext(Context context){
        this.context = context;
    }
    public DaoMaster getDaoMaster(){
        if (daoMaster == null){
            //通過DaoMaster的內部類DevOpenHelper,我們可以得到一個SQLiteOpenHelper物件。
            helper = new DaoMaster.DevOpenHelper(context,DB_NAME,null);
            daoMaster =new DaoMaster(helper.getWritableDatabase());
        }
        return daoMaster;
    }
    public void setUpgrade(){

        LYXOpenHelper helper = new LYXOpenHelper(context,DB_NAME , null);
        SQLiteDatabase sqlDB = helper.getWritableDatabase();
    }
    public DaoSession getDaoSession(){
        if (daoSession == null){
            if (daoMaster == null){
                daoMaster = getDaoMaster();
            }
            daoSession = daoMaster.newSession();
        }
        return daoSession;
    }
    public void setDebug(){
        /**如果你的query沒有返回期望的結果,這裡有兩個靜態的flag,
         * 可以開啟QueryBuilder身上的SQL和引數的log。
         * 它們會在任何build方法呼叫的時候打印出SQL命令和傳入的值。
         * 這樣你可以對你的期望值進行對比,或許也能夠幫助你複製SQL語句到某些
         * SQLite 資料庫的檢視器中,執行並獲取結果,以便進行比較。
         */
        QueryBuilder.LOG_SQL = true;
        QueryBuilder.LOG_VALUES = true;
    }
    public void closeConnection(){
        closeHelper();
        closeSession();
    }
    public void closeHelper(){
        if (helper!=null){
            helper.close();
            helper = null;
        }
    }
    public void closeSession(){
        if (daoSession!=null){
            daoSession.clear();
            daoSession = null;
        }
    }
}

下面我們在新建一個工具類把我們的操作資料庫的GreenDao語句封裝在裡面

public class CommonUtils {
    private DaoManager daoManager;
    private StudentDao dao;
    private DaoSession daoSession;
    public CommonUtils(Context context) {
        daoManager = DaoManager.getInstance();
        daoManager.initContext(context);
        dao = daoManager.getDaoSession().getStudentDao();
        daoSession = daoManager.getDaoSession();
    }
    public void insertStudent(Student student){
        dao.insert(student);
    }
    public void insertMultiStudent(final List<Student> sList){
        daoManager.getDaoSession().runInTx(new Runnable() {
            @Override
            public void run() {
                for (Student stu:sList) {
                    dao.insertOrReplace(stu);
                }
            }
        });
    }
    public void deleteStudent(Student student){
        dao.delete(student);
    }
    public void modifyStudent(Student student){
        dao.update(student);
    }
    public void queryStudent( long id){
        Student student = daoSession.load(Student.class, id);
    }
    public void queryStudent1(){
       List<Student>sList =daoSession.queryRaw(Student.class,"where name like ? and _id>?",new String[]{"%張%","1001L"});
    }

    public void queryAllStudent(){
        List<Student> sList =  daoSession.loadAll(Student.class);
    }
    /**
     * whereOr語句相當於select *from where name like ? or name = ? or age>?
     * ge是 >= 、like 則是包含的意思
     * whereOr是或的意思,比如下面的whereOr的意思就是age>=22||name 包含 張三
     * where則是age>=22 && name 包含 張三
     *greenDao除了ge和like操作之外還有很多其他方法
     * eq == 、 noteq != 、  gt >  、lt <  、le  <=  、between 倆者之間
     * in  在某個值內   、notIn  不在某個值內
     */
    public void queryStudent2(){
        QueryBuilder<Student> builder = daoSession.queryBuilder(Student.class);
        List<Student> sList = builder.where(StudentDao.Properties.Age.ge(22)).where(StudentDao.Properties.Name.like("張三")).list();
//        List<Student> sList = builder.whereOr(StudentDao.Properties.Age.ge(22),StudentDao.Properties.Name.like("張三")).list();
    }
    public void onUpgrade(){
        daoManager.setUpgrade();
    }
}

以上是資料庫的CRUD操作,如果資料庫需要升級怎麼辦呢?
比如說我們想增加一個sex欄位,需要如下圖所示
這裡寫圖片描述
在java工程的類裡面需要新增entity.addStringProperty(“sex”);這樣一句程式碼,然後按照上面的執行即可生成上面的四個類StudentDao.java、Student.java、DaoMaster.java、DaoSession.java。然後將之前的四個類替換掉並且更重要的一點就是需要在DaoMaster這個裡面將SCHEMA_VERSION 的由1值修改為 2,後續依次累加
這裡寫圖片描述
也可以直接在java工程的類裡面直接修改
這裡寫圖片描述

由於DaoMaster內部類OpenHelper裡面的onUpgrade對資料哭升級的操作是刪除所有表,然後重新建立。程式碼如下:

 @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
     dropAllTables(db, true);
     onCreate(db);
 }

但是我們升級資料庫的同時並不想刪除我們資料庫裡面已有的資料,因此資料庫的升級我們需要新建一個類繼承DaoMaster.OpenHelper,升級就在這個類裡面操作

public class LYXOpenHelper extends DaoMaster.OpenHelper {

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

    /**
     * 資料庫升級
     * @param db
     * @param oldVersion
     * @param newVersion
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //操作資料庫的更新
        if (oldVersion<newVersion) {
            MigrationHelper.migrate(db, StudentDao1.class, StudentDao.class);
        }
    }
}

注意:

StudentDao為新生成的也就是java工程類裡面新新增欄位時生成的類,而StudentDao1則是剛開始生成的類。
僅僅只有上面一個類還是不行的我們還需要一個類對資料庫已有的內容進行操作,其實很簡單,就是新建一個臨時表,將原來已有的資料寫入到臨時表中,然後再將臨時表中的資料寫入到新表中。下面的程式碼是我在網上找的,效果還可以

public class MigrationHelper {

    /**
     * 呼叫升級方法
     * @param db
     * @param daoClasses 一系列dao.class
     */
    public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        //1 新建臨時表
        generateTempTables(db, daoClasses);
        //2 建立新表
        createAllTables(db, false, daoClasses);
        //3 臨時表資料寫入新表,刪除臨時表
        restoreData(db, daoClasses);
    }


    /**
     * 生成臨時表,儲存舊的表資料
     * @param db
     * @param daoClasses
     */
    private static void generateTempTables(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        //方法2
        for (int i=0;i<daoClasses.length;i++){
            DaoConfig daoConfig = new DaoConfig(db,daoClasses[i]);
            String tableName = daoConfig.tablename;
            if (!checkTable(db,tableName))
                continue;
            String tempTableName = daoConfig.tablename.concat("_TEMP");
            StringBuilder insertTableStringBuilder = new StringBuilder();
            insertTableStringBuilder.append("alter table ")
                    .append(tableName)
                    .append(" rename to ")
                    .append(tempTableName)
                    .append(";");
            db.execSQL(insertTableStringBuilder.toString());
        }
    }

    /**
     * 檢測table是否存在
     * @param db
     * @param tableName
     */
    private static Boolean checkTable(SQLiteDatabase db,String  tableName){
        StringBuilder query = new StringBuilder();
        query.append("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='").append(tableName).append("'");
        Cursor c = db.rawQuery(query.toString(), null);
        if (c.moveToNext()){
            int count = c.getInt(0);
            if(count>0){
               return true;
            }
            return false;
        }
        return false;
    }

    /**
     * 刪除所有舊錶
     * @param db
     * @param ifExists
     * @param daoClasses
     */
    private static void dropAllTables(SQLiteDatabase db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
        reflectMethod(db, "dropTable", ifExists, daoClasses);
    }

    /**
     * 建立新的表結構
     * @param db
     * @param ifNotExists
     * @param daoClasses
     */
    private static void createAllTables(SQLiteDatabase db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
        reflectMethod(db, "createTable", ifNotExists, daoClasses);
    }

    /**
     * 建立根刪除都在StudentDao聲明瞭,可以直接拿過來用
     * dao class already define the sql exec method, so just invoke it
     */
    private static void reflectMethod(SQLiteDatabase db, String methodName, boolean isExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
        if (daoClasses.length < 1) {
            return;
        }
        try {
            for (Class cls : daoClasses) {
                //根據方法名,找到宣告的方法
                Method method = cls.getDeclaredMethod(methodName, SQLiteDatabase.class, boolean.class);
                method.invoke(null, db, isExists);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 臨時表的資料寫入新表
     * @param db
     * @param daoClasses
     */
    private static void restoreData(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
        for (int i = 0; i < daoClasses.length; i++) {
            DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
            String tableName = daoConfig.tablename;
            String tempTableName = daoConfig.tablename.concat("_TEMP");
            if (!checkTable(db,tempTableName))
                continue;
            // get all columns from tempTable, take careful to use the columns list
            List<String> columns = getColumns(db, tempTableName);
            //新表,臨時表都包含的欄位
            ArrayList<String> properties = new ArrayList<>(columns.size());
            for (int j = 0; j < daoConfig.properties.length; j++) {
                String columnName = daoConfig.properties[j].columnName;
                if (columns.contains(columnName)) {
                    properties.add(columnName);
                }
            }
            if (properties.size() > 0) {
                final String columnSQL = TextUtils.join(",", properties);

                StringBuilder insertTableStringBuilder = new StringBuilder();
                insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
                insertTableStringBuilder.append(columnSQL);
                insertTableStringBuilder.append(") SELECT ");
                insertTableStringBuilder.append(columnSQL);
                insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
                db.execSQL(insertTableStringBuilder.toString());
            }
            StringBuilder dropTableStringBuilder = new StringBuilder();
            dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
            db.execSQL(dropTableStringBuilder.toString());
        }
    }

    private static List<String> getColumns(SQLiteDatabase db, String tableName) {
        List<String> columns = null;
        Cursor cursor = null;
        try {
            cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null);
            if (null != cursor && cursor.getColumnCount() > 0) {
                columns = Arrays.asList(cursor.getColumnNames());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (cursor != null)
                cursor.close();
            if (null == columns)
                columns = new ArrayList<>();
        }
        return columns;
    }
}

MainActivity類

package com.liuyongxiang.robert.greendao;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;

import com.liuyongxiang.robert.greendao.daomanager.CommonUtils;
import com.liuyongxiang.robert.greendao.lyxdao.Student;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends Activity {

    @BindView(R.id.btn_add)
    Button btnAdd;
    @BindView(R.id.btn_delete)
    Button btnDelete;
    @BindView(R.id.btn_modify)
    Button btnModify;
    @BindView(R.id.btn_query)
    Button btnQuery;
    @BindView(R.id.btn_multi_add)
    Button btnMultiAdd;
    @BindView(R.id.btn_multi_query)
    Button btnMultiQuery;
    @BindView(R.id.btn_add_param)
    Button btnAddParam;
    private CommonUtils commonUtils;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        commonUtils = new CommonUtils(this);
    }


    @OnClick(R.id.btn_add)
    void addData() {
        Student student = new Student();
        student.setId(1001L);
        student.setAddress("北京市昌平區" + 1 + "號院");
        student.setName("張三");
        student.setAge(22);
        commonUtils.insertStudent(student);
    }

    @OnClick(R.id.btn_multi_add)
    void addMultiData() {
        List<Student> sList = new ArrayList<>();
        for (int i = 1; i < 11; i++) {
            Student student = new Student();
            student.setAddress("北京市昌平區" + i + "號院");
            student.setName("張三" + i);
            student.setAge(22 + i);
            sList.add(student);
        }
        commonUtils.insertMultiStudent(sList);
    }

    @OnClick(R.id.btn_delete)
    void deleteData() {
        Student student = new Student();
        student.setId(1003L);
        commonUtils.deleteStudent(student);
    }

    @OnClick(R.id.btn_modify)
    void modifyData() {
        Student student = new Student();
        student.setId(1002L);
        student.setName("王五");
        student.setAddress("北京市朝陽區");
        student.setAge(26);
        commonUtils.modifyStudent(student);
    }

    @OnClick(R.id.btn_query)
    void queryData() {
        commonUtils.queryStudent(1001L);
    }

    @OnClick(R.id.btn_multi_query)
    void queryMultiData() {
        commonUtils.queryAllStudent();
    }
    @OnClick(R.id.btn_add_param)
    void addParamData(){
        commonUtils.onUpgrade();
        Student student = new Student();
        student.setAddress("北京市昌平區");
        student.setName("趙六");
        student.setAge(24);
        student.setSex("男");
        commonUtils.insertStudent(student);
    }
}

資料庫升級之前
這裡寫圖片描述
資料庫升級之後
這裡寫圖片描述

如有疑問歡迎留言!
掃一掃關注本人公眾號
這裡寫圖片描述

相關推薦

Android GreenDao實現CRUD升級

我們不論在學習Android還是在開發應用的過程中或多或少的會接觸到一些SQLite。增(insert)、刪(delete)、改(update)、查(query),當然如果我們在使用的過程中想要新增欄位的話,離不了資料庫的升級(onUpgrade)。下面我們就使

Android的Drawable分類使用

一、前言 最近在看關於Android的書籍,發現居然把Drawable當做一個章節來講,感覺沒有必要啊,Drawable不就是圖片引用嗎。深入理解後才發現我們平常用的只是比較常用和簡單的,Drawable還是有很多其他實現方式的。今天就詳細講解一下Drawable。 二、概述 其實D

Android Proguard工具使用配置

Android開發中的Proguard Proguard是Android開發時經常會用到的一個工具,在Android SDK中已經集成了一個免費的Proguard版本,位於<sdk>/tools/proguard目錄中。 在Android專案中,

Android WebView 快取機制模式

package com.example.webviewtest; import java.io.File; import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle;

Android ContentProvider 基本原理使用

ContentProvider(內容提供者)是 Android 的四大元件之一,管理 Android 以結構化方式存放的資料,以相對安全的方式封裝資料(表)並且提供簡易的處理機制和統一的訪問介面供其他程式呼叫。 Android 的資料儲存方式總共有五種,分別是:Shared Preferences、網路儲

Android檔案儲存的問題:ClassLoader實現Parcelable介面後 及用途

可能小夥伴們讀了我上一篇部落格關於Android檔案儲存的的,在檔案操作的時候大家有沒有疑問呀,有就對了,可能在儲存自定義物件的時候,如何序列化呀?ClassLoader到底是啥鬼呀?序列化後怎麼讀取出來呀?好吧,針對於上面的問題,我一個一個的說明吧! 今天主

Android中APK簽名工具之jarsignerapksigner

內容 value signature align light 文件簽名 item als release 一.工具介紹 jarsigner是JDK提供的針對jar包簽名的通用工具, 位於JDK/bin/jarsigner.exe apksigner是Google官方提

CGLIB實現AOP,MethodInterceptor介面Enhancer——Spring AOP(四)

上一章講到了使用JDK的Proxy實現AOP: https://blog.csdn.net/qq_34598667/article/details/83380628 這一章我們講另外一種方式,使用CGLIB實現AOP 使用CGLIB實現AOP功能 上一章我們已經說過,要產生

JDK的Proxy技術實現AOP,InvocationHandlerProxy——Spring AOP(三)

上一章已經講到了想要完成AOP的設計原理以及要實現AOP功能,得需要使用代理模式: 本章就介紹一個實現動態代理的兩種方式之一——JDK中的Proxy技術 AOP實現原理(使用JDK中的Proxy技術實現AOP功能,InvocationHandler和Proxy(Class)詳解

BFSDFS以及java實現(轉載)

作者: Leo-Yang 原文都先發布在作者個人部落格: http://www.leoyang.net/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利. 前言

Android 自定義view(1) --- Attr、StyleTheme

轉載:https://www.jianshu.com/p/dd79220b47dd 概念說明:       Attr:屬性,風格樣式的最小單元;      Style:風格,它是一系列Attr的集合用以定義一個View

Android開發之onTouchonClick

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android SDK 目錄作用

1、add-ons 這裡面儲存著附加庫,比如GoogleMaps,當然你如果安裝了OphoneSDK,這裡也會有一些類庫在裡面。 2、docs 這裡面是Android SDKAPI參考文件,所有的API都可以在這裡查到。 3、market_licensing 作為A

JWT——Token認證的兩種實現安全

前言: 最近因為專案中需要解決跨域取值的問題,所有考慮到用Token認證做技術支撐點,自己看了許多與之相關的文章,從中總結出了以下兩個要點(簽名和token時間)。在說這兩個要點之前先大概簡單說一下與之有關的一些問題。 首先,如果你對token認證的知識一點都不瞭解,那麼我覺得這篇文章還不太適合你,

rsync+inotify-toos實現檔案實時同步引數

文章摘自:http://lxw66.blog.51cto.com/5547576/1331048 文章摘自:http://www.cnblogs.com/smail-bao/p/5667287.html rsync 幫助文件:http://man.linuxde.ne

Android網路程式設計(二)ConnectivityManagerNetworkInfo

    一.   ConnectivityManager詳解      概要      ConnectivityManager是網路連線相關的管理器,它主要用於查詢網路狀態並在網路發生改變時發出狀態變化通知。這個類主要負責的下列四個方面:      1.  監控網路狀態(包

【轉】Android OkHttp3簡介使用

一 OKHttp簡介 OKHttp是一個處理網路請求的開源專案,Android 當前最火熱網路框架,由移動支付Square公司貢獻,用於替代HttpUrlConnection和Apache HttpClient(android API23 6.0裡已移除HttpClient)。 OKHttpGit

android開發 -- 檢視陰影 (Material Design)

Material Design包含了很多內容,大致把它分為四部分: 主題和佈局——ANDROID L——Material Design詳解(主題和佈局) 檢視和陰影——ANDROID L——Material Design詳解(檢視和陰影) UI控制元件——ANDROID

android開發 -- 對話方塊 Dialog DialogFragment Android 官方推薦 DialogFragment 建立對話方塊 )

 Android 官方推薦使用 : DialogFragment 建立對話方塊 ,不推薦直接使用Dialog建立對話方塊,所以能用寫對話方塊儘量用DialogFragment。自定義對話方塊也方便很多 推薦一篇DialogFragment的文章:http://blog.csdn.n

java實現標準化考試系統(二)-----資料庫、資料表的規劃題庫增刪改查

(一)、資料庫、資料表的規劃 首先我們需要考慮一下作為考試系統我們需要哪些資料,這些資料將以後作為欄位值出現。 我們先來看看這張圖: 圖中框起來的部分基本上就是我們需要的資料,細數數就是: 1.試題序號,它作為主鍵出現不可以重複(id) 2.適用工程,可以理解為這個題適用