Android GreenDao 简单封装

1.简介

Android开发中我们或多或少都会接触到数据库。Android中提供了一个占用内存极小的关系型数据库-SQLite。虽然Android系统中提供了许多操作SQLite的API,但是我们还是需要手动去编写SQL语句,这经常会出现一些莫名其妙的问题(😂,不要问我为什么)。所以便出现了许多ORM(对象关系映射)框架。其中比较著名的有GreenDao、OrmLite、Litepal等。下面主要介绍一下GreenDao,我平时用的最多的也是GreenDao。

GreenDAO是一个开源的Android ORM(“对象/关系映射”),通过ORM(称为“对象/关系映射”),在我们数据库开发过程中节省了开发时间!

gitHub 地址:https://github.com/lyyRunning/greenDao

image.png

2.GreenDao的官方文档

  1. GreenDao:适用于您的SQLite数据库的Android ORM
  2. GreenDao的github地址
  3. GreenDao的Google讨论区
  4. GreenDao 加密SQLCipher for Android官方说明地址
  5. GreenDao使用文档
  6. GreenDao 升级文档

3.GreenDao的优缺点?

1.高性能,下面是官方给出的关于GreenDao,OrmLite和ActiveAndroid三种ORM解决方案的数据统计图:


image.png

2.易于使用的强大API,涵盖关系和连接;

3.最小的内存消耗;

4.小库大小(<100KB)以保持较低的构建时间并避免65k方法限制;

5.数据库加密:greenDAO支持SQLCipher,以确保用户的数据安全;

6.强大的社区支持;

4.GreenDao的使用

GreenDao的核心类有三个:分别是DaoMaster,DaoSession,XXXDao,这三个类都会自动创建,无需自己编写创建!

  • DaoMaster::DaoMaster保存数据库对象(SQLiteDatabase)并管理特定模式的DAO类(而不是对象)。它有静态方法来创建表或删除它们。它的内部类OpenHelper和DevOpenHelper是SQLiteOpenHelper实现,它们在SQLite数据库中创建模式。

  • DaoSession:管理特定模式的所有可用DAO对象,您可以使用其中一个getter方法获取该对象。DaoSession还提供了一些通用的持久性方法,如实体的插入,加载,更新,刷新和删除。

  • XXXDao:数据访问对象(DAO)持久存在并查询实体。对于每个实体,greenDAO生成DAO。它具有比DaoSession更多的持久性方法,例如:count,loadAll和insertInTx。

  • Entities :可持久化对象。通常, 实体对象代表一个数据库行使用标准 Java 属性(如一个POJO 或 JavaBean )。

(1)导入Gradle插件和Dao代码生成

buildscript {
    
    repositories {
        google()
        jcenter()
       //添加
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0'
        //greendao
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
        classpath 'com.jakewharton:butterknife-gradle-plugin:8.5.1'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
         //添加
        maven { url "https://jitpack.io" }
    }
}

(2)配置相关依赖

// 在 Moudle:app的  build.gradle 文件中添加:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin
 
dependencies {
     //greendao
    implementation 'org.greenrobot:greendao:3.2.2'
    //数据库升级防止丢失数据
    implementation 'io.github.yuweiguocn:GreenDaoUpgradeHelper:v2.2.1'
}

(3)配置数据库相关信息

 greendao {
        //数据库版本号
        schemaVersion 1
        // 设置DaoMaster、DaoSession、Dao 包名
        daoPackage 'com/function/luo/day0620/greenDao.db'
        //设置DaoMaster、DaoSession、Dao目录,请注意,这里路径用.不要用
        targetGenDir 'src/main/java'
        //设置为true以自动生成单元测试。
        generateTests false
        //应存储生成的单元测试的基本目录。默认为 src / androidTest / java。
        targetGenDirTests 'src/main/java'
    }

配置完成,在Android Studio中使用Build> Make Project,重写build项目,GreenDao集成完成!

(4)创建存储对象实体类

使用GreenDao存储数据只需要在存储数据类前面声明@Entity注解就让GreenDao为其生成必要的代码:

@Entity(nameInDb = "STUDENT")
public class Student extends DbBean{
    @Id(autoincrement = true)
    Long id;
    /**
     * 学号
     */
    @Property(nameInDb = "studentNo")
    int studentNo;
    /**
     * 年龄
     */
    @Property(nameInDb = "age")
    int age;
    /**
     * 手机号
     */
    @Property(nameInDb = "telPhone")
    String telPhone;
    /**
     * 性别
     */
    @Property(nameInDb = "sex")
    String sex;
    /**
     * 姓名
     */
    @Property(nameInDb = "name")
    String name;
    /**
     * 家庭住址
     */
    @Property(nameInDb = "address")
    String address;
    /**
     * 学校名字
     */
    @Property(nameInDb = "schoolName")
    String schoolName;
    /**
     * 几年级
     */
    @Property(nameInDb = "grade")
    String grade;

(5)GreenDao初始化

我们可以在Application中维持一个全局的会话。我们在Applicaiton进行数据库的初始化操作:

    /**
     * 初始化GreenDao,直接在Application中进行初始化操作
     */
    private static DaoSession daoSession;
    private void initGreenDao() {
        // 初始化//如果你想查看日志信息,请将 DEBUG 设置为 true
        if (BuildConfig.DEBUG){
            MigrationHelper.DEBUG = true;
        }else {
            MigrationHelper.DEBUG = false;
        }

        //数据库名字
        MySqliteOpenHelper mySqliteOpenHelper = new MySqliteOpenHelper(mContext, "greenDaoTest.db",null);

        SQLiteDatabase db = mySqliteOpenHelper.getWritableDatabase();
        DaoMaster daoMaster = new DaoMaster(db);

        daoSession = daoMaster.newSession();


    }

    /**
     * 提供一个全局的会话
     * @return
     */
    public static DaoSession getDaoSession() {
        return daoSession;
    }

(6)重写MySqliteOpenHelper,防止数据库升级清空本地数据

public class MySqliteOpenHelper extends DaoMaster.OpenHelper {

    public MySqliteOpenHelper(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) {
        super.onUpgrade(db, oldVersion, newVersion);

        //这里添加要增加的字段
        MigrationHelper.migrate(db, StudentDao.class);

    }
}

初始化完成之后重新rebuild一下项目会发现在设置的targetGenDir的目录生成三个类文件,这个是GreenDao自动生成的!说明数据库已经连接好了,咱们接下来只需要进行数据库的增删改查操作就行了。

4.使用GreenDao实现增删改查(我封装一个类)

public class DaoSessionUtils {

    static DaoSession daoSession;

    public static DaoSession getDaoInstance() {

        if (daoSession == null) {
            daoSession = MyApplication.getDaoSession();
        }
        //清空所有数据表的缓存数据
        //daoSession.clear();
        return daoSession;
    }


    /**
     * insert() 插入数据
     */
    public static void insertDbBean(DbBean bean) {
        try {
            getDaoInstance().insert(bean);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "插入本地数据失败:" + e.getMessage());
        }

    }


    /**
     * insertOrReplace()数据存在则替换,数据不存在则插入
     */
    public static void insertOrReplaceDbBean(DbBean bean) {
        try {
            getDaoInstance().insertOrReplace(bean);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "插入或替换本地数据失败:" + e.getMessage());
        }

    }


    /**
     * delete()删除单个数据
     */
    public static void deleteDbBean(DbBean bean) {
        try {
            getDaoInstance().delete(bean);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "删除本地数据失败:" + e.getMessage());
        }

    }


    /**
     * deleteAll()删除所有数据
     */
    public static void deleteAllDbBean(DbBean bean) {
        try {
            getDaoInstance().deleteAll(bean.getClass());

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "删除本地所有数据失败:" + e.getMessage());
        }

    }


    /**
     * update()修改本地数据
     */
    public static void updateDbBean(DbBean bean) {
        try {
            getDaoInstance().update(bean);
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "修改本地所有数据失败:" + e.getMessage());
        }

    }

    /**
     * loadAll()查询本地所有数据
     */
    public static List<? extends DbBean> queryAll(DbBean bean) {
        List<DbBean> beanList = null;
        try {
            beanList = (List<DbBean>) getDaoInstance().loadAll(bean.getClass());
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "查询本地所有数据失败:" + e.getMessage());
        }

        return beanList;
    }


    /**
     * 根据条件查询本地所有数据
     * 调用时传值方法whereConditions
     * List<WhereCondition> whereConditions = new ArrayList<>();
     * whereConditions.add(StudentDao.Properties.Name.eq("小明"));
     * whereConditions.add(StudentDao.Properties.Age.eq(22));
     */
    public static List<? extends DbBean> queryConditionAll(DbBean bean, List<WhereCondition> whereConditions) throws ClassCastException {
        List<DbBean> beanList = null;
        try {

            QueryBuilder queryBuilder = getDaoInstance().queryBuilder(bean.getClass());
            //把条件循环加入
            if (null != whereConditions) {
                for (WhereCondition whereCondition : whereConditions) {
                    queryBuilder.where(whereCondition);
                }

            }
            beanList = queryBuilder.build().list();
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "按条件查询本地数据失败:" + e.getMessage());
        }

        return beanList;
    }


    /**
     * 根据原始 SQL 数据查询
     * 手输写 SQL 语句sqlConditions
     */
    public static List<? extends DbBean> querySqlAll(DbBean bean, String sqlConditions) throws ClassCastException {
        List<DbBean> beanList = null;
        try {
            //查询条件
            WhereCondition.StringCondition stringCondition = new WhereCondition.StringCondition(sqlConditions);
            //查询QueryBuilder
            QueryBuilder<DbBean> queryBuilder = (QueryBuilder<DbBean>) getDaoInstance().queryBuilder(bean.getClass());
            //添加查询条件
            queryBuilder.where(stringCondition);

            beanList = queryBuilder.build().list();
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "sql按条件查询本地数据失败:" + e.getMessage());
        }

        return beanList;
    }
}

5.使用GreenDao简单使用

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        init();
        studentBean = new Student();
        studentBean.setId((long) (Math.random() * 100));
        studentBean.setAge(25);
        studentBean.setName("小米");
    }

    private void init() {


        for (int i = 0; i < 100; i++) {
            Student student = new Student();
            student.setStudentNo(i);
            int age = (int) (Math.random() * 10 + 10);
            student.setAge(age);
            student.setTelPhone(String.valueOf(Math.random() * 10000000));
            String chineseName = String.valueOf("名字:" + Math.random() * 10000000);
            student.setName(chineseName);
            if (i % 2 == 0) {
                student.setSex("男");
            } else {
                student.setSex("女");
            }
            student.setAddress(String.valueOf(Math.random() * 10000));
            student.setGrade(String.valueOf(age % 10) + "年纪");
            student.setSchoolName("学校:" + String.valueOf(Math.random() * 1000));
            DaoSessionUtils.insertDbBean(student);
        }

    }

    @OnClick({R.id.button1, R.id.button2, R.id.button3, R.id.button4, R.id.button5, R.id.button6})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.button1:
                //增加
                DaoSessionUtils.insertOrReplaceDbBean(studentBean);
                break;
            case R.id.button2:
                studentBean.setName("东方不败");
                DaoSessionUtils.updateDbBean(studentBean);
                //修改
                break;
            case R.id.button3:
                DaoSessionUtils.deleteDbBean(studentBean);
                //删除
                break;
            case R.id.button4:
                //全部查询
                List<? extends DbBean> dbBeans = DaoSessionUtils.queryAll(studentBean);
                if (dbBeans != null) {
                    Log.d("LUO", "数量" + dbBeans.size());
                    tv1.setText(String.valueOf(dbBeans));
                } else {
                    tv1.setText("无数据");
                }

                break;
            case R.id.button5:
                //按条件查询
                List<WhereCondition> whereConditions = new ArrayList<>();
                whereConditions.add(StudentDao.Properties.Name.eq("小米"));
                whereConditions.add(StudentDao.Properties.Age.eq(25));
                List<? extends DbBean> dbList = DaoSessionUtils.queryConditionAll(studentBean, whereConditions);

                if (dbList != null) {
                    Log.d("LUO", "数量" + dbList.size());
                    tv1.setText(String.valueOf(dbList));
                } else {
                    tv1.setText("无数据");
                }

                break;
            case R.id.button6:
                //sql 语句查询
                String sql = "_ID IN " + "(SELECT _ID FROM STUDENT WHERE _ID > 5)";
                List<? extends DbBean> dbBeans1 = DaoSessionUtils.querySqlAll(studentBean, sql);

                if (dbBeans1 != null) {
                    Log.d("LUO", "数量" + dbBeans1.size());
                    tv1.setText(String.valueOf(dbBeans1));
                } else {
                    tv1.setText("无数据");
                }

                break;
            default:
        }
    }
}

6.使用GreenDao基本使用总结:

(1)新增

  • insert() 插入数据
  • insertOrReplace()数据存在则替换,数据不存在则插入

(2)删除

  • delete()单个数据删除
  • deleteAll()这个表所有数据删除

(3)修改

  • update()修改单个数据

(4)查询

  • loadAll():查询所有数据
  • queryRaw():根据条件查询
  • queryBuilder() : 方便查询的创建

(5)QueryBuilder的使用

编写SQL可能很困难并且容易出现错误,这些错误仅在运行时才会被注意到。该QueryBuilder的类可以让你建立你的实体,而不SQL自定义查询,并有助于在编译时已检测错误。

我们先讲下QueryBuilder的常见方法:

  • where(WhereCondition cond, WhereCondition... condMore): 查询条件,参数为查询的条件!

  • or(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore): 嵌套条件或者,用法同or。

  • and(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore): 嵌套条件且,用法同and。

  • join(Property sourceProperty, Class<J> destinationEntityClass):多表查询,后面会讲。
    输出结果有四种方式,选择其中一种最适合的即可,list()返回值是List,而其他三种返回值均实现Closeable,需要注意的不使用数据时游标的关闭操作:

  • list ()所有实体都加载到内存中。结果通常是一个没有魔法的 ArrayList。最容易使用。

  • listLazy ()实体按需加载到内存中。首次访问列表中的元素后,将加载并缓存该元素以供将来使用。必须关闭。

  • listLazyUncached ()实体的“虚拟”列表:对列表元素的任何访问都会导致从数据库加载其数据。必须关闭。

  • listIterator ()让我们通过按需加载数据(懒惰)来迭代结果。数据未缓存。必须关闭。

  • orderAsc() 按某个属性升序排;

  • orderDesc() 按某个属性降序排;

GreenDao中SQL语句的缩写,我们也了解下,源码在Property中,使用的时候可以自己点进去查询即可:

  • eq():"equal ('=?')" 等于;
  • notEq() :"not equal ('<>?')" 不等于;
  • like():" LIKE ?" 值等于;
  • between():" BETWEEN ? AND ?" 取中间范围;
  • in():" IN (" in命令;
  • notIn():" NOT IN (" not in 命令;
  • gt():">?" 大于;
  • lt():"<? " 小于;
  • ge():">=?" 大于等于;
  • le():"<=? " 小于等于;
  • isNull():" IS NULL" 为空;
  • isNotNull():" IS NOT NULL" 不为空;

(5)使用QueryBuilder进行查询操作

  1. 简单条件查询
    查询当前Student表的所有的数据:
  public List queryAllList(){
        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
        QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class);
        List<Student> list = qb.list(); // 查出所有的数据
    return list;
    }

2.查询Name为“一”的所有Student:

 public List queryListByMessage(String name){
         DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
        QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class);
        QueryBuilder<Student> studentQueryBuilder = qb.where(StudentDao.Properties.Name.eq("一")).orderAsc(StudentDao.Properties.Name);
        List<Student> studentList = studentQueryBuilder.list(); //查出当前对应的数据
        return list;
    }

2.原始查询
通过原始的SQL查询语句进行查询!其实上面有提到QueryBuilder的目的就是方便快捷的编写SQL查询语句,避免我们自己在编写过程中出错!简单介绍下通过QueryBuilder编写数据库,方式方法如下 :

public List queryListBySqL(){
// 查询ID大于5的所有学生
        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
        Query<Student> query = daoSession.queryBuilder(Student.class).where(
                new WhereCondition.StringCondition("_ID IN " +
                        "(SELECT _ID FROM STUDENT WHERE _ID > 5)")
        ).build();
        List<Student> list = query.list();
        return list;
    }
  1. 嵌套条件查询
    查询Id大于5小于10,且Name值为"一"的数据:
public List queryList(){
        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
        QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class);
        qb = daoSession.queryBuilder(Student.class);
        List<Student> list2 = qb.where(StudentDao.Properties.Name.eq("一"),
                qb.and(StudentDao.Properties.Id.gt(5),
                        StudentDao.Properties.Id.le(50))).list();
        return  list2;
    }

取10条Id大于1的数据,且偏移2条

 public List queryListByOther(){
        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
        QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class);

        //搜索条件为Id值大于1,即结果为[2,3,4,5,6,7,8,9,10,11];
        // offset(2)表示往后偏移2个,结果为[4,5,6,7,8,9,10,11,12,13];
        List<Student> list = qb.where(StudentDao.Properties.Id.gt(1)).limit(10).offset(2).list();
        return list;
    }
  1. 多次执行查找

使用QueryBuilder构建查询后,可以重用 Query对象以便稍后执行查询。这比始终创建新的Query对象更有效。如果查询参数没有更改,您可以再次调用list / unique方法。可以通过setParameter方法来修改条件参数值:

public List queryListByMoreTime(){
        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
        QueryBuilder<Student> qb = daoSession.queryBuilder(Student.class);

        //搜索条件为Id值大于1,即结果为[2,3,4,5,6,7,8,9,10,11];
        // offset(2)表示往后偏移2个,结果为[4,5,6,7,8,9,10,11,12,13];
        Query<Student> query = qb.where(StudentDao.Properties.Id.gt(1)).limit(10).offset(2).build();
        List<Student> list = query.list();
        
        //通过SetParameter来修改上面的查询条件,比如我们将上面条件修改取10条Id值大于5,往后偏移两位的数据,方法如下!
        query.setParameter(0,5);
        List<Student> list1 = query.list();
        return list1;
    }
  1. 在多个线程中使用QueryBuilder

如果在多个线程中使用查询,则必须调用 forCurrentThread ()以获取当前线程的Query实例。Query的对象实例绑定到构建查询的拥有线程。
这使您可以安全地在Query对象上设置参数,而其他线程不会干扰。如果其他线程尝试在查询上设置参数或执行绑定到另一个线程的查询,则会抛出异常。像这样,您不需要同步语句。实际上,您应该避免锁定,因为如果并发事务使用相同的Query对象,这可能会导致死锁。
每次调用forCurrentThread ()时, 参数都会在使用其构建器构建查询时设置为初始参数。

  1. 使用QueryBuilder进行批量删除操作
    使用QueryBuilder进行批量删除操作,不会删除单个实体,但会删除符合某些条件的所有实体。要执行批量删除,请创建QueryBuilder,调用其 buildDelete ()方法,然后执行返回的 DeleteQuery。

例子:删除数据库中id大于5的所有其他数据

public boolean deleteItem(){
        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
        QueryBuilder<Student> where = daoSession.queryBuilder(Student.class).where(StudentDao.Properties.Id.gt(5));
        DeleteQuery<Student> deleteQuery = where.buildDelete();
        deleteQuery.executeDeleteWithoutDetachingEntities();
        return false;
    }

7.注解讲解

从GreenDao 3 使用注解来定义模型和实体,前面也讲过,通过注解的使用可以快速构建数据库表,包括设置主键,自增,值是否唯一等等等……

下面我们来看下注解的简单使用:

@Entity
public class Student {
    @Id(autoincrement = true)
    Long id;
    @Unique
    int studentNo;//学号
    int age; //年龄
    String telPhone;//手机号
    String sex; //性别
    String name;//姓名
    String address;//家庭住址
    String schoolName;//学校名字
    String grade;//几年级
    ……getter and setter and constructor method……
    }

1. @Entity注解

@Entity是GreenDao必不可少的注解,只有在实体类中使用了
@Entity注解GreenDao才会创建对应的表。当然我们也可以使用@Entity配置一些细节:

  • schema:如果你有多个架构,你可以告诉GreenDao当前属于哪个架构。

  • active:标记一个实体处于活跃状态,活动实体有更新、删除和刷新方法。

  • nameInDb:在数据中使用的别名,默认使用的是实体的类名。

  • indexes:标记如果DAO应该创建数据库表(默认为true),如果您有多个实体映射到一个表,或者表的创建是在greenDAO之外进行的,那么将其设置为false。

  • createInDb:标记创建数据库表。

  • generateGettersSetters:如果缺少,是否应生成属性的getter和setter方法。

2. 基础属性注解(@Id,@Property,@NotNull,@Transient)

@Id
@Id注解选择 long / Long属性作为实体ID。在数据库方面,它是主键。参数autoincrement = true 表示自增,id不给赋值或者为赋值为null即可(这里需要注意,如果要实现自增,id必须是Long,为long不行!)。

@Entity
public class Student {
    @Id(autoincrement = true)
    Long id;
    ……
}

@Property
允许您定义属性映射到的非默认列名。如果不存在,GreenDAO将以SQL-ish方式使用字段名称(大写,下划线而不是camel情况,例如 name将成为 NAME)。注意:您当前只能使用内联常量来指定列名。

@Entity
public class Student {
    @Id(autoincrement = true)
    Long id;
    @Property (nameInDb="name") //设置了,数据库中的表格属性名为"name",如果不设置,数据库中表格属性名为"NAME"
    String name;
    ……
}

@NotNull :设置数据库表当前列不能为空 。

@Transient :添加次标记之后不会生成数据库表的列。标记要从持久性中排除的属性。将它们用于临时状态等。或者,您也可以使用Java中的transient关键字。

3. 索引注解

  • @Index:使用@Index作为一个属性来创建一个索引,通过name设置索引别名,也可以通过unique给索引添加约束。
  • @Unique:向索引添加UNIQUE约束,强制所有值都是唯一的。
@Entity
public class Student {
    @Id(autoincrement = true)
    Long id;
    @Property(nameInDb="name")
    @Index(unique = true)
     String name;
    ……
}

注意: 上面这种情况,约定name为唯一值,向数据库中通过insert方法继续添加已存在的name数据,会抛异常:

10-08 20:59:46.274 31939-31939/com.example.aserbao.aserbaosandroid E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.aserbao.aserbaosandroid, PID: 31939
    android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: STUDENT.name (Sqlite code 2067), (OS error - 2:No such file or directory)
    ……

若使用insertOrReplace()方法添加数据,当前数据库中不会有重复的数据,但是重复的这条数据的id会被修改!若项目中有用到id字段进行排序的话,这一点需要特别注意。

4. 关系注解

关系型注解GreenDao中主要就两个:

  • @ToOne:定义与另一个实体(一个实体对象)的关系
  • @ToMany:定义与多个实体对象的关系

8.一对一,一对多,多对多关系表的创建

平常项目中,我们经常会使用到多表关联,如文章开头所说的数据库表结构设置的那样!接下来我们来讲如何通过GreenDao实现多表关联。

1. 一对一

一个学生对应一个身份证号:
做法:

我们在Student中设置一个注解@ToOne(joinProperty = "name")
在创建Student的时候,将对应的数据传递给IdCard;
代码部分:

学生Student代码:

@Entity
public class Student {
    @Id(autoincrement = true)
    Long id;
    @Unique
    int studentNo;//学号
    int age; //年龄
    String telPhone;//手机号
    String sex; //性别
    String name;//姓名
    String address;//家庭住址
    String schoolName;//学校名字
    String grade;//几年级
    @ToOne(joinProperty = "name")
    IdCard student;
    ……getter and setter ……
}

身份证IdCard代码:

@Entity
public class IdCard {
    @Id 
    String userName;//用户名
    @Unique
    String idNo;//身份证号
       ……getter and setter ……
}

insert一组数据:

public void addStudent(){
                        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
                        Student student = new Student();
                        student.setStudentNo(i);
                        int age = mRandom.nextInt(10) + 10;
                        student.setAge(age);
                        student.setTelPhone(RandomValue.getTel());
                        String chineseName = RandomValue.getChineseName();
                        student.setName(chineseName);
                        if (i % 2 == 0) {
                            student.setSex("男");
                        } else {
                            student.setSex("女");
                        }
                        student.setAddress(RandomValue.getRoad());
                        student.setGrade(String.valueOf(age % 10) + "年纪");
                        student.setSchoolName(RandomValue.getSchoolName());
                        daoSession.insert(student);
                        
                        //插入对应的IdCard数据
                        IdCard idCard = new IdCard();
                        idCard.setUserName(userName);
                        idCard.setIdNo(RandomValue.getRandomID());
                        daoSession.insert(idCard);
      }

ok,数据可以了!现在数据库表插入完成了。

2. 一对多

一个人拥有多个信用卡
做法:

  • 在我们在Student中设置@ToMany(referencedJoinProperty = "studentId");
  • 我们在CreditCard中设置编写对应的id主键;

Student的代码:

@Entity
public class Student {
    @Id(autoincrement = true)
    Long id;

    @Unique
    int studentNo;//学号

    int age; //年龄
    String telPhone;//手机号
    String sex; //性别
    String name;//姓名
    String address;//家庭住址
    String schoolName;//学校名字
    String grade;//几年级
    
    @ToMany(referencedJoinProperty = "studentId) // 这个studentId是对应在CreditCard中的studentId
    List<CreditCard> creditCardsList;
      ……getter and setter ……
    }

CreditCard的代码:

@Entity
public class CreditCard {
    @Id
    Long id;
    Long studentId;
    Long teacherId;
    String userName;//持有者名字
    String cardNum;//卡号
    String whichBank;//哪个银行的
    int cardType;//卡等级,分类 0 ~ 5
     ……getter and setter ……
    }

添加数据代码:

public void addStudent(){
                        DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();
                        Student student = new Student();
                        student.setStudentNo(i);
                        int age = mRandom.nextInt(10) + 10;
                        student.setAge(age);
                        student.setTelPhone(RandomValue.getTel());
                        String chineseName = RandomValue.getChineseName();
                        student.setName(chineseName);
                        if (i % 2 == 0) {
                            student.setSex("男");
                        } else {
                            student.setSex("女");
                        }
                        student.setAddress(RandomValue.getRoad());
                        student.setGrade(String.valueOf(age % 10) + "年纪");
                        student.setSchoolName(RandomValue.getSchoolName());
                        daoSession.insert(student);
                        
                        //插入对应的CreditCard数据
                       for (int j = 0; j < random.nextInt(5) + 1 ; j++) {
                        CreditCard creditCard = new CreditCard();
                        creditCard.setUserId(id);
                        creditCard.setUserName(userName);
                        creditCard.setCardNum(String.valueOf(random.nextInt(899999999) + 100000000) + String.valueOf(random.nextInt(899999999) + 100000000));
                        creditCard.setWhichBank(RandomValue.getBankName());
                        creditCard.setCardType(random.nextInt(10));
                        daoSession.insert(creditCard);
                    }
      }

3. 多对多

一个学生有多个老师,老师有多个学生。
做法:

  1. 我们需要创建一个学生老师管理器(StudentAndTeacherBean),用来对应学生和老师的ID;

  2. 我们需要在学生对象中,添加注解:

@ToMany
@JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = "studentId",targetProperty = "teacherId")
List<Teacher> teacherList;

3.我们需要在老师对象中,添加注解:@ToMany

  @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = "teacherId",targetProperty = "studentId")
    List<Student> studentList;

StudentAndTeacherBean代码:

@Entity
public class StudentAndTeacherBean {
    @Id(autoincrement = true)
    Long id;
    Long studentId;//学生ID
    Long teacherId;//老师ID
    ……getter and setter ……
}

Student 代码:

@Entity
public class Student {
    @Id(autoincrement = true)
    Long id;
    @Unique
    int studentNo;//学号
    int age; //年龄
    String telPhone;//手机号
    String sex; //性别
    String name;//姓名
    String address;//家庭住址
    String schoolName;//学校名字
    String grade;//几年级
    @ToMany
    @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = "studentId",targetProperty = "teacherId")
    List<Teacher> teacherList;
        ……getter and setter ……
    }

Teacher代码:

@Entity
public class Teacher {
    @Id(autoincrement = true)
    Long id;
    @Unique
    int teacherNo;//职工号
    int age; //年龄
    String sex; //性别
    String telPhone;
    String name;//姓名
    String schoolName;//学校名字
    String subject;//科目

    @ToMany
    @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = "teacherId",targetProperty = "studentId")
    List<Student> studentList;
  ……getter and setter ……
}

数据添加:

public void addData(){
                       Student student = new Student();
                        student.setStudentNo(i);
                        int age = mRandom.nextInt(10) + 10;
                        student.setAge(age);
                        student.setTelPhone(RandomValue.getTel());
                        String chineseName = RandomValue.getChineseName();
                        student.setName(chineseName);
                        if (i % 2 == 0) {
                            student.setSex("男");
                        } else {
                            student.setSex("女");
                        }
                        student.setAddress(RandomValue.getRoad());
                        student.setGrade(String.valueOf(age % 10) + "年纪");
                        student.setSchoolName(RandomValue.getSchoolName());
                        daoSession.insert(student);

                        Collections.shuffle(teacherList);
                        for (int j = 0; j < mRandom.nextInt(8) + 1; j++) {
                            if(j < teacherList.size()){
                                Teacher teacher = teacherList.get(j);
                                StudentAndTeacherBean teacherBean = new StudentAndTeacherBean(student.getId(), teacher.getId());
                                daoSession.insert(teacherBean);
                            }
                        }
                        }

8.数据库的升级

参考博客 1:https://blog.csdn.net/qq_35956194/article/details/79167897
参考博客 2:https://github.com/yuweiguocn/GreenDaoUpgradeHelper

GreenDao的OpenHelper下有个 onUpgrade(Database db, int oldVersion, int newVersion)方法,当设置的数据库版本改变时,在数据库初始化的时候就会回调到这个方法,我们可以通过继承OpenHelper重写onUpgrade方法来实现数据库更新操作:

GreenDao的升级思路:

  1. 创建临时表TMP_,复制原来的数据库到临时表中;
  2. 删除之前的原表;
  3. 创建新表;
  4. 将临时表中的数据复制到新表中,最后将TMP_表删除掉;

ok,思路就是这样, 总共两个类: 一个MySqliteOpenHelper(OpenHelper继承类),一个DaoSessionUtils(数据库操作类) 下面是代码编写:
修改Application中的DaoMaster的创建:

       //数据库名字
        MySqliteOpenHelper mySqliteOpenHelper = new MySqliteOpenHelper(mContext, "greenDaoTest.db",null);
       // DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "greenDaoTest.db");
        SQLiteDatabase db = mySqliteOpenHelper.getWritableDatabase();
        DaoMaster daoMaster = new DaoMaster(db);

        daoSession = daoMaster.newSession();

MySqliteOpenHelper代码:

public class MySqliteOpenHelper extends DaoMaster.OpenHelper {

    public MySqliteOpenHelper(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) {
        super.onUpgrade(db, oldVersion, newVersion);

        //这里添加要增加的字段
        //MigrationHelper.migrate(db, StudentDao.class);
        MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {
            @Override
            public void onCreateAllTables(Database db, boolean ifNotExists) {
                DaoMaster.createAllTables(db, ifNotExists);
            }
            @Override
            public void onDropAllTables(Database db, boolean ifExists) {
                DaoMaster.dropAllTables(db, ifExists);
            }
        },StudentDao.class);
       
    
    }
}

DaoSessionUtils 代码:

public class DaoSessionUtils {

    static DaoSession daoSession;

    public static DaoSession getDaoInstance() {

        if (daoSession == null) {
            daoSession = MyApplication.getDaoSession();
        }
        //清空所有数据表的缓存数据
        //daoSession.clear();
        return daoSession;
    }


    /**
     * insert() 插入数据
     */
    public static void insertDbBean(DbBean bean) {
        try {
            getDaoInstance().insert(bean);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "插入本地数据失败:" + e.getMessage());
        }

    }


    /**
     * insertOrReplace()数据存在则替换,数据不存在则插入
     */
    public static void insertOrReplaceDbBean(DbBean bean) {
        try {
            getDaoInstance().insertOrReplace(bean);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "插入或替换本地数据失败:" + e.getMessage());
        }

    }


    /**
     * delete()删除单个数据
     */
    public static void deleteDbBean(DbBean bean) {
        try {
            getDaoInstance().delete(bean);

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "删除本地数据失败:" + e.getMessage());
        }

    }


    /**
     * deleteAll()删除所有数据
     */
    public static void deleteAllDbBean(DbBean bean) {
        try {
            getDaoInstance().deleteAll(bean.getClass());

        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "删除本地所有数据失败:" + e.getMessage());
        }

    }


    /**
     * update()修改本地数据
     */
    public static void updateDbBean(DbBean bean) {
        try {
            getDaoInstance().update(bean);
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "修改本地所有数据失败:" + e.getMessage());
        }

    }

    /**
     * loadAll()查询本地所有数据
     */
    public static List<? extends DbBean> queryAll(DbBean bean) {
        List<DbBean> beanList = null;
        try {
            beanList = (List<DbBean>) getDaoInstance().loadAll(bean.getClass());
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "查询本地所有数据失败:" + e.getMessage());
        }

        return beanList;
    }


    /**
     * 根据条件查询本地所有数据
     * 调用时传值方法whereConditions
     * List<WhereCondition> whereConditions = new ArrayList<>();
     * whereConditions.add(StudentDao.Properties.Name.eq("小明"));
     * whereConditions.add(StudentDao.Properties.Age.eq(22));
     */
    public static List<? extends DbBean> queryConditionAll(DbBean bean, List<WhereCondition> whereConditions) throws ClassCastException {
        List<DbBean> beanList = null;
        try {

            QueryBuilder queryBuilder = getDaoInstance().queryBuilder(bean.getClass());
            //把条件循环加入
            if (null != whereConditions) {
                for (WhereCondition whereCondition : whereConditions) {
                    queryBuilder.where(whereCondition);
                }

            }
            beanList = queryBuilder.build().list();
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "按条件查询本地数据失败:" + e.getMessage());
        }

        return beanList;
    }


    /**
     * 根据原始 SQL 数据查询
     * 手输写 SQL 语句sqlConditions
     */
    public static List<? extends DbBean> querySqlAll(DbBean bean, String sqlConditions) throws ClassCastException {
        List<DbBean> beanList = null;
        try {
            //查询条件
            WhereCondition.StringCondition stringCondition = new WhereCondition.StringCondition(sqlConditions);
            //查询QueryBuilder
            QueryBuilder<DbBean> queryBuilder = (QueryBuilder<DbBean>) getDaoInstance().queryBuilder(bean.getClass());
            //添加查询条件
            queryBuilder.where(stringCondition);

            beanList = queryBuilder.build().list();
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("LUO", "sql按条件查询本地数据失败:" + e.getMessage());
        }

        return beanList;
    }
}

9.GreenDao数据库加密

开发中对于存储于数据库中的敏感数据,我们可以通过对数据库加密来进行保护。GreenDao可以通过SQLCipher来进行加密处理。下面我们简单讲解下加密过程:

步骤:

  1. 导入加密库文件:
implementation 'net.zetetic:android-database-sqlcipher:3.5.6'
  1. 修改DaoSession的生成方式:
//       MyDaoMaster helper = new MyDaoMaster(this, "aserbaos.db");  //数据库升级写法
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "aserbao.db");
        //SQLiteDatabase db = helper.getWritableDatabase(); //不加密的写法
        Database db = helper.getEncryptedWritableDb("aserbao"); //数据库加密密码为“aserbao"的写法
        DaoMaster daoMaster = new DaoMaster(db);
        daoSession = daoMaster.newSession();

参考博客:

作者:aserbao
链接:https://www.jianshu.com/p/53083f782ea2
作者:tonycheng93
链接:https://www.cnblogs.com/tonycheng93/p/6295724.html
作者:我有我的方式
链接:https://blog.csdn.net/qq_35956194/article/details/79167897
作者:于伟国
链接:https://github.com/yuweiguocn/GreenDaoUpgradeHelper

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容