GreenDao3 学习

GreenDao3

以下内容均来自官网 documentation

[TOC]

快速开始

如何快速开始使用:

  • gradle 配置

    //project
    buildscript {
            repositories {
            mavenCentral()
        }
        dependencies {
            classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'
        }
    }
    
    //app module        
    apply plugin: 'org.greenrobot.greendao'
    
    dependencies {
        compile 'org.greenrobot:greendao:3.2.0'
    }
    
  • 在全局 Application 中初始化 DaoSession

    DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this,"notes-db");
    Database db = helper.getWritableDb();
    mDaoSession = new DaoMaster(db).newSession();
    
  • 创建实体类

    通过注解自动生成对应的 Dao 文件

    @Entity(indexes = {@Index(value = "text, date DESC",unique = true)})
    public class Note {
        @Id
        private Long id;
        @NotNull
        private String text;
        private String comment;
        private Date date;
    
        @Convert(converter = NoteTypeConverter.class, columnType = String.class)
        private NoteType type;
    
        @Generated(hash = 1272611929)
        public Note() {
        }
    
        ...
    }
    
  • 增删该查

    DaoSession daoSession = ((App) getApplication()).getDaoSession();
    noteDao = daoSession.getNoteDao();
    
    noteDao.insert(note);
    
    noteDao.deleteByKey(id);
    
    Query<Note> notesQuery = noteDao.queryBuilder().orderAsc(NoteDao.Properties.Text).build();
    List<Note> notes = notesQuery.list();
    

简介

greenDAO 是一个对象关系映射(ORM)的框架,能够提供一个接口通过操作对象的方式去操作关系型数据库,它能够让你操作数据库时更简单、更方便。

Introduction

核心类

CoreClass

  • DaoMaster

    DaoMaster 中根据特定的 schema 管理着数据库对象(SQLiteDatabase)和 Dao 类【DAO classes (not objects)】, 类中有静态的方法可以创建或者删除表,内部类 OpenHelper 和 DevOpenHelper 是 SQLiteOpenHelper 的实现类,可以创建不同模式(schema)的数据库

  • DaoSession

    管理所有 Dao 对象【DAO objects】,可以通过 getXXXDao() 方法取得相应的 Dao 对象,并且也提供了一些通用的方法比如insert, load, update, refresh and delete for entities

  • DAOs

    GreenDao 根据实体类自动生成,相比 DaoSession,具有更多的方法,比如: count, loadAll, and insertInTx

  • Entities

    持久化对象

实体类创建

entities

schema

schema就是数据库对象的集合,这个集合包含了各种对象如:表、视图、存储过程、索引等。为了区分不同的集合,就需要给不同的集合起不同的名字,默认情况下一个用户对应一个集合,用户的schema名等于用户名,并作为该用户缺省schema。所以schema集合看上去像用户名。
如果把database看作是一个仓库,仓库很多房间(schema),一个schema代表一个房间,table可以看作是每个房间中的储物柜,user是每个schema的主人,有操作数据库中每个房间的权利,就是说每个数据库映射的user有每个schema(房间)的钥匙。

greenDao 配置

我们至少应该配置 schema 的版本:

在 app 的 build.gradle 中:

greendao{
    schemaVersion 2              // 当前数据库版本,升级
    // 还支持其他属性,如下
    daoPackage "xxx"             // DAOs, DaoMaster, and DaoSession 目录,默认和 entity 一个目录
    targetGenDir "xxx"            // 自动生成的文件本地目录,默认(build/generated/source/greendao)
    generateTests true/false     // true 自动生成单元测试
    targetGenDirTests "xxx"      // 单元测试目录
}

注解

@Entity
public class User {
    @Id
    private Long id;

    private String name;

    @Transient
    private int tempUsageCount; // not persisted

       // getters and setters for id and user ...
}

@Entity

@Entity 注解让 Java 实体类成为 GreenDao 的持久化对象,这是一个 Retention 为 Source 和 Target 为 TYPE 类型的注解,注解里面的方法有:

String nameInDb() default "";
Index[] indexes() default {};
boolean createInDb() default true;
String schema() default "default";
boolean active() default false;
boolean generateConstructors() default true;
boolean generateGettersSetters() default true;
Class protobuf() default void.class;

对应的我们在使用注解 @Entity 的时候就可以加上相应的配置,如:

@Entity(
    // 每个 entity 对应的 schema
    schema = "myschema",

    // 这个实体类是否为 "active" 的标志,true: Active 的 entities 有 update,
    // delete, and refresh 这些方法.
    active = true,

    // 数据库中对应表名,默认和根据该 entity 生成
    nameInDb = "AWESOME_USERS",

    // 索引,可以创建多列索引
    indexes = {
            @Index(value = "name DESC", unique = true)
    },

    // 这个实体类是否应该建表,默认 true, 如果你有多个 entity 映射到一张表或者之前已经在 GreenDao 外建立了对应的表时设为 false 
    createInDb = false,

    // Whether an all properties constructor should be generated.
    // A no-args constructor is always required.
    generateConstructors = true,

    // Whether getters and setters for properties should be generated if missing.
    generateGettersSetters = true
)
public class User {
      ...
}

基本注解

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

@Property(nameInDb = "USERNAME")
private String name;

@NotNull
private int repos;

@Transient
private int tempUsageCount;

...
}
  • @Id 主键,可以设置是否 autoincrement
  • @Property 重新设置对应数据库表的列名
  • @NotNull 非空
  • @Transient 建表时排除此字段

主键约束

GreenDao 要求必须有一个 long(Long) 形的 id 作为主键,如果我们需要定义自己的 key property,表明该键值唯一 ,只需要加上下面注解:

@Id
private Long id;

@Index(unique = true)
private String key;

注意这里的 @Index 和 @Entity 里面的 @Index, 注解的定义是一样的,区别在于注解方法里面的 value 方法只对 entity 有效。

修改 generated 的 code

在 greenDAO 3 中开发者可以创建和修改 Entity 类,这样随着代码自动生成可能会增加 Entity 的代码量,greenDao 会给方法和变量生成的时候加上一个 @Generated 注解,给开发者提示这是通过 greenDAO 自动生成的,和防止代码缺失,大多数情况下,不应该修改带有 @Generated 注解的代码。

@Generated 注解告诉开发者被该注解标注的变量,构造函数和方法,因为 Entity 的变化在下一次的 run generation 中这些被标记的元素会被改变或者删除

如果 generated 的 code 被我们手动修改时,greenDAO 的处理方式比较谨慎,他不会覆盖之前的代码,并且会报一个错误:

Error:Execution failed for task ':app:greendao'.

会提示 generation 后 XX 方法会被改变,我们要么保持被 @Generated 标注的代码,要么删掉让它重新生成。

或者使用 @Keep 注解,现已不推荐使用。

Sessions

DaoSession 是 greenDAO 的核心接口之一,DaoSession 为开发者提供了 Entity 基本操作和一系列 DAOs 的操作,另外,sessions 也为 entities 管理着 identity scope(不懂这个,文档里有介绍Hibernate’s session to grasp the full concept of sessions and identity scopes.

DaoMaster and DaoSession

之前也提到我们需要先创建一个 DaoMaster 然后通过它得到 DaoSession:

daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();

注意数据库连接属于 DaoMaster ,所以我们创建多个 daoSession 使用的是同一个数据库连接,因此,我们能够快速创建新的 sessions ,然而每一个 session 都需要分配内存,通常每个 entity 实体都会有一个 session “缓存”。

Identity scope and session “cache”

Identity scope 是什么呢?,如果我们有两个 query 返回了相同的数据库记录,到底需要一个还是两个 java 对象,这取决与 Identity scope。

在 greenDAO 中,多次查询返回的是同一个 java objects 的引用,例如我们查询 USER 表 ID 为 42 的记录对于两次查询返回的同一个 User 对象。

这样相当于有一种 entity “cache”,如果 Entity object 仍然在内存中(greenDAO 使用软引用),这样的话 entity 就不会被再次创建,会从 session cache 中马上返回,这样查询速度会快一个或两个数量级。

Clear the identity scope

清除整个 daoSession 的 Identity scope, 此后不会有 缓存 的对象会被返回

daoSession.clear();

清除某一个 DAO 的 Identity scope:

noteDao = daoSession.getNoteDao();
noteDao.detachAll();

查询

查询返回精确条件下的匹配结果,在 greenDAO 中,可以使用 SQL 中的 raw 查询,也可以使用更简单的 QueryBuilder API。查询也支持懒加载,在数据集较大的时候能够节约内存的提高性能。

QueryBuilder

直接写数据库的查询语句比较复杂而且容易出错,出错也只能在运行时能够检测到,使用 QueryBuilder 更加简便也能是错误能够在编译时检测出来。

  • 简单查询

    查询 first name = joe , 根据 last name 排序:

    List joes = userDao.queryBuilder()
      .where(Properties.FirstName.eq("Joe"))
      .orderAsc(Properties.LastName)
      .list();
    
  • 嵌套查询

    查询 first name = joe, 出生在 1970.10 以后

    QueryBuilder qb = userDao.queryBuilder();
    qb.where(Properties.FirstName.eq("Joe"),
    qb.or(Properties.YearOfBirth.gt(1970),
    qb.and(Properties.YearOfBirth.eq(1970), Properties.MonthOfBirth.ge(10))));
    List youngJoes = qb.list();
    
  • Limit, Offset, and Pagination

    有事我们只需要查询结果的一部分,但数据集很大的时候我们也不能通过查询条件来筛选的时候,QueryBuilder 也提供了相应的分页查询方法:

    limit(int):取查询结果的部分数据

    offset(int): 与 limit 结合使用,先跳过 offset 个数据,再取 limit 个条目,不能单独使用。

  • 基本类型作为参数

    通常,能明显察觉到 greenDAO 在查询时候的类型映射,比如 boolean 类型被转换为 INTEGER 的 0 和 1, Date 被映射为 (long)INTEGER ,我们 build 一个 query 的时候必须使用数据库类型值,例如我们定义了枚举变量映射为 int 的值,查询的时候就要用 int 值,greenDAO 提供了 PropertyConverter 类,我们通过继承复写其中的方法能够完成转换。

Query and LazyList

每一次查询都是通过 Query 类来执行,并且可能执行多次,我们使用 QueryBuilder 来查询的时候内部也是调用了 Query 类,如果我们想对某个查询执行多次,我们应该调用 QueryBuilder 的 build 方法保存这个 QueryBuilder,以便下次使用,而不是执行他。

greenDAO 支持 unique results(0 or 1 result) 和 result lists, 如果我们希望查询结果是 unique 的,那么在 Query 或 QueryBuilder 的时候调用 unique(), 这样将会得到一个单独的结果或者是 null, 如果不希望有 null 结果,调用 uniqueOrThrow() ,将会确保结果非空或者抛出异常。如果我们希望查询的结果是列表,调用一下的方法:

  • list()————————-所有记录都被加载进内存,返回结果一般是 ArrayList
  • listLazy()——————-实体延时加载到内存中,一旦列表的第一个元素加载到内存中,开始缓存,必须手动关闭
  • listLazyUncached()——-一个“虚拟”的实体列表:任何访问列表元素的结果从数据库加载数据。必须关闭。
  • listIterator()—————-遍历延时加载的结果列表,数据没有缓存,必须关闭

listLazy(), listLazyUncached(), listIterator() 方法使用了 greenDAO 的 LazyList 类,为了延时加载,它持有一个数据库 cursor 的引用,所以我们必须在使用后关闭,通常用 try/finally 中关闭。在方法 listLazy() 和 listIterator() 中,一旦所有元素存取完成或者遍历完成时,游标会自动关闭,但是出现异常时我们必须调用 close() 方法手动关闭。

执行多次 queries

一旦我们使用 QueryBuilder 来 build 一个 query, 这个 query 能够复用,而不用每次都创建一个新的 Query 。如果查询条件不变可以直接使用,如果发生了变化,也支持修改,例如:

// fetch users with Joe as a first name born in 1970
Query query = userDao.queryBuilder().where(
    Properties.FirstName.eq("Joe"), Properties.YearOfBirth.eq(1970)
).build();
List joesOf1970 = query.list();

// using the same Query object, we can change the parameters
// to search for Marias born in 1977 later:
query.setParameter(0, "Maria");
query.setParameter(1, 1977);
List mariasOf1977 = query.list();
  • 多线程执行查询

    如果我们在多线程中执行查询,我们必须调用 forCurrentThread() 得到一个当前线程的 Query 实例,每一个 Query 都会绑定与创建它的那个线程。这样我们在不同线程里面给 query 设置查询参数时,就会报错,这样我们就不需要同步锁。

Raw queries

为了防止 QueryBuilder 不支持一些查询,仍然有两种方法来支持原始 SQL 查询:

  • 首选使用 QueryBuilder 的 WhereCondition.StringCondition. 这样将查询条件写成 String 来构建 QueryBuilder,例如(使用 join 连接会更好):

    Query query = userDao.queryBuilder().where(
          new StringCondition("_ID IN " +
            "(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)")
    ).build();
    
  • 第二种方法是不用 QueryBuilder 而是用 queryRaw 或 queryRawCreate 方法,它们允许输入原始 Sql 查询字符串,这样我们可以写任意查询条件也可以使用 ORDER BY ,例如:

    Query query = userDao.queryRawCreate(
          ", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin"
    );
    

只是为了举例,graeenDAO 本身支持 joins 操作。

注意:查询时候的表名和列名强烈建议使用自动生成的名字。

Delete queries

批量删除不删除单个实体,是删除达到条件的所有实体,为了批量删除创建一个 QueryBuilder 调用 buildDelete() 方法,执行结果返回删除掉的 DeleteQuery。

这一部分的 API 未来会改动。

记住一点批量删除不影响在 identity scope 中的 entities。

查询调试

有两个静态的 flag 我们可以知道 QueryBuilder 过程中的日志:

QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;    

query builder 中的 Join

join

比较复杂的查询需要从好几个表中找出数据,在数据库中我们可以用连接操作 join 将变连接起来。示例: User 表对 Address 表时一对多的关系,查询地址是 “Sesame Street” 的 user, 我们将把 Address 连接的 User 表通过 user id 和 一个 Address 表的 where 条件:

QueryBuilder<User> queryBuilder = userDao.queryBuilder();
queryBuilder.join(Address.class, AddressDao.Properties.userId)
  .where(AddressDao.Properties.Street.eq("Sesame Street"));
List<User> users = queryBuilder.list();

Join 操作需要目标 entity 作为参数并且每一个表(entity) 的一个属性作为连接条件,示例中,只有 Address 中的属性参与了连接,因为主键使用的是默认的,换句话说,查询结果中的用户里面有一个 address entity 的 user id 等于 User entity 中的 id ,并且地址是查询条件中的那个地址。

QueryBuilder Join API

当主键也被用于连接的条件时我们能够省略一个连接属性条件,QueryBuilder 中有 3 个重载方法:

/**
  * Expands the query to another entity type by using a JOIN.
  * The primary key property of the primary entity for
  * this QueryBuilder is used to match the given destinationProperty.
  */
public <J> Join<T, J> join(Class<J> destinationEntityClass, Property destinationProperty)

/**
 * Expands the query to another entity type by using a JOIN.
 * The given sourceProperty is used to match the primary
 * key property of the given destinationEntity.
 */
public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass)

/**
 * Expands the query to another entity type by using a JOIN.
 * The given sourceProperty is used to match the given
 * destinationProperty of the given destinationEntity.
 */
public <J> Join<T, J> join(Property sourceProperty, Class<J> destinationEntityClass,
    Property destinationProperty)

Chained Joins

greenDAO 也支持 chain joins:

/**
 * Expands the query to another entity type by using a JOIN.
 * The given sourceJoin's property is used to match the
 * given destinationProperty of the given destinationEntity.
 * Note that destination entity of the given join is used
 * as the source for the new join to add. In this way,
 * it is possible to compose complex "join of joins" across
 * several entities if required.
 */
public <J> Join<T, J> join(Join<?, T> sourceJoin, Property sourceProperty,
    Class<J> destinationEntityClass, Property destinationProperty)

示例:洲、国家、城市三个表,查找欧洲人口在 100 万以上的城市:

QueryBuilder qb = cityDao.queryBuilder().where(Properties.Population.ge(1000000));
Join country = qb.join(Properties.CountryId, Country.class);
Join continent = qb.join(country, CountryDao.Properties.ContinentId,
      Continent.class, ContinentDao.Properties.Id);
continent.where(ContinentDao.Properties.Name.eq("Europe"));    
List<City> bigEuropeanCities = qb.list();

Self Join

自连接:

QueryBuilder qb = personDao.queryBuilder();
Join father = qb.join(Person.class, Properties.FatherId);
Join grandfather = qb.join(father, Properties.FatherId, Person.class, Properties.Id);
grandfather.where(Properties.Name.eq("Lincoln"));
List<Person> lincolnDescendants = qb.list();    

Relations(to-one and to-many)

在 greenDAO 中,entities 有两种关系,1-1 或 1-n , 如果我们想要用 greenDAO 创建一个 1-n 关系的 entity, 同事要有一个 1-1 和 1-n,并且这两个没有关联,升级的时候要同时升级。

To-One Relations

使用注解 @ToOne 来定义:

@Entity
public class Order {
    @Id private Long id;

    private long customerId;

    @ToOne(joinProperty = "customerId")
    private Customer customer;
}

@Entity
public class Customer {
    @Id private Long id;
}

To-Many Relations

使用注解 @ToMany 定义,有三种方法,使用的时候用一种就可以:

  • referencedJoinProperty

    @Entity
    public class Customer {
        @Id private Long id;
    
        @ToMany(referencedJoinProperty = "customerId")
        @OrderBy("date ASC")
        private List<Order> orders;
    }
    
    @Entity
    public class Order {
        @Id private Long id;
        private Date date;
        private long customerId;
    }
    
  • joinProperties

    @Entity
    public class Customer {
        @Id private Long id;
        @Unique private String tag;
    
        @ToMany(joinProperties = {
        @JoinProperty(name = "tag", referencedName = "customerTag")
    })
    @OrderBy("date ASC")
    private List<Site> orders;
    }
    
    @Entity
    public class Order {
            @Id private Long id;
        private Date date;
        @NotNull private String customerTag;
    }
    
  • JoinEntity

    @Entity
    public class Product {
        @Id private Long id;
    
        @ToMany
        @JoinEntity(
            entity = JoinProductsWithOrders.class,
            sourceProperty = "productId",
            targetProperty = "orderId"
        )
    private List<Order> ordersWithThisProduct;    
    }    
    
    @Entity
    public class JoinProductsWithOrders {
        @Id private Long id;
        private Long productId;
        private Long orderId;
    }
    
    @Entity
    public class Order {
        @Id private Long id;
    }
    

更新 toMany 关系…

greenDAO 支持的基本类型

boolean, Boolean
int, Integer
short, Short
long, Long
float, Float
double, Double
byte, Byte
byte[]
String
Date

利用 Convert 注解实现其他类型转换为基本类型,如枚举转换为 int:

@Entity
public class User {
@Id
private Long id;

@Convert(converter = RoleConverter.class, columnType = Integer.class)
private Role role;

public enum Role {
    DEFAULT(0), AUTHOR(1), ADMIN(2);

    final int id;

    Role(int id) {
        this.id = id;
    }
}

public static class RoleConverter implements PropertyConverter<Role, Integer> {
    @Override
    public Role convertToEntityProperty(Integer databaseValue) {
        if (databaseValue == null) {
            return null;
        }
        for (Role role : Role.values()) {
            if (role.id == databaseValue) {
                return role;
            }
        }
        return Role.DEFAULT;
    }

    @Override
    public Integer convertToDatabaseValue(Role entityProperty) {
        return entityProperty == null ? null : entityProperty.id;
    }
}
}

数据库加密

greenDAO 支持数据库加密,以保护敏感数据。

greenDAO支持SQLCipher直接绑定。SQLCipher 是SQLite使用256位AES加密自定义构建的。

如何使用:

  1. 添加 SQLCipher 依赖 SQLCipher for Android
  2. 数据库初始化

    DevOpenHelper helper = new DevOpenHelper(this, "notes-db-encrypted.db");
    Database db = helper.getEncryptedWritableDb("<your-secret-password>");
    daoSession = new DaoMaster(db).newSession();
    

### 支持 Robolectric 单元测试

官网java文档更新

greenDAO JavaDoc