Android GreenDao实现CRUD和升级详解

Posted 刘永祥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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和升级详解的主要内容,如果未能解决你的问题,请参考以下文章

ORM对象关系映射之使用GreenDAO进行CRUD操作

Android 使用 GreenDAO 3.x 进行增删改查和升级

将 Android Studio Gradle 升级到 6.1.1 会破坏 Greendao3GradlePlugin

GreenDao在Android项目中的实践总结

Android实战——GreenDao3.2的使用,爱不释手

Android实战——GreenDao3.2的使用,爱不释手