天天看點

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 等類。

Android GreenDao實作CRUD和更新詳解

需要建立一個Moudle,操作順序為:File -> New -> New Module -> Java Library -> 填寫相應的包名與類名 -> Finish。一定要注意是Java Library Java Library Java Library !!!

在建立Moudle的build.gradle檔案裡面需要配置

Android GreenDao實作CRUD和更新詳解

代碼為

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

在app的build.gradle檔案裡面也需要配置如下内容

Android GreenDao實作CRUD和更新詳解

代碼如下

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");
    }
}
           

點選滑鼠右鍵執行如下操作

Android GreenDao實作CRUD和更新詳解

當你的控制台輸出以下日志内容表示成功

Android GreenDao實作CRUD和更新詳解

當我們再次回到我們的java-gen這個檔案夾檢視時,可以看到裡面多了一些檔案夾和類,是不是感覺很熟悉啊?沒錯,這些檔案夾和類就是我們在java工程裡面通過代碼設定的。

Android GreenDao實作CRUD和更新詳解

别看到類爆紅就害怕,這些類生成後雖然爆紅但是影響并不大,我們可以在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字段,需要如下圖所示

Android GreenDao實作CRUD和更新詳解

在java工程的類裡面需要添加entity.addStringProperty(“sex”);這樣一句代碼,然後按照上面的執行即可生成上面的四個類StudentDao.java、Student.java、DaoMaster.java、DaoSession.java。然後将之前的四個類替換掉并且更重要的一點就是需要在DaoMaster這個裡面将SCHEMA_VERSION 的由1值修改為 2,後續依次累加

Android GreenDao實作CRUD和更新詳解

也可以直接在java工程的類裡面直接修改

Android GreenDao實作CRUD和更新詳解

由于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 GreenDao實作CRUD和更新詳解

點選下載下傳源碼

如有疑問歡迎留言!