GreenDao 3.0 基本使用

3,676 阅读7分钟

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版本,这里提一句apiimplementation区别只是影响编译期的依赖传递,我是在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中,存在BookChapter两个类,他们之间的关系,一本书有他的章节列表,那么他们就是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 使用全面讲解