译文html
版本:greenDAO 3.2.2
写在前面:这个库极大的优化了咱们使用SQLite数据库,但对SQLite数据库及其语法的掌握依旧是咱们须要作的,不建议新手在没使用过Android原生的数据库API的状况下就使用这个。
简介:greenDAO 是一款开源的面向 Android 的轻便、快捷的 ORM 框架,将 Java 对象映射到 SQLite 数据库中,咱们操做数据库的时候,不在须要编写复杂的 SQL语句, 在性能方面,greenDAO 针对 Android 进行了高度优化, 最小的内存开销 、依赖体积小 同时仍是支持数据库加密。
何为ORM?
它的特征:
- 最高性能(多是最快的Android ORM),咱们也是开源的
- 容易使用
- 最小的内存消耗
- 库很小(<100KB)可让你构建花费的时间变低而且能够避免65k方法的限制
- 数据库加密:greenDAO支持SQLCipher来保证您的用户数据安全
- 强大的社区:超过5000的GitHub stars证实了这是一个强大并活跃的社区
何为SQLCipher?Android SQLite是不支持数据加密的,这样对于用户的数据来讲是不安全的(不少手机都是Root过的,其能够直接进入到/data/data/<package_name>/databases目录下面),因此,咱们须要对其进行加密,一种是对内容进行加密(但数据库的结构仍是能尽收眼底,同时这样加密后搜索会是一个问题),一种是直接对SQLite数据库进行加密,直接对数据库文件进行加密就会用到SQLCipher,它是加密工具中的一种,它是免费的,其它的多为收费。SQLCipher,彻底开源,托管在GitHub( https://github.com/sqlcipher/sqlcipher)上。
谁在用greenDAO?不少顶级的Android应用依赖于greenDAO,这些APP中有一些已经有超过1000万的安装量,咱们认为,这在业界证实了它的可靠性。你能够在AppBrain中查看当前的统计数据。
先配置Gradlejava
// In your root build.gradle file: buildscript { repositories { jcenter() mavenCentral() // add repository } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // add plugin } } // In your app projects build.gradle file: apply plugin: 'com.android.application' apply plugin: 'org.greenrobot.greendao' // apply plugin dependencies { compile 'org.greenrobot:greendao:3.2.2' // add library }
让咱们跳进代码:在 src 文件夹你会找到笔记的实体类, Note.java。它被保存在数据库中并包含做为笔记一部分的全部数据,如id,笔记的文本和建立日期。android
@Entity(indexes = { @Index(value = "text, date DESC", unique = true) }) public class Note { @Id private Long id; @NotNull private String text; private Date date; ... }
总而言之,一个实体做为一个类保存在数据库中(例如 每一个对象对应一行)。一个实体包含映射到数据库列的属性。git
如今作这个项目,例如在Android Studio中 Build > Make project 。greenDAO触发器会生成DAO类,如 like NoteDao.java, 这将帮助咱们添加笔记到数据库。github
要学习如何添加一些笔记,请查看 NoteActivity 类. 首先,咱们必须为咱们的 Note 类准备一个DAO对象,咱们在onCreate()中:sql
// get the note DAO DaoSession daoSession = ((App) getApplication()).getDaoSession(); noteDao = daoSession.getNoteDao();
当用户点击添加按钮的时候,方法 addNote() 被调用。这里,咱们建立一个新的 Note 对象并经过DAO的 insert() 方法将其插入到数据库中:数据库
Note note = new Note(); note.setText(noteText); note.setComment(comment); note.setDate(new Date()); note.setType(NoteType.TEXT); noteDao.insert(note); Log.d("DaoExample", "Inserted new note, ID: " + note.getId());
注意当咱们建立笔记的时候没有传入id。在这种状况下数据库决定笔记的id。DAO负责在返回插入结果以前自动的设置新的id(参阅日志表)。编程
删除日志也很简单,请参阅 NoteClickListener:缓存
noteDao.deleteByKey(id);
在example例子中没有展现,可是一样简单:更新笔记,只须要修改它的属性并调用DAO的update() 方法:安全
note.setText("This note has changed."); noteDao.update(note);
还有其它方法来插入,查询,更新和删除实体。查看全部DAO继承的the AbstractDao class (AbstractDao类)的方法以了解更多信息。
你已经看到了DAOs,可是你如何初始化greenDAO和底层的数据库?一般你须要初始化一个 DaoSession,在 the Application class 里,对于整个应用一般执行一次。
DevOpenHelper helper = new DevOpenHelper(this, "notes-db"); Database db = helper.getWritableDb(); daoSession = new DaoMaster(db).newSession();
数据库由助手类 DevOpenHelper 建立,将数据库传入生成 DaoMaster 类.在DaoMaster中实现了 OpenHelper 类,它为您设置全部的数据库。不须要写 “CREATE TABLE” 语句。
而后Activities and fragments 能够调用 getDaoSession() 来访问全部的实体DAO,入上面插入和删除笔记时看到的。
为了扩展咱们的笔记或建立一个新的实体,你只须要以一样的方式修改和建立它们的Java类和注解。而后重建您的项目。
详情请参阅Modelling entities
目录
为了在一个项目中使用greenDAO,你须要建立一个实体模型表示将保存数据到您的应用里。而后,基于这个模型greenDAO为DAO类生成Java代码。
模型自己是使用具备注解的Java类定义的。
你能够开始使用greenDAO Gradle插件而无需任何额外配置。尽管如此,你至少应该考虑设置个schema的版本:
// In the build.gradle file of your app project: android { ... } greendao { schemaVersion 2 }
此外,greendao配置元素支持许多配置选项:
greenDAO 3 用注解来定义模式和实体。这是一个简单的例子:
@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 注解将Java类 User 转换成数据库支持的实体。这也将指示greenDAO生成必要的代码(例如DAO)。
注意: 只支持Java类。若是您喜欢其它语言好比Kotlin,您的实体类必须任然是Java。
正如你在上面例子看到的那样,@Entity 注解将一个Java类标记为一个预先存在的实体。
虽然不添加任何附加参数一般很好,可是您仍然可使用它配置一些细节。
@Entity:
@Entity( // If you have more than one schema, you can tell greenDAO // to which schema an entity belongs (pick any string as a name). // 若是你有超过一个schema,你能够告诉greenDAO实体所属的schema(选择任何字符串做为名字) schema = "myschema", // Flag to make an entity "active": Active entities have update, // delete, and refresh methods. // 将实体标记为"active":Active 实体有更新,删除,和刷新方法。 active = true, // Specifies the name of the table in the database. // By default, the name is based on the entities class name. // 给数据库的表指定一个名字。默认,名字是基于实体的类名。 nameInDb = "AWESOME_USERS", // Define indexes spanning multiple columns here. // 定义跨越多个列的索引 indexes = { @Index(value = "name DESC", unique = true) }, // Flag if the DAO should create the database table (default is true). // Set this to false, if you have multiple entities mapping to one table, // or the table creation is done outside of greenDAO. // 标记DAO是否应建立数据库表(默认为true)。设置它为false,若是你有多个实体映射到一张表,或者建立表是在greenDAO以外完成的。 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. // 若是错失,是否应该生成属性的getter和setter generateGettersSetters = true ) public class User { ... }
注意,当使用Gradle插件时,目前不支持多个模式(https://github.com/greenrobot/greenDAO/issues/356)。暂时,继续使用你的生成器项目。
@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 注解选择一个 long/ Long 属性做为实体ID。在数据库的术语中,它是主键。参数autoincrement 是使ID值不断增长的标记(不重复使用旧值)。
@Property 当属性被映射到的时候,容许你定义一个非默认的列名。若是没有,greenDAO将以SQL-ish方式使用字段名称(大写字母,下划线来替换驼峰命名,例如 customName 将是 CUSTOM_NAME )。注意:你当前只能使用内置的常量(能够理解为关键字)来指定列名
@NotNull 使该属性在数据库端为“NOT NULL”列。一般使用 @NotNull 标记基本类型(long,int,short,byte)是有意义的,同时其包装类能够为空值(Long, Integer, Short, Byte)。
@Transient 标记属性被排除在持久化之中。用于临时状态,等等。或者,您也可使用Java的 transient 关键字。
目前,属性必须拥有一个 long or Long 属性做为他们的主键。 这是 Android 和 SQLite 推荐的作法。
为了解决这个问题,将您的关键属性定义为一个额外的属性,可是请为它建立一个惟一的索引。
@Id private Long id; @Index(unique = true) private String key;
在属性中使用@Index 为相应的数据库列建立一个数据库索引。使用如下参数定制:
@Entity public class User { @Id private Long id; @Index(unique = true) private String name; }
@Unique 像数据库列添加惟一的约束。注意,SQLite也隐式的为其建立了一个索引。
@Entity public class User { @Id private Long id; @Unique private String name; }
greenDAO 尝试使用合理的默认值,所以开发者没必要一一配置。
例如数据库端的表名和列明来自实体和属性的名字。与在Java中使用驼峰命名不一样,默认的数据库名字是大写的,使用下划线来分隔单词。
例如,一个名为 creationDate 将变成在数据库列 CREATION_DATE。
要学习如何增长一对一和一对多的关联,请参阅 Relations.
一旦你的实体schema就位,你就能够在您的IDE中使用“Make project” 来触发代码的生成过程。或者直接执行 greendao 的Gradle task.
若是您在更改了您的实体类后遇到了错误, 尝试从新构建您的项目以确保清理旧的生成类。
在greenDAO 3中实体类是由开发人员本身建立和编辑。然而,在代码的生成过程当中可能会增长实体中的源代码。
greenDAO将为它生成的方法和字段添加@Generated 注解,通知开发人员并防止代码的丢失。在大多数状况下你不比关心 @Generated.生成的代码。
做为预防措施,greenDAO不会覆盖现有代码,而且若是你手动更改生成的代码会引起错误:
Error:Execution failed for task ':app:greendao'. > Constructor (see ExampleEntity:21) 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.
正如错误消息所暗示的,一般有两种解决办法:
部分不在支持旧版本中greenDAO的KEEP部分
然而,若是Gradle插件检测到 KEEP FIELDS 部分它会自动使用 @Transient.注解字段。而后又,周围的 KEEP FIELDS注释可能会被删除。
数据库表可使用 1:1, 1:N, or N:M 的关系关联起来。若是你是一个数据库关系的新手,在咱们讨论 ORM 细节以前最好先了解一下。 这里是一些讨论关系的随机连接( some random links )
在greenDAO,实体与一个或多个关系有关。例如,若是你想在greenDAO中创建一个1:n的关系,那么您将拥有一个一对一或一对多的关系。可是,注意一对一和一对多关系之间没有联系,因此你将更新这两种关系。
@ToOne 注解定义了与另外一个实体(一个实体对象)的关系。将其应用于持有其它对象的属性。
在内部,greenDAO须要一个附加属性指向目标实体的ID,该属性由 joinProperty 参数指定。若是没有此参数,则会自动建立一个额外的列来保存键。
@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; }
对一个关系的getter方法(在这个示例中,getCustomer())在第一次调用时延迟地解析目标实体。后续调用将当即返回先前解析的对象。
注意,若是你改变了外键的属性(这里是customerId),接下来调用getter (getCustomer()) 将会为更新的ID解析实体。
一样,若是你设置一个新的实体( setCustomer()),那么外键属性( customerId)也将会被更新。
Customer customerA = user.getCustomer(); // change the customer id user.setCustomerId(customerIdB); // or set a customer with a different id user.setCustomer(customerB); customerB = user.getCustomer(); assert(customerA.getId() != customerB.getId());
注意: 为了急切的加载到一对一的关系,请使用实体DAO类的 loadDeep() 和 queryDeep() 。这将经过单个数据库查询来解析全部 与一对一关系的实体。若是您老是想访问相关的实体,这对于性能是很是好的。
@ToMany 定义了与一组其它实体(多个实体对象)的关系。将此应用于表示目标实体 List的属性。引用的实体必须有一个或多个属性指向拥有@ToMany的实体。
在内部, greenDAO 须要一个额外的属性来指向目标实体的ID, 这由 joinProperty 参数指定。若是此参数不存在,则会自动建立一个附加列来保存键。
@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; }
一对一关系中的getter方法(在这个例子中 getCustomer())在第一次调用时延迟解析目标实体。后续调用将当即返回先前解析的对象。
N注意,若是你更改了外键的属性(这里 customerId),下次调用getter(getCustomer())将解析更新ID后的实体。
另外,若是你设置了一个新的实体( setCustomer()),外键属性( customerId) 也将会被更新。
Customer customerA = user.getCustomer(); // change the customer id user.setCustomerId(customerIdB); // or set a customer with a different id user.setCustomer(customerB); customerB = user.getCustomer(); assert(customerA.getId() != customerB.getId());
注意:要急切的加载到一对一的关系,请使用实体DAO的 loadDeep() 和 queryDeep() 。这将经过单个数据库来解析全部一对一关系的实体。若是你老是访问相关的实体,这对于性能是很是好的。
@ToMany 定义了与一组其余实体(多个实体对象)的关系。将这个属性应用于表示目标实体的 List 。被引用的实体有一个或多个属性指向拥有 @ToMany 的实体。
有三种可能性来指定关系映射,只使用他们中的一个:
@Entity public class Customer { @Id private Long id; @ToMany(referencedJoinProperty = "customerId") @OrderBy("date ASC") private List orders; } @Entity public class Order { @Id private Long id; private Date date; private long customerId; }
@Entity public class Customer { @Id private Long id; @Unique private String tag; @ToMany(joinProperties = { @JoinProperty(name = "tag", referencedName = "customerTag") }) @OrderBy("date ASC") private List orders; } @Entity public class Order { @Id private Long id; private Date date; @NotNull private String customerTag; }
@Entity public class Product { @Id private Long id; @ToMany @JoinEntity( entity = JoinProductsWithOrders.class, sourceProperty = "productId", targetProperty = "orderId" ) private List ordersWithThisProduct; } @Entity public class JoinProductsWithOrders { @Id private Long id; private Long productId; private Long orderId; } @Entity public class Order { @Id private Long id; }
一旦运行,插件将会生成一个getter来解析被引用的实体的列表。例如,在前两种状况下:
// return all orders where customerId == customer.getId() List orders = customer.getOrders();
多对多关系在第一次请求中解析比较慢,而后缓存在 List 对象内的源实体中。后续对get方法的调用不会查询数据库。
更新多对多关系须要一些额外的工做。由于许多列表都被缓存了,当相关实体被添加到数据库中时,它们不会被更新。如下代码说明了这种行为:
// get the current list of orders for a customer // 获取客户的当前订单列表 List orders1 = customer.getOrders(); // insert a new order for this customer // 为这个客户插入新的订单 Order order = new Order(); order.setCustomerId(customer.getId()); daoSession.insert(order); // get the list of orders again // 再次获取订单列表 List orders2 = customer.getOrders(); // the (cached) list of orders was not updated // orders1 has the same size as orders2 // (缓存)订单列表没有被更新 // orders1和orders2具备相同的大小 assert(orders1.size() == orders2.size); // orders1 is the same object as orders2 // orders1 与 orders2是同一个对象 assert(orders1.equals(orders2));
所以,要添加新的相关实体,将它们手动的添加到源实体的许多列表中:
// get the to-many list before inserting the new entity // otherwise the new entity might be in the list twice // 在插入新实体以前获取多个列表 // 不然新的实体可能在列表中两次 List orders = customer.getOrders(); // create the new entity // 建立新的实体 Order newOrder = ... // set the foreign key property // 设置外键属性 newOrder.setCustomerId(customer.getId()); // persist the new entity // 保存新的实体 daoSession.insert(newOrder); // add it to the to-many list // 将其添加到多个列表中 orders.add(newOrder);
一样,你能够删除相关实体:
List orders = customer.getOrders(); // remove one of the orders from the database // 从数据库中删除一个订单 daoSession.delete(someOrder); // manually remove it from the to-many list // 从对多列表中删除它 orders.remove(someOrder);
在添加、更新或删除许多相关的实体时,可使用reset方法清除缓存列表。下一步将从新的查询相关的实体:
// clear any cached list of related orders // 清除任何相关订单的缓存列表 customer.resetOrders(); List orders = customer.getOrders();
有时,你想在两个方向上操纵1:N关系。在greenDAO,你必须添加一对一和一对多的关系来实现这一点。
接下来的例子展现了完成建立custormer和order实体,咱们用先前的一个例子。此次,咱们用 customerId 属性来建立两个关系:
@Entity public class Customer { @Id private Long id; @ToMany(referencedJoinProperty = "customerId") @OrderBy("date ASC") private List orders; } @Entity public class Order { @Id private Long id; private Date date; private long customerId; @ToOne(joinProperty = "customerId") private Customer customer; }
假设咱们有一个订单实体。使用这两种关系,咱们能够获得客户和客户曾今创造的全部订单。
List allOrdersOfCustomer = order.getCustomer().getOrders();
你能够经过创建一个指向自身一对一和一对多关系的实体来建立一个关系树。
@Entity public class TreeNode { @Id private Long id; private Long parentId; @ToOne(joinProperty = "parentId") private TreeNode parent; @ToMany(referencedJoinProperty = "parentId") private List children; }
生成的实体可让你操纵它的父类和子类:
TreeNode parent = entity.getParent(); List children = entity.getChildren();