Markdown版本笔记 | 个人GitHub首页 | 个人博客 | 个人微信 | 个人邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
ORM数据库框架 greenDAO SQLite MDjava
针对Android进行了高度优化,性能卓越,占用内存极
少。Protocol buffers
协议数据的直接存储,若是经过protobuf协议和服务器交互,不须要任何的映射。Protocol Buffers协议:以一种高效
可扩展
的对结构化数据
进行编码
的方式。google内部的RPC
协议和文件格式大部分都是使用它。
RPC:Remote Procedure Call
,远程过程调用
,是一个计算机通讯协议,它是一种经过网络从远程计算机程序上请求服务,而不须要了解底层网络技术的协议。android
特性:git
Android上最快的ORM
,由智能代码生成器
驱动QueryBuilder
使用属性常量来避免拼写错误[typos]SQLCipher
加密数据库优势:github
100k
大小内存缓存
中,下次使用时能够直接从缓存中取,这样可使性能提升N个数量级代码自动生成
:greenDao 会根据modle类自动生成 DAO 类(DaoMaster、DaoSession 和 **DAO)greenrobot 其余流行的开源项目:正则表达式
面向对象数据
库[object-oriented database]。是比SQLite更快的对象持久化方案[object persistence]。中央发布/订阅总线
,具备可选的传递线程
[delivery],优先级
和粘性事件
[sticky]。这是一个很是好的工具,能够将组件 (e.g. Activities, Fragments, logic components)彼此分离。文档地址sql
greenDA:适用于您 SQLite 数据库的 Android ORM
注意:对于新的应用程序,咱们建议使用 ObjectBox,这是一个新的面向对象的数据库,它比 SQLite 快得多而且更易于使用。 对于基于 greenDAO 的现有应用程序,咱们提供 DaoCompat 以实现轻松切换(另请参阅 announcement)。数据库
greenDAO是一个开源的 Android ORM,使 SQLite 数据库的开发再次变得有趣。它减轻了开发人员处理低级数据库需求的同时节省了开发时间。 SQLite是一个很棒的嵌入式关系数据库。尽管如此,编写 SQL 和解析查询结果仍然是一项很是繁琐且耗时的任务。greenDAO 经过将 Java 对象映射到数据库表(称为ORM,“对象/关系映射”)将您从这些中解放出来。 这样,您可使用简单的面向对象的API来存储,更新,删除和查询Java对象。express
greenDAO的特色一览浏览器
您想了解有关greenDAO功能的更多信息,例如活动实体[active entities],protocol buffers 支持或者预加载[eager loading]? 能够看看咱们的完整功能列表。缓存
如何开始使用greenDAO,文档
有关greenDAO的第一步,请查看 documentation,尤为是 getting started guide 和 introduction tutorial。
谁在使用greenDAO?
许多顶级Android应用都依赖于greenDAO。 其中一些应用程序的安装量超过1000万。 咱们认为,这代表在行业中是可靠的。 在 AppBrain 上查看本身的当前统计数据。
greenDAO真的那么快吗? 它是最快的Android ORM吗?
咱们相信它是。咱们不是营销人员,咱们是开发人员。 咱们常常作基准测试来优化性能,由于咱们认为性能很重要。 咱们但愿提供最快的Android ORM。 虽然有些事情让咱们感到很自豪,但咱们并不热衷于营销演讲。 咱们全部的基准测试都是开源的,能够在达到高标准的同时实现最大透明度。你能够本身检查最新的基准测试结果并得出本身的结论。
此库中注解的保留策略都是:@Retention(RetentionPolicy.SOURCE)
源文件
中有效, 编译器要丢弃的注解
。greenDAO尝试使用合理的默认值,所以开发人员没必要配置每个属性值。
例如,数据库端的表名和列名
是从实体名和属性名
派生的,而 不是 Java中使用的驼峰案例样式,默认数据库名称是大写的,使用下划线来分隔单词
。例如,名为 creationDate 的属性将成为数据库列CREATION_DATE。
@Entity
注解用于将 Java 类转换为数据库支持的实体。这也将指示 greenDAO 生成必要的代码(例如DAO)。
注意:仅支持Java类。 若是你喜欢另外一种语言,如Kotlin,你的实体类仍然必须是Java。
@Entity( //为 greendao 指明这是一个须要映射到数据库的实体类,一般不须要任何额外的参数 nameInDb = "AWESOME_USERS",// 指定该表在数据库中的名称,默认是基于实体类名 indexes = {@Index(value = "name DESC", unique = true)},// 在此处定义跨越多列的索引 createInDb = true,// 是否建立该表。默认为true。若是有多个实体映射到一个表,或者该表是在greenDAO外部建立的,则可置为false schema = "myschema",// 若是您有多个模式,则能够告诉greenDAO实体所属的模式(选择任何字符串做为名称) active = true,// 标记一个实体处于活动状态,活动实体(设置为true)有更新、删除和刷新方法。默认为false generateConstructors = true,//是否生成全部属性的构造器。注意:无参构造器老是会生成。默认为true generateGettersSetters = true//是否为属性生成getter和setter方法。由于**Dao会用到这些方法,若是不自动生成,必须手动生成。默认为true )
注意,当使用Gradle插件时,目前不支持多个模式。目前,继续使用你的 generator 项目。
@Target(ElementType.TYPE) public @interface Entity { String nameInDb() default ""; //指定此实体映射到的DB侧的名称(例如,表名)。 默认状况下,名称基于实体类名称。 Index[] indexes() default {}; //注意:要建立单列索引,请考虑在属性自己上使用 Index boolean createInDb() default true; //高级标志,设置为false时可禁用数据库中的表建立。这能够用于建立部分实体,其可能仅使用子属性的子集。但请注意,greenDAO不会同步多个实体,例如在缓存中。 String schema() default "default"; //指定实体的模式名称:greenDAO能够为每一个模式生成独立的类集。属于不一样模式的实体应不具备关系。 boolean active() default false; //是否应生成更新/删除/刷新方法。若是实体已定义 ToMany 或 ToOne 关系,那么它 active 独立于此值 boolean generateConstructors() default true; //是否应生成一个具备全部属性构造函数。一个 no-args 的构造函数老是须要的。 boolean generateGettersSetters() default true; //若是缺乏,是否应生成属性的getter和setter。 Class protobuf() default void.class; //定义此实体的protobuf类,以便为其建立额外的特殊DAO。 }
添加 active = true
时会自动生成如下代码:
@Generated(hash = 2040040024) private transient DaoSession daoSession; //Used to resolve relations @Generated(hash = 363862535) private transient NoteDao myDao; //Used for active entity operations. //被内部机制所调用,本身不要调用 @Generated(hash = 799086675) public void __setDaoSession(DaoSession daoSession) { this.daoSession = daoSession; myDao = daoSession != null ? daoSession.getNoteDao() : null; } //方便调用 org.greenrobot.greendao.AbstractDao 的 delete(Object) 方法。实体必须附加到实体上下文。 @Generated(hash = 128553479) public void delete() { if (myDao == null) throw new DaoException("Entity is detached from DAO context"); myDao.delete(this); } //方便调用 org.greenrobot.greendao.AbstractDao 的 refresh(Object) 方法。实体必须附加到实体上下文。 @Generated(hash = 1942392019) public void refresh() { if (myDao == null) throw new DaoException("Entity is detached from DAO context"); myDao.refresh(this); } //方便调用 org.greenrobot.greendao.AbstractDao 的 update(Object) 方法。实体必须附加到实体上下文。 @Generated(hash = 713229351) public void update() { if (myDao == null) throw new DaoException("Entity is detached from DAO context"); myDao.update(this); }
设置 generateConstructors = false
时,若是没有无参的构造方法,则会生成一个普通的无参构造方法(若是已存在此无参构造方法,则不会生成):
public Note() {}
添加 generateConstructors = true
时,若是没有无参的构造方法,则会生成一个带 @Generated
注解的无参构造方法(若是已存在此无参构造方法,则不会生成):
@Generated(hash = 1272611929) public Note() { }
而且会生成一个带 @Generated
注解的具备全部属性的构造方法(若是已存在此具备全部属性的构造方法,会编译时会报错,提示你 Can't replace constructor
):
@Generated(hash = 2139673067) public Note(Long id, @NotNull String text, Date date, NoteType type) { this.id = id; this.text = text; this.date = date; this.type = type; }
@Id 注解选择long/Long
属性做为实体ID,在数据库术语中,它是主键[primary key]。能够经过 autoincrement = true
设置自增加(不重用旧值)
@Target(ElementType.FIELD) public @interface Id { boolean autoincrement() default false; //指定id应自增加(仅适用于Long/long字段)。SQLite上的自增加会引入额外的资源使用,一般能够避免 }
官方文档对主键的说明:
目前,实体必须使用long或Long属性做为其主键。这是Android和SQLite的推荐作法。
要解决此问题[To work around this],请将你的键属性定义为其余属性[additional property],但为其建立惟一索引[create a unique index for it]:
@Id private Long id; @Index(unique = true) private String key;
@Property(nameInDb = "")
为该属性在数据库中映射的字段名设置一个非默认的名称。默认是将单词大写,用下划线分割单词,如属性名 customName 对应列名 CUSTOM_NAME
//可选:为持久字段配置映射列。 此注释也适用于@ToOne,无需额外的外键属性 @Target(ElementType.FIELD) public @interface Property { String nameInDb() default ""; //此属性的数据库列的名称。 默认为字段名称。 }
@Property(nameInDb = "USERNAME") private String name;
@NotNull
代表这个列非空,一般使用 @NotNull 标记基本类型,然而可以使用包装类型(Long, Integer, Short, Byte)使其可空
//您还可使用任何其余NotNull或NonNull注释(来自任何库或您本身的),它们是等价的 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) public @interface NotNull { }
@NotNull private int age; @NotNull private String name; //在保存到数据库中以前须要保证此值不为null,不然会报 IllegalArgumentException
@Transient 代表此字段不存储到数据库中,用于不须要持久化的字段,好比临时状态
@Target(ElementType.FIELD) public @interface Transient { }
@Transient private int tempUsageCount;
@Index 为相应的数据库列[column]建立数据库索引[index]。若是不想使用 greenDAO 为该属性生成的默认索引名称,可经过name
设置;向索引添加UNIQUE
约束,强制全部值是惟一的。
//能够用来:一、指定应对属性编制索引;二、经过 Entity 的 indexes() 定义多列索引 @Target(ElementType.FIELD) public @interface Index { String value() default ""; //以逗号分隔的应该 be indexed 的属性列表,例如 “propertyA,propertyB,propertyC”。要指定顺序,请在列名后添加 ASC 或 DESC,例如:“propertyA DESC,propertyB ASC”。只有在 Entity 的 indexes() 中使用此注解时才应设置此项 String name() default ""; //索引的可选名称。若是省略,则由 greenDAO 自动生成基于属性列名称 boolean unique() default false; //是否应该基于此索引建立惟一约束 }
@Index(name = "indexNo", unique = true) private String name;
@Unique 为相应列添加惟一约束。注意,SQLite会隐式地为该列建立索引
//在表建立期间标记属性应具备UNIQUE约束。此注释也适用于 @ToOne,无需额外的外键属性。要在建立表后拥有惟一约束,可使用 Index 的 unique()。注意同时拥有 @Unique 和 Index 是多余的,会致使性能下降 @Target(ElementType.FIELD) public @interface Unique { }
@Unique private String name;
@Generated
用以标记由 greenDAO 自动生成的字段、构造函数或方法。
greenDAO中的实体类由开发人员建立和编辑。然而,在代码生成过程当中,greenDAO可能会增长实体的源代码。greenDAO将为它建立的方法和字段添加一个@Generated
注解,以通知开发人员并防止任何代码丢失。在大多数状况下,你没必要关心使用@Generated
注解的代码。
做为预防措施,greenDAO不会覆盖现有代码,而且若是手动更改生成的代码会引起错误:
Constructor (see Note:34) has been changed after generation.
Please either mark it with @Keep annotation instead of @Generated to keep it untouched[保持不变], or use @Generated (without hash) to allow to replace it[容许替换].
正如错误消息所提示的,一般有两种方法来解决此问题:
@Generated
注解的代码的更改。或者,你也能够彻底删除更改的构造函数或方法,它们将在下一次build时从新生成。@Keep
注解替换@Generated
注解。这将告诉greenDAO永远不要触摸[touch]带注解的代码。请记住,你的更改可能会中断实体和其余greenDAO之间的约定[break the contract]。 此外,将来的greenDAO版本可能指望生成的方法中有不一样的代码。因此,要谨慎!采用单元测试的方法来避免麻烦是个不错的选择。//All the code elements that are marked with this annotation can be changed/removed during next run of generation in respect of[就...而言] model changes @Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface Generated { int hash() default -1; }
@Keep
指定在下次运行 greenDAO 生成期间应保留的目标。
在 Entity 类上使用此批注会以静默方式禁用任何类修改[itself silently disables any class modification]。由用户负责编写和支持 greenDAO 所需的任何代码。若是您不彻底肯定本身在作什么,请不要在类成员上使用此注释,由于在模型更改的状况下,greenDAO 将没法对目标代码进行相应的更改。
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE}) public @interface Keep { }
做用:注解代码段以禁止 greenDAO 修改注解的代码段,注解实体类以禁止 greenDAO 修改此类。
注意:再也不支持旧版本的greenDAO中使用的KEEP。
KEEP sections used in older versions of greenDAO are no longer supported。
由 @Generated
注解的代码通常是 greenDAO 须要的可是以前不存在的代码,因此在 build 时 greenDAO 自动生成了这些代码。
好比,示例代码中会自动生成如下一个 @Generated 注解的构造方法:
@Generated(hash = 1272611929) public Note() { }
此时,若是更改此方法为:
@Generated(hash = 1272611929) public Note() { System.out.println("须要自定义此构造方法"); }
则在 build 时会报错,按照提示,能够改成:
@Generated() public Note() { System.out.println("须要自定义此构造方法"); }
但实际上,这样的修改并无任何意义,由于下次 build 时会删掉你添加的全部代码,自动会恢复原样(和整个删掉这些代码的效果相同)。
PS:恢复的注解中的 hash 值和以前也是彻底同样的,由于其实 hash 是根据生成的代码计算后得来的,改动此块代码就会致使 hash 值不符,相同代码的 hash 值也同样。
或者使用 @Keep
代替 @Generated
,这将告诉 greenDao 不会修改带有改注解的代码:
@Keep public Note() { System.out.println("须要自定义此构造方法"); }
这样,下次 build 时此代码就会保持你自定义的样子。可是这可能会破坏 entities 类和 greenDAO 的其余部分的链接(由于你修改了 greenDAO 须要的逻辑),而且文档中也明确说明了,通常状况下都不建议使用 @Keep
。
其实,我感受添加 @Keep
的效果和去掉全部注解的效果多是同样的,好比改为这样:
public Note() { System.out.println("须要自定义此构造方法"); }
可是我感受可能在这种状况下,greenDAO 仍然能够在须要的时候自动修改此方法(也即不会像添加 @Keep 那样可能会破坏 greenDAO 的逻辑),由于按照文档的意思,只有添加 @Keep 才会阻止 greenDAO 自动修改代码。
但若是是这样的话,为什么错误提示没有说明呢?
public @interface Convert { Class<? extends PropertyConverter> converter(); Class columnType(); //能够在DB中保留的列的类。这仅限于greenDAO原生支持的全部java类。 }
@Convert(converter = NoteTypeConverter.class, columnType = String.class) //类型转换类,自定义的类型在数据库中存储的类型 private NoteType type; //在保存到数据库时会将自定义的类型 NoteType 经过 NoteTypeConverter 转换为数据库支持的 String 类型。反之亦然
greenDAO是Android的对象/关系映射(ORM)工具。 它为关系数据库SQLite提供了一个面向对象的接口。像greenDAO一类的ORM工具为你作不少重复性的任务,提供简单的数据接口。
一旦项目构建完毕,你就能够在Android项目中开始使用greenDAO了。
如下核心类是greenDAO的基本接口:
DaoMaster
:是 GreenDao 的入口,DaoMaster保存数据库对象(SQLiteDatabase)并管理特定模式[specific schema]的DAO类。它有静态方法来建立表或删除表。它的内部类OpenHelper和DevOpenHelper都是SQLiteOpenHelper的实现,用来在SQLite数据库中建立 schema。DaoSession
:管理特定schema的全部可用DAO对象,你可使用其中一个的getter方法获取DAO对象。DaoSession还为实体提供了一些通用的持久性[persistence]方法,如插入,加载,更新,刷新和删除。 最后,DaoSession对象也跟踪[keeps track of] identity scope。DAOs
:数据访问对象[Data access objects],用于实体的持久化和查询。 对于每一个实体,greenDAO会生成一个DAO。 它比DaoSession拥有更多的持久化方法,例如:count,loadAll和insertInTx。Entities
:可持久化对象[Persistable objects]。 一般,实体是使用标准Java属性(如POJO或JavaBean)表示数据库行的对象。最后,如下代码示例说明了初始化数据库和核心greenDAO类的第一步:
//下面代码仅仅须要执行一次,通常会放在application DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db"); Database db = helper.getWritableDb(); DaoMaster daoMaster = new DaoMaster(db); DaoSession daoSession = daoMaster.newSession();
NoteDao noteDao = daoSession.getNoteDao();//通常在activity或者fragment中获取Dao对象
该示例假设存在一个Note实体。 经过使用它的DAO,咱们能够调用这个特定实体的持久化操做。
完成以上全部工做之后,咱们的数据库就已经自动生成了,接下来就能够对数据库进行操做了。
PS:API 中的
PK
是PrimaryKey
的意思,也就是主键
的意思。
// 数据删除相关 void delete(T entity) // 从数据库中删除给定的实体 void deleteAll() // 删除数据库中所有数据 void deleteByKey(K key) // 从数据库中删除给定Key所对应的实体 void deleteByKeyInTx(java.lang.Iterable<K> keys) // 使用事务操做删除数据库中给定的全部key所对应的实体 void deleteByKeyInTx(K... keys) // 使用事务操做删除数据库中给定的全部key所对应的实体 void deleteInTx(java.lang.Iterable<T> entities) // 使用事务操做删除数据库中给定实体集合中的实体 void deleteInTx(T... entities) // 使用事务操做删除数据库中给定的实体
// 数据插入相关 long insert(T entity) // 将给定的实体插入数据库 void insertInTx(java.lang.Iterable<T> entities) // 使用事务操做,将给定的实体集合插入数据库 void insertInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey) // 使用事务操做将给定的实体集合插入数据库,并设置是否设定主键 void insertInTx(T... entities) // 将给定的实体插入数据库 long insertOrReplace(T entity) // 将给定的实体插入数据库,若此实体类存在,则覆盖 void insertOrReplaceInTx(java.lang.Iterable<T> entities) // 使用事务操做,将给定的实体插入数据库,若此实体类存在,则覆盖 void insertOrReplaceInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey) // 插入数据库,若此存在则覆盖,并设置是否设定主键 void insertOrReplaceInTx(T... entities) // 使用事务操做,将给定的实体插入数据库,若此实体类存在,则覆盖 long insertWithoutSettingPk(T entity) // 将给定的实体插入数据库,但不设定主键
// 新增数据插入相关API void save(T entity) // 将给定的实体插入数据库,若此实体类存在,则更新 void saveInTx(java.lang.Iterable<T> entities) // 使用事务操做,将给定的实体插入数据库,若此实体类存在,则更新 void saveInTx(T... entities) // 使用事务操做,将给定的实体插入数据库,若此实体类存在,则更新
//更新数据 void update(T entity) // 更新给定的实体 void updateInTx(java.lang.Iterable<T> entities) // 使用事务操做,更新给定的实体 void updateInTx(T... entities) // 使用事务操做,更新给定的实体
// 加载相关 T load(K key) // 加载给定主键的实体 List<T> loadAll() // 加载数据库中全部的实体 T loadByRowId(long rowId) // 加载某一行并返回该行的实体
官方文档
查询返回符合特定条件[certain criteria]的实体。在 greenDAO 中,你可使用原始[raw] SQL 来制定查询[formulate queries],或者使用 QueryBuilder
API,相对而言后者会更容易。
此外,查询支持懒加载
结果,当在大结果集上操做时,能够节省内存和性能。
编写SQL可能很困难,而且容易出现错误,这些错误仅在运行时被察觉。QueryBuilder类容许你为再不写SQL语句的状况下为实体构建自定义查询,并帮助在编译时检测错误。
简单条件[condition]示例:查询全部名为“Joe”的用户,按姓氏排序:
List<User> joes = userDao.queryBuilder() .where(Properties.FirstName.eq("Joe")) .orderAsc(Properties.LastName) .list();
嵌套条件[Nested conditions]示例:获取1970年10月或以后出生的名为“Joe”的用户:
QueryBuilder<User> 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<User> youngJoes = qb.list();
greenDao 的 Properties
支持的操做:
您能够排序查询结果。基于姓氏和出生年份的人的例子:
queryBuilder.orderAsc(Properties.LastName); // order by last name queryBuilder.orderDesc(Properties.LastName); // in reverse queryBuilder.orderAsc(Properties.LastName).orderDesc(Properties.YearOfBirth); // order by last name and year of birth
greenDAO使用的默认排序规则是COLLATE NOCASE
,但可使用 stringOrderCollation()
进行自定义。有关影响结果顺序的其余方法,请参阅QueryBuilder类的文档。
有时候,你只须要一个查询结果的子集,例如在用户界面中显示前10个元素。当拥有大量实体时,这是很是有用的(而且也节省资源),而且你也不能使用 where 语句来限制结果。QueryBuilder 有定义限制和偏移的方法:
一般,greenDAO以透明方式映射查询中使用的类型。 例如,boolean
被映射到具备0或1值的INTEGER
,而Date
被映射到(long)INTEGER
值。
自定义类型是一个例外:在构建查询时,您始终必须使用数据库值类型。 例如,若是使用转换器将枚举类型映射到 int 值,则应在查询中使用 int 值。
public enum NoteType { TEXT, PICTURE, UNKNOWN } public static final String TYPE_TEXT = "文本"; public String convertToDatabaseValue(NoteType entityProperty) { if (entityProperty == TEXT) return TYPE_TEXT; ... }
NoteDao.Properties.Type.eq(NoteTypeConverter.TYPE_TEXT); //这里必须使用数据库中的值类型 String 而不能使用枚举类型 NoteType.TEXT
Query 类表示能够屡次执行的查询[executed multiple times]。当你使用 QueryBuilder 中的一个方法来获取结果时,执行过程当中 QueryBuilder 内部会使用 Query 类
。若是要屡次运行同一个查询,应该在 QueryBuilder 上调用 build()
来建立 query 而并不是执行它。
greenDAO 支持惟一结果(0或1个结果)和结果列表。若是你想在 Query(或QueryBuilder)上有惟一的结果,请调用 unique()
,这将为你提供单个结果或者在没有找到匹配的实体时返回 null。若是你的用例禁止 null 做为结果,调用 uniqueOrThrow()
将保证返回一个非空的实体(不然会抛出一个 DaoException)。
若是但愿多个实体做为查询结果,请使用如下方法之一:
方法 listLazy()、listLazyUncached() 和 listIterator() 使用 greenDAO 的 LazyList 类。为了按需加载数据,它保存对数据库游标的引用[holds a reference to a database cursor],这就是为何你必须确保关闭懒性列表和迭代器(一般在try/finally块)。
来自 listLazy() 的缓存 lazy 列表和 listIterator() 中的 lazy 迭代器在访问或遍历全部元素后自动关闭游标。若是列表处理过早中止,开发者须要本身调用close()进行处理[it’s your job to call close() if the list processing stops prematurely]。
若是在多个线程中使用查询,则必须调用 forCurrentThread() 获取当前线程的Query实例。 Query实例绑定到构建查询的那个线程[Object instances of Query are bound to their owning thread that built the query]。
你能够安全地设置Query对象的参数,由于其余线程不会干涉到你[while other threads cannot interfere]。 若是其余线程尝试修改查询参数,或执行 query boun 到另外一个线程,系统将抛出异常。 用这种方式,你就再也不须要 synchronized 语句了。 实际上,你应该避免锁定,由于若是并发事务[concurrent transactions]使用相同的 Query 对象,这可能致使死锁。
每次在使用构建器构建查询时,参数设置为初始参数,forCurrentThread() 将被调用[Every time, forCurrentThread() is called, the parameters are set to the initial parameters at the time the query was built using its builder.]。
若是QueryBuilder不能知足你的需求,有两种方法来执行原始 SQL 语句并一样能够返回实体对象。
第一种,首选的方法是使用 QueryBuilder 和 WhereCondition.StringCondition。这样,你能够将任何 SQL 语句做为 WHERE 子句传递给查询构建器。
WhereCondition cond = new WhereCondition.StringCondition(NoteDao.Properties.Id.columnName + ">=? ", 2L); List<Note> list = dao.queryBuilder().where(cond).build().list();
如下代码是一个理论[theoretical]示例,说明如何运行子选择(使用 join 链接将是更好的解决方案):
Query<User> query = userDao.queryBuilder() .where(new StringCondition("_ID IN " +"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)")) .build();
第二种方法不使用QueryBuilder, 而是使用 queryRaw 或 queryRawCreate 方法。它们容许您传递一个原始 SQL 字符串,它附加在 SELECT 和实体列[entities columns]以后。 这样,你能够有任何 WHERE 和 ORDER BY 子句来选择实体。
可使用生成的常量引用表和列名称。这是建议的方式,由于能够避免打错字,由于编译器将检查名称。在实体的DAO中,你将发现 TABLENAME 包含数据库表的名称,以及一个内部类 Properties,其中包含全部的属性的常量(字段columnName)。
String where = "where " + NoteDao.Properties.Type.columnName + " like ? AND " + NoteDao.Properties.Id.columnName + ">=?"; ist<Note> list = dao.queryRaw(where, "%" + NoteTypeConverter.TYPE_TEXT + "%", Long.toString(2L));
可使用别名 T 来引用实体表[entity table]。
如下示例显示如何建立一个查询,该查询使用链接检索名为“admin”的组的用户(一样,greenDAO自己支持 joins 链接,这只是为了演示):
Query<User> query = userDao.queryRawCreate(", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin");
批量删除不会删除单个实体[Bulk deletes do not delete individual entities],可是全部实体都符合一些条件。 要执行批量删除,请建立一个 QueryBuilder,调用其 buildDelete() 方法,并执行返回的 DeleteQuery。
这一部分的API可能在未来会改变,例如可能会添加使用起来更方便的方法。
请注意,批量删除目前不影响 identity scope 中的实体,例如,若是已删除的实体先前已缓存并经过其ID(load 方法)进行访问,则能够“复活”[resurrect]。 若是这可能会致使你的用例的问题[cause issues for your use case],请考虑当即清除身份范围[identity scope]。
QueryBuilder 有两个静态标志能够启用 SQL 和参数 logging,启用后能够帮助你分析为什么您的查询没有返回预期的结果:
QueryBuilder.LOG_SQL = true; QueryBuilder.LOG_VALUES = true;
当调用其中一个构建方法时,它们将 log 生成的 SQL 命令和传递的值,并将它们与实际所需的值进行比较。此外,它可能有助于将生成的 SQL 复制到一些 SQLite 数据库浏览器中,并看看它如何执行。
自定义类型容许实体具备任何类型的属性。默认状况下,greenDAO支持如下类型:
若是 greenDao 的默认参数类型知足不了你的需求,那么你可使用数据库支持的原生数据类型(上面列出的那些)经过 PropertyConverter
类转换成你想要的属性。
@Convert(converter = NoteTypeConverter.class, columnType = String.class) //类型转换类,自定义的类型在数据库中存储的类型 private NoteType type; //在保存到数据库时会将自定义的类型 NoteType 经过 NoteTypeConverter 转换为数据库支持的 String 类型。反之亦然
public enum NoteType { TEXT, PICTURE, UNKNOWN }
PropertyConverter
接口的实现类,用于自定义类型和在数据库中存储的类型之间的相互转换:public class NoteTypeConverter implements PropertyConverter<NoteType, String> { public static final String TYPE_TEXT = "文本"; public static final String TYPE_PICTURE = "图片"; public static final String TYPE_UNKNOWN = "未知格式"; @Override public NoteType convertToEntityProperty(String databaseValue) { //将 数据库中存储的String类型 转换为 自定义的NoteType类型 switch (databaseValue) { case TYPE_TEXT: return NoteType.TEXT; case TYPE_PICTURE: return NoteType.PICTURE; default: //不要忘记正确处理空值 return NoteType.UNKNOWN; } } @Override public String convertToDatabaseValue(NoteType entityProperty) { //将 自定义的NoteType类型 转换为在数据库中存储的String类型 switch (entityProperty) { case TEXT: return TYPE_TEXT; case PICTURE: return TYPE_PICTURE; default: return TYPE_UNKNOWN; } } }
注意:为了得到最佳性能,greenDAO将为全部转换使用单个转换器实例。确保转换器除了无参数默认构造函数以外没有任何其余构造函数。另外,确保它是线程安全的,由于它可能在多个实体上并发调用。
如何正确转换枚举
greenDAO 支持加密的数据库来保护敏感数据。
虽然较新版本的Android支持文件系统加密[file system encryption],但Android自己并不为数据库文件提供加密。所以,若是攻击者得到对数据库文件的访问(例如经过利用安全缺陷或欺骗有 root 权限的设备的用户),攻击者能够访问该数据库内的全部数据。使用受密码保护的加密数据库增长了额外的安全层。它防止攻击者简单地打开数据库文件。
由于Android不支持加密数据库,因此你须要在APK中捆绑自定义构建的SQLite[bundle a custom build of SQLite]。 这些定制构建包括CPU相关[dependent]和本地代码。 因此你的APK大小将增长几MByte。 所以,你应该只在你真的须要它时使用加密数据库。
greenDAO直接支持带有绑定的 SQLCipher。 SQLCipher是使用256位AES加密
的自定义构建的SQLite。
请参阅 SQLCipher for Android,了解如何向项目添加SQLCipher。
在建立数据库实例时,只需调用 .getEncryptedWritableDb()
而不是 .getWritableDb()
便可像使用SQLite同样使用SQLCipher:
DevOpenHelper helper = new DevOpenHelper(this, "notes-db-encrypted.db"); Database db = helper.getEncryptedWritableDb("<your-secret-password>"); daoSession = new DaoMaster(db).newSession();
greenDAO 对全部数据库交互使用一个小型的抽象层[a thin abstraction layer],所以支持标准和非标准的SQLite实现:
android.database.sqlite.SQLiteDatabase
net.sqlcipher.database.SQLiteDatabase
org.greenrobot.greendao.database.Database
这使您可以轻松地从标准数据库切换到加密数据库,由于在针对DaoSession和单个DAO时,代码将是相同的。
Demo地址:https://github.com/baiqiantao/AndroidOrmTest.git
在根 build.gradle 文件中:
buildscript { repositories { mavenCentral() //greendao } dependencies { classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' //greendao } }
在模块的 build.gradle 文件中:
greendao { schemaVersion 1 //数据库模式的当前版本 }
可配置的属性:
schemaVersion
:数据库模式[database schema]的当前版本。 这用于 *OpenHelpers 类在模式版本之间迁移。若是更改 实体/数据库 模式,则必须增长此值
。 默认值为1。daoPackage
:生成的DAO,DaoMaster和DaoSession的包名称。默认为源实体的包名称。targetGenDir
:生成的源码应存储的位置。默认目录为 build/generated/source/greendao
。generateTests
:设置为 true 以自动生成单元测试。targetGenDirTests
:生成的单元测试应存储在的基本目录。 默认为 src/androidTest/java
。在模块的 build.gradle 文件中:
apply plugin: 'org.greenrobot.greendao' //greendao implementation 'org.greenrobot:greendao:3.2.2' //greendao
在构建项目时,它会生成 DaoMaster,DaoSession 和 **DAO
等类。若是在更改实体类以后遇到错误,请尝试从新生成项目,以确保清除旧生成的类。
@Entity( nameInDb = "BQT_USERS",// 指定该表在数据库中的名称,默认是基于实体类名 indexes = {@Index(value = "text, date DESC", unique = true)},// 定义跨多个列的索引 createInDb = true,// 高级标志,是否建立该表。默认为true。若是有多个实体映射一个表,或者该表已在外部建立,则可置为false //schema = "bqt_schema",// 告知GreenDao当前实体属于哪一个schema。属于不一样模式的实体应不具备关系。 active = true,// 标记一个实体处于活动状态,活动实体(设置为true)有更新、删除和刷新方法。默认为false generateConstructors = true,//是否生成全部属性的构造器。注意:无参构造器老是会生成。默认为true generateGettersSetters = true//是否应该生成属性的getter和setter。默认为true ) public class Note { @Id(autoincrement = false) //必须 Long 或 long 类型的,SQLite 上的自增加会引入额外的资源使用,一般能够避免使用 private Long id; @NotNull//在保存到数据库中以前须要保证此值不为null,不然会报 IllegalArgumentException ,并直接崩溃 private String text; @Transient //代表此字段不存储到数据库中,用于不须要持久化的字段,好比临时状态 private String comment; @Property(nameInDb = "TIME") //为该属性在数据库中映射的字段名设置一个非默认的名称 private Date date; @Convert(converter = NoteTypeConverter.class, columnType = String.class) //类型转换类,自定义的类型在数据库中存储的类型 private NoteType type; //在保存到数据库时会将自定义的类型 NoteType 经过 NoteTypeConverter 转换为 String 类型。反之亦然 }
编写完实体类之后按下 Ctrl+F9(Make project),程序会自动编译生成 dao 文件,生成的文件一共有三个:DaoMaster,DaoSession 和 **DAO。
public class GreenDaoActivity extends ListActivity { private Note mNote = Note.newBuilder().text("【text】").date(new Date()).comment("包青天").type(NoteType.PICTURE).build(); private NoteDao dao; private EditText editText; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String[] array = {"0、插入一条数据", "一、演示 insert 数据时一些特殊状况", "二、插入数据:insertInTx、insertOrReplace、save、saveInTx、insertWithoutSettingPk", "三、删除数据:delete、deleteAll、deleteByKey、deleteByKeyInTx、deleteInTx", "四、更新数据:update、updateInTx、updateKeyAfterInsert", "五、查询数据:where、whereOr、limit、offset、*order*、distinct、or、and、queryRaw*", "六、数据加载和缓存:load、loadByRowId、loadAll、detach、detachAll、unique、uniqueOrThrow", "七、其余API:getKey、getPkProperty、getProperties、getPkColumns、getNonPkColumns", "八、删除全部数据:deleteAll", "tag 值 +1",}; setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array))); editText = new EditText(this); editText.setInputType(EditorInfo.TYPE_CLASS_NUMBER); getListView().addFooterView(editText); initDB(); } private void initDB() { DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db"); Database db = helper.getWritableDb(); DaoMaster daoMaster = new DaoMaster(db); DaoSession daoSession = daoMaster.newSession(); dao = daoSession.getNoteDao(); } int tag = -1; @Override protected void onListItemClick(ListView l, View v, int position, long id) { if (TextUtils.isEmpty(editText.getText())) { tag++; Toast.makeText(this, "最新值为" + tag, Toast.LENGTH_SHORT).show(); } else { tag = Integer.parseInt(editText.getText().toString()); editText.setText(""); } switch (position) { case 0://插入一条数据 simpleInsert(); break; case 1://演示 insert 数据时一些特殊状况 insert(); break; case 2://插入数据 inserts(); break; case 3://删除数据 deletes(); break; case 4://更新数据 updates(); break; case 5://查询数据 query(); break; case 6: loadAndDetach(); break; case 7: testOtherApi(); break; case 8: dao.deleteAll(); break; } } private void simpleInsert() { Note insertNote = Note.newBuilder().type(tag % 3 == 2 ? NoteType.UNKNOWN : (tag % 3 == 1 ? NoteType.PICTURE : NoteType.TEXT)) .comment("comment" + tag).date(new Date()).text("text" + tag).build(); long insertId = dao.insert(insertNote);// 将给定的实体插入数据库,默认状况下,插入的数据的Id将从1开始递增 Log.i("bqt", "插入数据的ID= " + insertId + getAllDataString()); } private void insert() { long id = -10086;//如下注释无论是否给 id 设置【@Id(autoincrement = true)】注解时的得出的结论都是同样的 if (tag % 9 == 6) id = dao.insert(new Note());//一样会崩溃,插入数据的 text 不能为空(IllegalArgumentException) else if (tag % 9 == 5) id = dao.insert(Note.newBuilder().id(6L).text("text5").build());//会崩溃,由于此 id 已经存在 else if (tag % 9 == 4) id = dao.insert(Note.newBuilder().text("text4").build());//id =12 else if (tag % 9 == 3) id = dao.insert(Note.newBuilder().id(6L).text("text3").build());//自定义id,id =6 else if (tag % 9 == 2) id = dao.insert(Note.newBuilder().text("text2").type(NoteType.UNKNOWN).build());//id = 11 else if (tag % 9 == 1) id = dao.insert(Note.newBuilder().id(10L).text("text1").build());//插入的数据的Id将从最大值开始 else if (tag % 9 == 0) id = dao.insert(Note.newBuilder().id(-2L).text("text0").build());//id =-2,并不限制id的值 Log.i("bqt", "插入数据的ID= " + id + getAllDataString()); } private void inserts() { Note note1 = Note.newBuilder().text("text1-" + tag).date(new Date()).comment("包青天").type(NoteType.TEXT).build(); Note note2 = Note.newBuilder().text("text2-" + tag).date(new Date()).comment("包青天").type(NoteType.TEXT).build(); if (tag % 9 >= 6) { dao.save(note1);// 将给定的实体插入数据库,若此实体类存在,则【更新】 Note quryNote = dao.queryBuilder().build().list().get(0); quryNote.setText("【新的Text2】"); mNote.setText("【新的Text2】"); dao.saveInTx(quryNote, mNote);//一样参数能够时集合,基本上全部的 **InTx 方法都是这样的 } else if (tag % 9 == 5) { Note quryNote = dao.queryBuilder().build().list().get(0); quryNote.setText("【新的Text】"); mNote.setText("【新的Text】"); dao.insertOrReplaceInTx(quryNote, mNote);//一样参数能够时集合,并可选择是否设定主键 } else if (tag % 9 == 4) { long rowId = dao.insertOrReplace(mNote);// 将给定的实体插入数据库,若此实体类存在则【覆盖】,返回新插入实体的行ID Log.i("bqt", "对象的ID=" + mNote.getId() + " 返回的ID=" + rowId); } else if (tag % 9 == 3) dao.insertWithoutSettingPk(note1);//插入数据库但不设定主键(不明白含义),返回新插入实体的行ID else if (tag % 9 == 2) dao.insertInTx(Arrays.asList(note1, note2), false);//并设置是否设定主键(不明白含义) else if (tag % 9 == 1) dao.insertInTx(Arrays.asList(note1, note2));// 使用事务操做,将给定的实体集合插入数据库 else if (tag % 9 == 0) dao.insertInTx(note1, note2);// 使用事务操做,将给定的实体集合插入数据库 Log.i("bqt", getAllDataString()); } private void deletes() { if (tag % 9 >= 7) dao.deleteAll(); //删除数据库中所有数据。再插入的数据的 Id 一样将从最大值开始 else if (tag % 9 == 6) dao.deleteByKeyInTx(Arrays.asList(1L, 3L)); else if (tag % 9 == 5) dao.deleteByKeyInTx(1L, 2L);// 使用事务操做删除数据库中给定的全部key所对应的实体 else if (tag % 9 == 4) dao.deleteByKey(1L);//从数据库中删除给定Key所对应的实体 else if (tag % 9 == 3) dao.delete(new Note());//从数据库中删除给定的实体 else if (tag % 9 == 2) dao.deleteInTx(new Note(), new Note());// 使用事务操做删除数据库中给定实体集合中的实体 else if (tag % 9 == 1) dao.deleteInTx(Arrays.asList(new Note(), new Note())); else if (tag % 9 == 0) dao.deleteInTx(dao.queryBuilder().limit(1).list()); Log.i("bqt", getAllDataString()); } private void query() { WhereCondition cond1 = NoteDao.Properties.Id.eq(1);//== WhereCondition cond2 = NoteDao.Properties.Type.notEq(NoteTypeConverter.TYPE_UNKNOWN);//!= //在构建查询时,必须使用数据库值类型。 由于咱们使用转换器将枚举类型映射到 String 值,则应在查询中使用 String 值 WhereCondition cond3 = NoteDao.Properties.Id.gt(10);//大于 WhereCondition cond4 = NoteDao.Properties.Id.le(5);// less and eq 小于等于 WhereCondition cond5 = NoteDao.Properties.Id.in(1, 4, 10);//能够是集合。在某些值内,notIn 不在某些值内 WhereCondition cond6 = NoteDao.Properties.Text.like("%【%");//包含 // 最经常使用Like通配符:下划线_代替一个任意字符(至关于正则表达式中的 ?) 百分号%代替任意数目的任意字符(至关于正则表达式中的 *) WhereCondition cond7 = NoteDao.Properties.Date.between(System.currentTimeMillis() - 1000 * 60 * 20, new Date());//20分钟 WhereCondition cond8 = NoteDao.Properties.Date.isNotNull();//isNull WhereCondition cond9 = new WhereCondition.StringCondition(NoteDao.Properties.Id.columnName + ">=? ", 2L); //其余排序API:orderRaw(使用原生的SQL语句)、orderCustom、stringOrderCollation、preferLocalizedStringOrder Property[] orders = new Property[]{NoteDao.Properties.Type, NoteDao.Properties.Date, NoteDao.Properties.Text};//一样支持可变参数 List<Note> list = null; if (tag % 9 == 8) { String where = "where " + NoteDao.Properties.Type.columnName + " like ? AND " + NoteDao.Properties.Id.columnName + ">=?"; list = dao.queryRaw(where, "%" + NoteTypeConverter.TYPE_TEXT + "%", Long.toString(2L)); Log.i("bqt", "queryRaw查询到的数据" + new Gson().toJson(list)); list = dao.queryRawCreate(where, "%" + NoteTypeConverter.TYPE_TEXT + "%", Long.toString(2L)).list(); } else if (tag % 9 == 7) list = dao.queryBuilder().where(cond9).build().list(); //执行原生的SQL查询语句 else if (tag % 9 == 6) { QueryBuilder<Note> qb = dao.queryBuilder(); WhereCondition condOr = qb.or(cond2, cond3, cond4); WhereCondition condAnd = qb.and(cond6, cond7, cond8); list = qb.where(cond1, condOr, condAnd).build().list(); } else if (tag % 9 == 5) list = dao.queryBuilder().whereOr(cond5, cond6, cond7).build().list();//多个语句间是 OR 的关系 else if (tag % 9 == 4) list = dao.queryBuilder().where(cond5, cond6, cond7).build().list();//多个语句间是 AND 的关系 else if (tag % 9 == 3) list = dao.queryBuilder().where(cond4).offset(1).limit(2).build().list();//(必须)与limit一块儿设置查询结果的偏移量 else if (tag % 9 == 2) list = dao.queryBuilder().where(cond5).limit(2).distinct().build().list();//限制查询结果个数,避免重复的实体(不明白) else if (tag % 9 == 1) list = dao.queryBuilder().where(cond2).orderAsc(orders).build().list(); //经常使用升序orderAsc、降序orderDesc else if (tag % 9 == 0) list = dao.queryBuilder().build().list(); Log.i("bqt", getAllDataString() + "\n查询到的数据" + new Gson().toJson(list)); } private void updates() { dao.save(mNote);//确保要更新的实体已存在 if (tag % 9 >= 4) dao.update(Note.newBuilder().text("text-update0").build()); //若是要更新的实体不存在,则会报DaoException异常 else if (tag % 9 == 3) { mNote.setText("【Text-update3】"); long rowId = dao.updateKeyAfterInsert(mNote, 666L); Log.i("bqt", "rowId=" + rowId);//更新实体内容,并更新key } else if (tag % 9 == 2) { mNote.setText("【Text-update2】"); dao.updateInTx(Arrays.asList(dao.queryBuilder().build().list().get(0), mNote));// 使用事务操做,更新给定的实体 } else if (tag % 9 == 1) { Note updateNote = dao.queryBuilder().build().list().get(0); updateNote.setText("【Text-update1】"); dao.updateInTx(updateNote, mNote);// 使用事务操做,更新给定的实体 } else if (tag % 9 == 0) dao.update(mNote);//更新给定的实体,无论有没有变化,只需用新的实体内容替换旧的内容便可(必须是同一实体) Log.i("bqt", getAllDataString()); } private void loadAndDetach() { Note note1 = dao.load(1L);// 加载给定主键的实体 Note note2 = dao.load(10086L);//若是给定主键不存在则返回null Note note3 = dao.loadByRowId(1L);// 加载某一行并返回该行的实体 Note note4 = dao.queryBuilder().limit(1).unique();//返回一个元素(可能为null)。若是查询结果数量不是0或1,则抛出DaoException Note note5 = dao.queryBuilder().limit(1).build().uniqueOrThrow(); //保证返回一个非空的实体(不然会抛出一个 DaoException) if (tag % 9 >= 2) { note1.setText("-------Text" + System.currentTimeMillis()); Log.i("bqt", dao.load(1L).getText());//由于这里获取到的实体对象仍然是 note1 ,因此这里的值当即就改变了! dao.detachAll();//清除指定实体缓存,将会从新从数据库中查询,而后封装成新的对象 Log.i("bqt", dao.load(1L).getText());//由于只更改了实体但没有调用更新数据库的方法,因此数据库中的数据并无改变 } else if (tag % 9 == 1) { dao.detach(note4);//清除指定实体缓存;dao.detachAll 清除当前表的全部实体缓存;daoSession.clear() 清除全部的实体缓存 Note note7 = dao.load(1L);// 清除指定Dao类的缓存后,再次获得的实体就是构造的新的对象 Log.i("bqt", (note1 == note3 && note1 == note4 && note1 == note5) + " " + (note1 == note7));//true false } else if (tag % 9 == 0) { Log.i("bqt", new Gson().toJson(Arrays.asList(note1, note2, note3, note4, note5))); } } private void testOtherApi() { dao.save(mNote);//确保要更新的实体已存在 //查找指定实体的Key,当指定实体在数据库表中不存在时返回 null(而不是返回-1或其余值) Log.i("bqt", "实体的 Key = " + dao.getKey(mNote) + " " + dao.getKey(Note.newBuilder().text("text").build())); Property pk = dao.getPkProperty();//id _id 0 true class java.lang.Long for (Property p : dao.getProperties()) { Log.i("bqt", p.name + "\t" + p.columnName + "\t" + p.ordinal + "\t" + p.primaryKey + "\t" + p.type + "\t" + (p == pk)); } Log.i("bqt", "全部的PK列:" + Arrays.toString(dao.getPkColumns()));//[_id] Log.i("bqt", "全部的非PK列:" + Arrays.toString(dao.getNonPkColumns()));//[TEXT, TIME, TYPE] } private String getAllDataString() { List<Note> list1 = dao.queryBuilder().build().list();//缓存查询结果 List<Note> list2 = dao.queryBuilder().list(); List<Note> list3 = dao.loadAll(); return " 个数=" + dao.count() + ",等价=" + (list1.equals(list2) && list1.equals(list3)) + ",数据=" + new Gson().toJson(list1);//true } }
2018-5-28