GreenDao是一款Android上的ORM框架(对象/关系映射),在数据库开发中能帮我们从枯燥的CRUD中节约大量的时间,这里针对GreenDao3.0以后的版本做一个简单的使用介绍。
1、引入
GreenDao Github地址
引入Gradle插件,
在根目录的build.gradle中,加入GreenDao的插件依赖地址:
dependencies {
classpath 'com.android.tools.build:gradle:3.4.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.jakewharton:butterknife-gradle-plugin:10.2.1"
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // greendao 插件依赖
}
在包含entity注解的实体类所在module的build.gradle中,加入使用插件:
apply plugin: 'org.greenrobot.greendao'
在base module或者你的数据库管理类所在的module的build.gradle中加入基本依赖:
api 'org.greenrobot:greendao:3.2.2'
GreenDao的基本配置:
greendao {
//数据库的schema版本,也可以理解为数据库版本号,如果版本升级,数据库表字段发生变化,必须要升级
schemaVersion 1
//设置DaoMaster、DaoSession、Dao包名,也就是要放置这些类的包的全路径。可以不设置,使用默认值
daoPackage 'tsou.com.simple.greendaoforkotlin.greendao'
//设置DaoMaster、DaoSession、Dao目录 , 可以不设置,使用默认值
targetGenDir 'src/main/java'
}
如果使用混淆,那么需要加入以下配置:
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties { *; }
# If you DO use SQLCipher:
-keep class org.greenrobot.greendao.database.SqlCipherEncryptedHelper { *; }
# If you do NOT use SQLCipher:
-dontwarn net.sqlcipher.database.**
# If you do NOT use RxJava:
-dontwarn rx.**
这里引入的是3.2.2版本,这里提一句api和implementation区别只是影响编译期的依赖传递,我是在base模块引入的,为了让应用模块都能正常使用,所以使用的是api来引入。
上述依赖都添加完成之后,写一个作为表的entity的case:
@Entity
public class Chapter {
public Chapter(String name) {
this.name = name;
}
/**
* 章节名字
*/
public String name;
/**
* 章节内容
*/
public String content;
/**
* 章节url
*/
public String url;
}
然后编译一次工程,就会生成对应的DaoMaster,DaoSession,XXXDao了。 这里需要注意,作为entity的数据实体类,必须使用java编写,目前还不支持kotin
2、基本使用
接下来我们对数据库进行操作,首先我们需要能够对数据库进行创建打开升级等操作,这里会创建一个操作类:
// 这里的name参数,就是我们的数据库文件的文件名,如果以后需要建立多个数据库,那么创建helper的时候传入不同的名字就可以了
class MyOpenHelper(context : Context, name : String) : DaoMaster.DevOpenHelper(context , name){
override fun onUpgrade(db: Database?, oldVersion: Int, newVersion: Int) {
// 在这里进行升级的处理,这个升级的辅助类在文章最后会说
MigrationHelper.migrate(db , BookDao::class.java , ChapterDao::class.java)
}
}
然后我们可以创建一个类,帮住我们管理数据库:
object DBManager {
// 这里可以保存下这个DaoSession,这是和数据库的一个会话链接,后续的增删改查都要通过他来进行
lateinit var mDaoSession : DaoSession
fun initGreenDao(context: Context){
// 这里创建了一个数据库,并获得了一个可写的数据库链接,并保存下来
val helper = MyOpenHelper(context, "hanli_book")
val writableDatabase = helper.getWritableDatabase()
val master = DaoMaster(writableDatabase)
mDaoSession = master.newSession()
}
}
然后就可以开始基本的增删改查了:
// 直接通过daoSession插入,和使用BookDao插入效果一样,DaoSession也会根据传入的实体寻找对应的XXXDao来做对应操作
// 但是使用XXXDao操作,可以指定选择使用transaction来操作,还可以操作多条数据
DBManager.mDaoSession.insert(book)
DBManager.mDaoSession.bookDao.insert(book)
DBManager.mDaoSession.bookDao.insertInTx(book)
DBManager.mDaoSession.bookDao.insertOrReplace(book) // 如果没有就插入,如果有就替换,这里以主键一样则认为是同一个对象
// 这是更新某一个实体,他会根据你的主键来查找对象并更新
DBManager.mDaoSession.update(book)
DBManager.mDaoSession.bookDao.updateInTx(book) // 可以操作多条数据
// 删除一条数据和删除整个表的数据
DBManager.mDaoSession.delete(book)
DBManager.mDaoSession.bookDao.deleteAll()
下面说一下查询,查询还可以结合增删改一起操作,因为很多场景我们都不是根据主键来操作,在我的case中,比如我可能想删除一本叫《诛仙》的书:
DBManager.mDaoSession.bookDao.loadAll() // 找出整张表所有书
// 这里就是拼sql来查询了
val resultList = DBManager.mDaoSession.bookDao.queryRaw("where bookName = ?", "诛仙")
DBManager.mDaoSession.bookDao.deleteInTx(resultList) // 将所有名字叫诛仙的书都删除掉
由于拼sql存在一些语法不熟,不小心写错了等问题,我们还可以使用GreenDao提供的方法来操作:
val list = DBManager.mDaoSession.bookDao.queryBuilder()
.where(BookDao.Properties.BookName.eq("诛仙"))
.list()
以下是一些方法的含义:
orderAsc: 升序排序
orderDesc: 降序排序
eq():==
noteq():!=
gt(): >
t():<
ge:>=
le:<=
like():包含
between:俩者之间
in:在某个值内
notIn:不在某个值内
3、多表关联,@ToMany、@ToOne的使用
3.1、1对多关系
在大多数的场景中都会有多表互相依赖的问题,在我的case中,存在Book和Chapter两个类,他们之间的关系,一本书有他的章节列表,那么他们就是1对多的关系,如下所示:
@Entity
public class Book implements Serializable {
public static final long serialVersionUID = 1231232L;
@Id(autoincrement = true) // 这是以bookId作为主键,自增
public Long bookId;
/**
* 书名
*/
public String bookName;
/**
* 本书对应的章节
*/
// 这里使用@ToMany标签,表示1对多的关系,referencedJoinProperty声明关联的字段
// 这里的bookId,是指Chapter类中需要有一个bookId,该字段和Book类的主键相等,则表示他们关联关系成立
// 主键是唯一的,这样能够保证书本Book的一,对应章节Chapter的多
// 在使用GreenDao查询Book的时候,会自动的将关联的Chapter查询出来填充到List中
@ToMany(referencedJoinProperty = "bookId")
public List<Chapter> chapters;
}
//Chapter类示例:
@Entity
public class Chapter {
/**
* 章节url
*/
@Id
private String chapterUrl;
/**
* 章节名字
*/
public String name;
/**
* 所属的书本id
*/
// 对应Book类中的主键
public Long bookId;
/**
* 章节内容
*/
public String content;
}
3.2、1对1关系
接下来是1对1的关系,例如1本书肯定会有并且只有一个正在阅读的章节,那么他们就是1对1的关系,可以如下表示:
@Entity
public class Book implements Serializable {
public static final long serialVersionUID = 1231232L;
@Id(autoincrement = true)
public Long bookId;
/**
* 书名
*/
public String bookName;
/**
* 当前正在阅读章节
*/
// 这使用@ToOne表示1对1关系,指定readingChapterUrl作为关联字段
// 这里的readingChapterUr指的是自己Book类中的字段,如果它和Chapter类中的主键相等,那么1对1的关系就成立了
// 使用GreenDao查询Book的时候,会自动找到对应的Chapter并填充
@ToOne(joinProperty = "readingChapterUrl")
public Chapter readingChapter;
/**
* 当前正在读章节的url,给greenDao使用
*/
private String readingChapterUrl;
}
//Chapter类示例:
@Entity
public class Chapter {
/**
* 章节url
*/
// 这里是Chapter的主键,给Book作为1对1关系绑定使用
@Id
private String chapterUrl;
/**
* 章节名字
*/
public String name;
/**
* 所属的书本id
*/
// 对应Book类中的主键
public Long bookId;
/**
* 章节内容
*/
public String content;
}
3.3 多对多关系
最后我们还有多对多的关系,这个稍微麻烦一点,使用到的场景一般也比较少,需要声明一个关联类,来绑定多对多的关系双方,例如假设我们1本书可以有多个精彩章节,1个章节也可以对应多本书(例子不太恰当,大家能理解就行):
@Entity
public class BookAndChapter {
@Id(autoincrement = true)
Long id;
// 对应book中的主键
Long bookId;
// 对应章节chapter中的主键
String chapterUrl;
}
@Entity
public class Book implements Serializable {
public static final long serialVersionUID = 1231232L;
@Id(autoincrement = true)
public Long bookId;
// 这里使用@ToMany和@JoinEntity来组合使用,属性中entity填入关联关系对象BookAndChapter的class,
// sourceProperty填入BookAndChapter类中代表当前类Book的主键bookId
// targetProperty填入BookAndChapter类中代表Chapter的主键chapterUrl
// 注意,这里填入的名称都是BookAndChapter中的字段名称,而不是Book和Chapter中的主键字段名称
@ToMany @JoinEntity(entity = BookAndChapter.class , sourceProperty = "bookId" ,
targetProperty = "chapterUrl")
public List<Chapter> hotChapters;
}
@Entity
public class Chapter {
@Id
private String chapterUrl;
public String name;
// 这里基本和Book类中一样,不过sourceProperty和targetProperty的值反过来了
@ToMany
@JoinEntity(entity = BookAndChapter.class , sourceProperty = "chapterUrl" , targetProperty = "bookId")
public List<Book> belongBooks;
}
这里声明就完了,如果需要将某些Book和Chapter绑定起来,那么需要向数据库中插入绑定关系BookAndChapter对象:
fun testGreenDao(){
// 现在有关系book1 对应 chapter1 , chapter3
// book2 对应 chapter1 , chapter2
var book1 = Book("书本1")
book1.bookId = 1
var book2 = Book("书本2")
book2.bookId = 2
var chapter1 = Chapter("章节1")
chapter1.url = "章节1"
var chapter2 = Chapter("章节2")
chapter2.url = "章节2"
var chapter3 = Chapter("章节3")
chapter3.url = "章节3"
DBManager.mDaoSession.bookDao.insertInTx(book1)
DBManager.mDaoSession.bookDao.insertInTx(book2)
DBManager.mDaoSession.chapterDao.insertInTx(chapter1)
DBManager.mDaoSession.chapterDao.insertInTx(chapter2)
DBManager.mDaoSession.chapterDao.insertInTx(chapter3)
// 这时候建立对应关系
DBManager.mDaoSession.bookAndChapterDao.insertInTx(BookAndChapter(book1.bookId , chapter1.url))
DBManager.mDaoSession.bookAndChapterDao.insertInTx(BookAndChapter(book1.bookId , chapter3.url))
DBManager.mDaoSession.bookAndChapterDao.insertInTx(BookAndChapter(book2.bookId , chapter3.url))
DBManager.mDaoSession.bookAndChapterDao.insertInTx(BookAndChapter(book2.bookId , chapter2.url))
// 打印出来
printBook(DBManager.mDaoSession.bookDao.load(book1.bookId))
printBook(DBManager.mDaoSession.bookDao.load(book2.bookId))
printChapter(DBManager.mDaoSession.chapterDao.load(chapter1.url))
printChapter(DBManager.mDaoSession.chapterDao.load(chapter2.url))
printChapter(DBManager.mDaoSession.chapterDao.load(chapter3.url))
}
fun printBook(book : Book){
val sb = StringBuilder()
sb.append(book.bookName + "[")
val hotChapters = book.getHotChapters()
hotChapters?.let {
for (item in hotChapters){
sb.append(item.name + " , ")
}
}
sb.append("]")
println("result: $sb")
}
fun printChapter(chapter: Chapter){
val sb = StringBuilder()
sb.append(chapter.name + "[")
val belongBooks = chapter.getBelongBooks()
belongBooks?.let {
for (item in belongBooks){
sb.append(item.bookName + " , ")
}
}
sb.append("]")
println("result: $sb")
}
执行结果:
result: 书本1[章节1 , 章节3 , ]
result: 书本2[章节3 , 章节2 , ]
result: 章节1[书本1 , ]
result: 章节2[书本2 , ]
result: 章节3[书本1 , 书本2 , ]
能看到执行结果是符合我们预期的
PS: 需要注意的问题
1、java和kotlin工程 纯java工程基本没有影响,但是在kotlin工程中,entity一定要用java写,GreenDao不支持kotlin写的entity;然后如果发现运行的时候一直提示找不到XXXDao,但是你IDE里面能找得到没报错,那么在module的build.gradle中加上:
android {
···
sourceSets {
main.java.srcDirs += 'build/generated/source/greendao'
}
}
因为GreenDao生成的这个资源目录,在kotlin中默认不属于源码目录
2、数据库升级技巧 可以看到上面的代码中我使用的是一个帮助类:
MigrationHelper.migrate(db , BookDao::class.java , ChapterDao::class.java)
在这里他会自动比较你传入的XXXDao和之前数据库中的表字段差异,自动进行修复,还是比较好用的,常规的每个版本人工修改变化,我之前数据库升级方法有几百行了。。。。
引用:
// 数据库升级依赖
api 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.1.0'
3、注意get方法 类似于刚刚介绍的多表关联的字段,不可以直接访问该字段,因为这个字段的填充是在他的get方法中进行赋值的,在entity类中可以看到GreenDao会将该字段的get方法重写,所以访问尽量通过get和set方法进行
参考资料: Android GreenDao 使用全面讲解