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)的框架,能够提供一个接口通过操作对象的方式去操作关系型数据库,它能够让你操作数据库时更简单、更方便。
核心类
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
持久化对象
实体类创建
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加密自定义构建的。
如何使用:
- 添加 SQLCipher 依赖 SQLCipher for Android
数据库初始化
DevOpenHelper helper = new DevOpenHelper(this, "notes-db-encrypted.db"); Database db = helper.getEncryptedWritableDb("<your-secret-password>"); daoSession = new DaoMaster(db).newSession();
### 支持 Robolectric 单元测试