大家在使用学习使用GreenDao的时候遇到什么问题,欢迎在我的公众号aserbao给我留言,无偿服务!同时,欢迎大家来加入微信群二维码讨论群,一起讨论Android开发技术!群二维码定时在我公众号更新!

之前在开发过程中,数据库基本上会使用Litepal和SQlite自己写,最近换新环境,公司原先使用的数据库就是GreenDao,在各种情况的作用下,准备了解下GreenDao,顺便写一篇文章记录下GreenDao的基本使用!大家在使用GreenDao的时候遇到什么问题,欢迎给我留言。

回归正题,不再扯没用的了!本文主要从如下几个方面进行讲解:1. 存储的数据库结构 2. GreenDao的优缺点 3. GreenDao的使用配置 4. 使用GreenDao实现数据的增删改查 5. GreenDao的注解使用 6. GreenDao的关系处理 7. GreenDao的升级 8. GreenDao数据库加密 9. 项目地址 10 总结 11. 参考博客

咱们先看一波最终的效果图:文章最后有项目地址;

文章目录

  • 1. 存储的数据库结构
  • 2. GreenDao的介绍
    • 什么是GreenDao?
    • GreenDao的官方文档
    • GreenDao的作用?
    • GreenDao的优缺点?
  • 3. GreenDao的使用
    • 1. 导入Gradle插件和Dao代码生成
    • 2. 创建存储对象实体类
    • 3. GreenDao初始化
    • 4. 使用GreenDao实现增删改查
      • 1. 增
      • 2. 删
      • 3. 改
      • 4. 查
  • 4. QueryBuilder的使用
    • 1. 使用QueryBuilder进行查询操作
      • 1. 简单条件查询
      • 2. 原始查询
      • 3. 嵌套条件查询
      • 4. 多次执行查找
      • 5. 在多个线程中使用QueryBuilder
    • 2. 使用QueryBuilder进行批量删除操作
  • 5. 注解讲解
    • 1. @Entity注解
    • 2. 基础属性注解(@Id,@Property,@NotNull,@Transient)
    • 3. 索引注解
    • 4. 关系注解
  • 6. 一对一,一对多,多对多关系表的创建
    • 1. 一对一
    • 2. 一对多
    • 3. 多对多
  • 7. 数据库的升级
  • 8. GreenDao数据库加密
  • 9. 项目地址
  • 10 总结
  • 11. 参考博客
  • 12.修改记录

1. 存储的数据库结构

学习数据库之前,我们先得设计自己的数据库,不多废话,下面是我此次学习的数据库结构,后面所有的数据请参考这个图进行学习:

2. GreenDao的介绍

简单的GreenDao的介绍,闲麻烦的可以直接跳到GreenDao使用开始看。

什么是GreenDao?

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

GreenDao的官方文档

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

GreenDao的作用?

通过GreenDao,我们可以更快速的操作数据库,我们可以使用简单的面相对象的API来存储,更新,删除和查询Java对象。

GreenDao的优缺点?

  1. 高性能,下面是官方给出的关于GreenDao,OrmLite和ActiveAndroid三种ORM解决方案的数据统计图:
  2. 易于使用的强大API,涵盖关系和连接;
  3. 最小的内存消耗;
  4. 小库大小(<100KB)以保持较低的构建时间并避免65k方法限制;
  5. 数据库加密:greenDAO支持SQLCipher,以确保用户的数据安全;

3. 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代码生成

要在Android项目中使用GreenDao,您需要添加GreenDao Gradle插件并添加GreenDao库:

  1. 导入插件
// 在 Project的build.gradle 文件中添加:
buildscript {repositories {jcenter()mavenCentral() // add repository}dependencies {classpath 'com.android.tools.build:gradle:3.1.2'classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin}
}
  1. 配置相关依赖
// 在 Moudle:app的  build.gradle 文件中添加:
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugindependencies {implementation 'org.greenrobot:greendao:3.2.2' // add library
}
  1. 配置数据库相关信息
greendao {schemaVersion 1 //数据库版本号daoPackage 'com.aserbao.aserbaosandroid.functions.database.greenDao.db'
// 设置DaoMaster、DaoSession、Dao 包名targetGenDir 'src/main/java'//设置DaoMaster、DaoSession、Dao目录generateTests false //设置为true以自动生成单元测试。targetGenDirTests 'src/main/java' //应存储生成的单元测试的基本目录。默认为 src / androidTest / java。
}

配置完成,点击sync, 在Android Studio中使用Build> Make Project,重写build项目,GreenDao集成完成!如果配置有问题,请先看完接下来2步。

2. 创建存储对象实体类

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

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

3. GreenDao初始化

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

 /*** 初始化GreenDao,直接在Application中进行初始化操作*/private void initGreenDao() {DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "aserbao.db");SQLiteDatabase db = helper.getWritableDatabase();DaoMaster daoMaster = new DaoMaster(db);daoSession = daoMaster.newSession();}private DaoSession daoSession;public DaoSession getDaoSession() {return daoSession;}

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

4. 使用GreenDao实现增删改查

1. 增

insert() 插入数据

 @Overridepublic void insertData(Thing s) {DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();for (int i = 0; i < 1000; i++) {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);}}

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

@Overridepublic void insertData(Thing s) {DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();for (int i = 0; i < 1000; i++) {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.insertOrReplace(student);//插入或替换}}

2. 删

删除有两种方式:delete()和deleteAll();分别表示删除单个和删除所有。

 @Overridepublic void deleteData(Student s) {DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();daoSession.delete(s);}
 @Overridepublic void deleteAll() {DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();daoSession.deleteAll(Student.class);}

3. 改

通过update来进行修改:

@Overridepublic void updataData(Student s) {DaoSession daoSession = ((AserbaoApplication) getApplication()).getDaoSession();daoSession.update(s);}

4. 查

查询的方法有:

  • loadAll():查询所有数据。
  • queryRaw():根据条件查询。
  • queryBuilder() : 方便查询的创建,后面详细讲解。
 public List queryAll(){List<Student> students = daoSession.loadAll(Student.class);return students;}
  @Overridepublic void queryData(String s) {List<Student> students = daoSession.queryRaw(Student.class, " where id = ?", s);mDataBaseAdapter.addNewStudentData(students);}

4. 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 destinationEntityClass):多表查询,后面会讲。
    输出结果有四种方式,选择其中一种最适合的即可,list()返回值是List,而其他三种返回值均实现Closeable,需要注意的不使用数据时游标的关闭操作:
  • list ()所有实体都加载到内存中。结果通常是一个没有魔法的 ArrayList。最容易使用。
  • listLazy ()实体按需加载到内存中。首次访问列表中的元素后,将加载并缓存该元素以供将来使用。必须关闭。
  • listLazyUncached ()实体的“虚拟”列表:对列表元素的任何访问都会导致从数据库加载其数据。必须关闭。
  • listIterator ()让我们通过按需加载数据(懒惰)来迭代结果。数据未缓存。必须关闭。

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" 不为空;
  • orderAsc() 按某个属性升序排;
  • orderDesc() 按某个属性降序排;

1. 使用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;}

查询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;}

3. 嵌套条件查询

查询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;}

4. 多次执行查找

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

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

5. 在多个线程中使用QueryBuilder

如果在多个线程中使用查询,则必须调用 forCurrentThread ()以获取当前线程的Query实例。Query的对象实例绑定到构建查询的拥有线程。

这使您可以安全地在Query对象上设置参数,而其他线程不会干扰。如果其他线程尝试在查询上设置参数或执行绑定到另一个线程的查询,则会抛出异常。像这样,您不需要同步语句。实际上,您应该避免锁定,因为如果并发事务使用相同的Query对象,这可能会导致死锁。

每次调用forCurrentThread ()时, 参数都会在使用其构建器构建查询时设置为初始参数。

2. 使用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;}

5. 注解讲解

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

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

@Entity
public class Student {@Id(autoincrement = true)Long id;@Uniqueint 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方法。
@Entity(schema = "myschema",active = true,nameInDb = "AWESOME_USERS",indexes = {@Index(value = "message DESC", unique = true)},createInDb = false,generateConstructors = true,generateGettersSetters = true
)
public class Student{   ……
}

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: mainProcess: com.example.aserbao.aserbaosandroid, PID: 31939android.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:定义与多个实体对象的关系
    至于如何使用,我们马上就讲。

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

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

1. 一对一

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

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

学生Student代码:

@Entity
public class Student {@Id(autoincrement = true)Long id;@Uniqueint 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;//用户名@UniqueString 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. 一对多

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

  1. 在我们在Student中设置@ToMany(referencedJoinProperty = “id”);
  2. 我们在CreditCard中设置编写对应的id主键;

Student的代码:

@Entity
public class Student {@Id(autoincrement = true)Long id;@Uniqueint studentNo;//学号int age; //年龄String telPhone;//手机号String sex; //性别String name;//姓名String address;//家庭住址String schoolName;//学校名字String grade;//几年级@ToMany(referencedJoinProperty = "id") // 这个id是对应在CreditCard中的idList<CreditCard> creditCardsList;……getter and setter ……}

CreditCard的代码:

@Entity
public class CreditCard {@IdLong id;Long userId;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 teacherList;

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

    @JoinEntity(entity = StudentAndTeacherBean.class,sourceProperty = “teacherId”,targetProperty = “studentId”)
    List studentList;

StudentAndTeacherBean代码:

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

Student 代码:

@Entity
public class Student {@Id(autoincrement = true)Long id;@Uniqueint 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;@Uniqueint 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);}}}

好了,成功;

7. 数据库的升级

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

GreenDao的升级思路:

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

ok,思路就是这样, 总共两个类: 一个MyDaoMaster(OpenHelper继承类),一个MigrationHelper(数据库操作类) 下面是代码编写:

修改Application中的DaoMaster的创建:

         MyDaoMaster helper = new MyDaoMaster(this, "aserbaos.db");
//      DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "aserbao.db");SQLiteDatabase db = helper.getWritableDatabase();DaoMaster daoMaster = new DaoMaster(db);daoSession = daoMaster.newSession();

MyDaoMaster代码:

public class MyDaoMaster extends OpenHelper {private static final String TAG = "MyDaoMaster";public MyDaoMaster(Context context, String name) {super(context, name);}public MyDaoMaster(Context context, String name, SQLiteDatabase.CursorFactory factory) {super(context, name, factory);}@Overridepublic void onUpgrade(Database db, int oldVersion, int newVersion) {super.onUpgrade(db, oldVersion, newVersion);MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {@Overridepublic void onCreateAllTables(Database db, boolean ifNotExists) {DaoMaster.createAllTables(db, ifNotExists);}@Overridepublic void onDropAllTables(Database db, boolean ifExists) {DaoMaster.dropAllTables(db, ifExists);}},ThingDao.class);Log.e(TAG, "onUpgrade: " + oldVersion + " newVersion = " + newVersion);}
}

MigrationHelper 代码:

public final class MigrationHelper {public static boolean DEBUG = false;private static String TAG = "MigrationHelper";private static final String SQLITE_MASTER = "sqlite_master";private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";private static WeakReference<ReCreateAllTableListener> weakListener;public interface ReCreateAllTableListener{void onCreateAllTables(Database db, boolean ifNotExists);void onDropAllTables(Database db, boolean ifExists);}public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {printLog("【The Old Database Version】" + db.getVersion());Database database = new StandardDatabase(db);migrate(database, daoClasses);}public static void migrate(SQLiteDatabase db, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {weakListener = new WeakReference<>(listener);migrate(db, daoClasses);}public static void migrate(Database database, ReCreateAllTableListener listener, Class<? extends AbstractDao<?, ?>>... daoClasses) {weakListener = new WeakReference<>(listener);migrate(database, daoClasses);}public static void migrate(Database database, Class<? extends AbstractDao<?, ?>>... daoClasses) {printLog("【Generate temp table】start");generateTempTables(database, daoClasses);printLog("【Generate temp table】complete");ReCreateAllTableListener listener = null;if (weakListener != null) {listener = weakListener.get();}if (listener != null) {listener.onDropAllTables(database, true);printLog("【Drop all table by listener】");listener.onCreateAllTables(database, false);printLog("【Create all table by listener】");} else {dropAllTables(database, true, daoClasses);createAllTables(database, false, daoClasses);}printLog("【Restore data】start");restoreData(database, daoClasses);printLog("【Restore data】complete");}private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {for (int i = 0; i < daoClasses.length; i++) {String tempTableName = null;DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);String tableName = daoConfig.tablename;if (!isTableExists(db, false, tableName)) {printLog("【New Table】" + tableName);continue;}try {tempTableName = daoConfig.tablename.concat("_TEMP");StringBuilder dropTableStringBuilder = new StringBuilder();dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");db.execSQL(dropTableStringBuilder.toString());StringBuilder insertTableStringBuilder = new StringBuilder();insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");db.execSQL(insertTableStringBuilder.toString());printLog("【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));printLog("【Generate temp table】" + tempTableName);} catch (SQLException e) {Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);}}}private static boolean isTableExists(Database db, boolean isTemp, String tableName) {if (db == null || TextUtils.isEmpty(tableName)) {return false;}String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;String sql = "SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?";Cursor cursor=null;int count = 0;try {cursor = db.rawQuery(sql, new String[]{"table", tableName});if (cursor == null || !cursor.moveToFirst()) {return false;}count = cursor.getInt(0);} catch (Exception e) {e.printStackTrace();} finally {if (cursor != null)cursor.close();}return count > 0;}private static String getColumnsStr(DaoConfig daoConfig) {if (daoConfig == null) {return "no columns";}StringBuilder builder = new StringBuilder();for (int i = 0; i < daoConfig.allColumns.length; i++) {builder.append(daoConfig.allColumns[i]);builder.append(",");}if (builder.length() > 0) {builder.deleteCharAt(builder.length() - 1);}return builder.toString();}private static void dropAllTables(Database db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {reflectMethod(db, "dropTable", ifExists, daoClasses);printLog("【Drop all table by reflect】");}private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {reflectMethod(db, "createTable", ifNotExists, daoClasses);printLog("【Create all table by reflect】");}/*** dao class already define the sql exec method, so just invoke it*/private static void reflectMethod(Database 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, Database.class, boolean.class);method.invoke(null, db, isExists);}} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}private static void restoreData(Database 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 (!isTableExists(db, true, tempTableName)) {continue;}try {// get all columns from tempTable, take careful to use the columns listList<TableInfo> newTableInfos = TableInfo.getTableInfo(db, tableName);List<TableInfo> tempTableInfos = TableInfo.getTableInfo(db, tempTableName);ArrayList<String> selectColumns = new ArrayList<>(newTableInfos.size());ArrayList<String> intoColumns = new ArrayList<>(newTableInfos.size());for (TableInfo tableInfo : tempTableInfos) {if (newTableInfos.contains(tableInfo)) {String column = '`' + tableInfo.name + '`';intoColumns.add(column);selectColumns.add(column);}}// NOT NULL columns listfor (TableInfo tableInfo : newTableInfos) {if (tableInfo.notnull && !tempTableInfos.contains(tableInfo)) {String column = '`' + tableInfo.name + '`';intoColumns.add(column);String value;if (tableInfo.dfltValue != null) {value = "'" + tableInfo.dfltValue + "' AS ";} else {value = "'' AS ";}selectColumns.add(value + column);}}if (intoColumns.size() != 0) {StringBuilder insertTableStringBuilder = new StringBuilder();insertTableStringBuilder.append("REPLACE INTO ").append(tableName).append(" (");insertTableStringBuilder.append(TextUtils.join(",", intoColumns));insertTableStringBuilder.append(") SELECT ");insertTableStringBuilder.append(TextUtils.join(",", selectColumns));insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");db.execSQL(insertTableStringBuilder.toString());printLog("【Restore data】 to " + tableName);}StringBuilder dropTableStringBuilder = new StringBuilder();dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);db.execSQL(dropTableStringBuilder.toString());printLog("【Drop temp table】" + tempTableName);} catch (SQLException e) {Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e);}}}private static List<String> getColumns(Database 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;}private static void printLog(String info){if(DEBUG){Log.d(TAG, info);}}private static class TableInfo {int cid;String name;String type;boolean notnull;String dfltValue;boolean pk;@Overridepublic boolean equals(Object o) {return this == o|| o != null&& getClass() == o.getClass()&& name.equals(((TableInfo) o).name);}@Overridepublic String toString() {return "TableInfo{" +"cid=" + cid +", name='" + name + '\'' +", type='" + type + '\'' +", notnull=" + notnull +", dfltValue='" + dfltValue + '\'' +", pk=" + pk +'}';}private static List<TableInfo> getTableInfo(Database db, String tableName) {String sql = "PRAGMA table_info(" + tableName + ")";printLog(sql);Cursor cursor = db.rawQuery(sql, null);if (cursor == null)return new ArrayList<>();TableInfo tableInfo;List<TableInfo> tableInfos = new ArrayList<>();while (cursor.moveToNext()) {tableInfo = new TableInfo();tableInfo.cid = cursor.getInt(0);tableInfo.name = cursor.getString(1);tableInfo.type = cursor.getString(2);tableInfo.notnull = cursor.getInt(3) == 1;tableInfo.dfltValue = cursor.getString(4);tableInfo.pk = cursor.getInt(5) == 1;tableInfos.add(tableInfo);// printLog(tableName + ":" + tableInfo);}cursor.close();return tableInfos;}}
}

8. 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();

9. 项目地址

当前文章所有代码在AserbaosAndroid/app/src/main/java/com/aserbao/aserbaosandroid/functions/database/greenDao/relation目录下;(不过就我这脾气,可能在今后整理代码的过程中会修改!不过请放心,修改后会在github上进行说明的)

AserbaosAndroid
aserbao的个人Android总结项目,希望这个项目能成为最全面的Android开发学习项目,这是个美好的愿景,项目中还有很多未涉及到的地方,有很多没有讲到的点,希望看到这个项目的朋友,如果你在开发中遇到什么问题,在这个项目中没有找到对应的解决办法,希望你能够提出来,给我留言或者在项目github地址提issues,我有时间就会更新项目没有涉及到的部分!项目会一直维护下去。当然,我希望是Aserbao’sAndroid 能为所有Android开发者提供到帮助!也期望更多Android开发者能参与进来,只要你熟悉Android某一块,都可以将你的代码pull上分支!

10 总结

这篇文章写到这里,零零碎碎花了差不多两周时间,从十月八号开始到今天正式准备发布,也算是对GreenDao数据库的进一步认识!如文章开头所说,我Android开发之初,使用的是自己编写SQLite来实现数据库存储,到后来使用第三方存储LitePal,最近,项目早期就使用了GreenDao,所以就又得学习一番GreenDao。对于开发者来说,我觉得无论是这三种中的哪一种,其实只要掌握一种我觉得就足够了!当然如果你有时间,可以多学习几种,多学无害嘛!

11. 参考博客

Android ORM 框架:GreenDao 使用详解
Android数据存储之GreenDao 3.0 详解
拆轮子系列之GreenDao框架原理分析

12.修改记录

  1. CreditCard中不能只使用一个useId来做关联,因为我这里Teacher和Student都和CreditCard是一对多关系,所以我们需要建两个对应关系字段。为了分辨添加了studentId和teacherId。
@Entity
public class CreditCard {@IdLong id;Long studentId;Long teacherId;String userName;//持有者名字String cardNum;//卡号String whichBank;//哪个银行的int cardType;//卡等级,分类 0 ~ 5
}
  1. Student中的@ToMany(referencedJoinProperty =“id”)这个id对应的是CreditCard中的studentId,不是自增Id。(问题由@山豆几_提出,感谢)
    修改后的代码应该是:
  @ToMany(referencedJoinProperty = "studentId")List<CreditCard> creditCardsList;

一篇好文之Android数据库 GreenDao的使用指南(源码+案列)相关推荐

  1. 一篇好文之Android数据库 GreenDao的完全解析

    数据库GreenDao.jpg 之前在开发过程中,数据库基本上会使用Litepal或者SQlite自己写,最近换新环境,公司原先使用的数据库就是GreenDao,在各种情况的作用下,准备了解下Gree ...

  2. 一篇好文之Android数据库 SQLite全解析

    项目效果地址: SQlite 1. 创建数据库 Android中使用SQlite,需要自己创建库,建表,添加数据!好在Android中提供了SQLiteOpenHelper类来帮助创建使用数据库,我们 ...

  3. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread 流程分析 二 )

    文章目录 前言 一.ActivityManagerService.attachApplicationLocked 二.ActivityStackSupervisor.attachApplication ...

  4. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread -> Activity、主线程阶段 二 )

    文章目录 前言 一.ActivityThread 类 handleLaunchActivity -> performLaunchActivity 方法 二.Instrumentation.new ...

  5. 【Android 启动过程】Activity 启动源码分析 ( ActivityThread -> Activity、主线程阶段 一 )

    文章目录 前言 一.ClientTransactionHandler.scheduleTransaction 二.ActivityThread.H 处理 EXECUTE_TRANSACTION 消息 ...

  6. 【Android 启动过程】Activity 启动源码分析 ( AMS -> ActivityThread、AMS 线程阶段 二 )

    文章目录 前言 一.热启动与冷启动选择 二.AMS 进程中执行的相关操作 三.通过 Binder 机制转到 ActivityThread 中执行的操作 总结 前言 上一篇博客 [Android 启动过 ...

  7. 【Android 电量优化】JobScheduler 相关源码分析 ( JobSchedulerService 源码分析 | 任务检查 | 任务执行 )

    文章目录 一.回调 StateChangedListener 接口 二.JobHandler 处理 ( 任务检查 ) 三.maybeRunPendingJobsH 方法 四.assignJobsToC ...

  8. android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  9. 基于Android+servlet的宠物商店【源码+文档+ppt】

    目录 1.文档目录 1.课题研究目的 2.开发技术 2.1 Android技术 2.2 servlet 3.需求分析 3.1 系统模型 3.2 系统用例图 3.3 顶层数据流图 3.4 零层数据流图 ...

最新文章

  1. ActionScript 3操作XML 详解
  2. k中心点聚类算法伪代码_聚类算法之——K-Means、Canopy、Mini Batch K-Means
  3. 六个经典的HTML5面试问题奉上,太有用啦!
  4. 浏览器阻止window.open的解决方案
  5. 新年第二弹|卖萌屋私藏书单大公开
  6. JAVA入门级教学之(赋值运算符)
  7. 卖身1.8亿的背背佳身后,隐藏了一个“忽悠大师”
  8. python 迭代器、生成器知识点总结
  9. 修改10g自动统计信息收集作业GATHER_STATS_JOB到仅仅周末执行
  10. 集群故障处理之处理思路以及健康状态检查(三十二)
  11. Java语法基础学习DayTwo
  12. cada0图纸尺寸_制图标准规定图纸幅面和格式_A0,A1,A2,A3,A4图纸标准大小和格式_飞翔教程...
  13. JUnit with Ant
  14. flv.js播放报错
  15. 外币兑换c语言编程,货币转换 C
  16. 支付宝批量转帐工具使用说明书
  17. 解密Animate.css之CSS3动画实现方式大全源码(6星级)
  18. JAVA医护人员排班系统计算机毕业设计Mybatis+系统+数据库+调试部署
  19. html中常用判断和工具(二)
  20. CCNP精粹系列之二十九--发布bgp子网信息,推荐

热门文章

  1. iOS滚动的导航条(仿网易新闻)
  2. Ceph 存储集群5-数据归置
  3. python下载视频工具
  4. h5 修改title 微信_微信分享H5自定义标题描述和图片
  5. 音频倍速(变速不变调)的实现
  6. ts15_Forecast multiple seas_mSTL_make_subplot_rMSPE_UCM_date format_NeuralProphet_changepoint_StateS
  7. docker 使用tar安装mysql_Docker安装MySQL
  8. 【C语言算法】归并排序
  9. 如何在iPhone/iPad上录屏
  10. 单片机可以用python编程吗,python可以单片机编程吗