greenDAO组件分析

目录

  1. 简介
  2. 建模
  3. 核心功能实现
    1. a. Master类
    2. b. DaoSession类
    3. c. 相应java POJO的Dao对象
    4. d. java POJO对象
  4. 其他细节
    1. 1. m:n关系
    2. 2. 查询Query
    3. 3. 线程安全
  5. 总结
  6. 参考

简介

greenDAO是一个开源的针对Android SQLite数据库的ORM组件,根据官方信息,其主要特点有性能好,包小(<100KB)等,本文主要分析greenDAO设计原理和思想,为今后我们自己使用和开发ORM组件提供指导。

建模

ORM组件可以使我们摆脱枯燥的各种SQL操作,使我们专心于对象操作。第一步就是如何进行建模,假设我们需要创建如下数据库:

按照greenDAO规范,我们需要根据数据库schema创建如下两个实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Entity(active = true, nameInDb = "USER")
public class User {
@Id
private Long id; // Long表示该值可以初始为空,由数据库生成;long表示开发者指定

@Property(nameInDb = "USER_NAME")
@NotNull
private String name;
}

@Entity(active = true, nameInDb = "DIALOG")
public class Dialog {

@Id
private long id;

@Property(nameInDb = "DIALOG_ID")
@NotNull
private long dialogId;

@Property(nameInDb = "USER_ID")
@NotNull
private long userId;

@ToOne(joinProperty = "userId")
private User user;
}

由此我们发现其设计理念:通过java POJO对象构建关系数据库表的概念,每个类代表一张表,成员变量表示Column,每个实例对象表示Row,辅助一些自定义注解来定义Column。我们只需要定义java POJO就可以定义数据库,在greendao-api文件夹下定义了当前所有对数据库定义的注解,基本上能满足正常使用,其表现力是可以的。下表展示常用注解和解释:

annotation 解释 属性值
@Entity 表的相关定义 nameInDb:表名
indexes:组合索引
createInDB:是否在数据库创建对应物理表
schema:属于哪个schema
active:update/delete/refresh方法是否生成
generateGettersSetters:是否生成getter/setter
generateConstructors:是否生成所有属性的构造方法
@Property 表的列 nameInDb:列名
@Id 主键声明 autoincrement:是否自增,根据sqlite3 ROWID特点,建议False
@Index 列的索引 name:名字
unique:是否唯一
@Unique
@NotNull
SQLite约束 UNIQUE约束
NOT NULL约束
@ToOne 一对一关系 joinProperty:指定外键
@ToMany 一对多关系 referencedJoinProperty/joinProperties(根据是不是外键)
@JoinEntity 多对多关系 entity/sourceProperty/targetProperty

核心功能实现

建模完成后我们查看其代码生成过程,如下图:

使用APT工具根据上述java POJO类,结合一些Freemarker模板生成新的java POJO,对应的DAO类,管理DAO类的DaoSession,以及管理数据库和生成DaoSession的DaoMaster。相比其他市面上纯Java的ORM组件,其最大特色是使用APT在编译期间将DAO类都生成完毕,不需要在运行期间动态生成SQL语句,因为其他组件通常使用注解在运行期间通过反射生成SQL语句,这样效率提升不少。

由于官方GitHub中并没有把使用的注解处理器开源,而且本文主要讨论greenDAO设计ORM框架的思想,所以我们关注生成类和组件源码中的设计思想。可以认为通过APT处理后,每个java POJO会生成一个包含其元数据的Entity类,接着通过源码中DaoGenerator工程生成上述app工程需要的类。

a. Master类

继承AbstractDaoMaster,主要功能有:

  1. 管理系统提供的数据库对象SQLiteDatabase(greenDAO为支持加密数据库增加了一层代理),内部提供SQLiteOpenHelper默认实现,我们也可以自定义实现SQLiteOpenHelper当做构造参数传入。

  2. 生成DaoSession:#newSession()#newSession(IdentityScopeType),这很类似网络领域中会话概念,主要思想是:对一个session中进行查询的对象进行缓存,如果再次获取相同对象(通常根据主键判断)可以节省构建对象过程,当然调用者可以关闭缓存功能。这种缓存功能带来效率提升,同时也会面临缓存与数据库数据不一致的情况。

  3. 构造方法中调用#registerDaoClass(Class<? extends AbstractDao<?, ?>> daoClass),初始化每个DAO类的DaoConfig对象。DaoConfig类是包含java Bean元数据的数据结构,包括表名,列名,主键等等,通过读取每个DAO类的成员变量TABLENAME和内部类Properties就可取到。

一个Master对象对应一个数据库连接,所以通常整个app只需构建一个Master。

b. DaoSession类

表示一次与数据库操作的会话,session存在就是为了减少在内存中构建对象次数。继承AbstractDaoSession,主要作用有:

  1. 初始化所有DAO对象并提供get方法,对所有DAO对象进行管理

  2. 每个DAO对象通过对应DaoConfig类进行初始化,每个DaoConfig#IdentityScope作为对应实体对象的缓存,由DaoSession#clear()负责清空;每个DaoSession创建对应DAO对象时会克隆一份DaoConfig对象,从而保证每个session缓存自身的数据。

  3. 提供多个DAO之间的事务操作,因为单个DAO对象只能针对单个表进行事务操作。

根据app业务可以选择多个session或者单个session,session需要缓存还是不需要缓存。

c. 相应java POJO的Dao对象

表示数据库中一张表的相关操作,继承AbstractDao类,主要作用有:

  1. Properties类,存储java POJO类中每个成员变量与数据库Column对应关系,以User为例:

    1
    2
    3
    4
    5
    6
    7
    public static final String TABLENAME = "USER";

    // 第四个变量表示是否是主键
    public static class Properties {
    public final static Property Id = new Property(0, long.class, "id", true, "_id");
    public final static Property Name = new Property(1, String.class, "name", false, "USER_NAME");
    }
  2. 创建表/删除表的函数

  3. 提供增(inset),删(delete),改(update),load(PrimaryKey),查(queryBuilder)等一系列函数操作。以插入USER表为例,AbstractDao主要负责拼接SQL模板,对应DAO对象负责bind操作。

  4. 提供attach\detach方法,针对单个对象进行缓存管理

d. java POJO对象

经过APT过程增加以下函数:

  1. 对应getter/setter函数

  2. 设置DaoSession,数据更新都通过DaoSession实现

  3. @Entity(active = true)时:

方法 解释
#update() java POJO更新到数据库
#refresh() 从数据库更新java POJO
#delete() 删除java POJO

其他细节

1. m:n关系

greenDAO对1:1的支持是使用成员变量,对1:n支持是使用List列表,m:n关系建议使用中间表转化成两个1:n实现。通过外键创建的对象都是懒加载,即第一次使用时才从数据库读取并生成对象,再次读取该对象时直接从内存中获得,所以需要注意数据一致性。

2. 查询Query

查询是最复杂的SQL操作,与查询相关的SQL由以下类组成:

其中,最常用的是Query,CountQuery和DeleteQuery,分别表示返回相关查询对象,执行count操作和从数据库查询指定数据并删除。Query返回结果主要有2种形式:1.一次性返回所有结果;2.实现懒加载,但需要手动关闭cursor。

Dao类与Query通过QueryBuilder类进行关联,使用构建者方式实现对应Query对象生成,同时QueryBuilder提供Join操作实现联表查询。

3. 线程安全

SQLite级别:通常我们针对一个数据库使用一个连接SQLiteDatabase,默认情况下对数据库CRUD操作都会被数据库加锁,因为DB是文件,所以是数据库级锁。API 11以上时,可以通过SQLiteDatabase#enableWriteAheadLogging()允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。

greenDAO级别: DaoSession和DAO都是线程安全的,其中,缓存IdentityScope使用ReentrantLock实现线程安全;写操作时使用SQLiteDatabase#isDbLockedByCurrentThread()判断是否获得数据库连接,没有的话,通过SQLiteDatabase#beginTransaction()获取数据连接(EXCLUSIVE mode);读操作时提供#forCurrentThread()方法获取针对当前线程的查询配置(通过QueryBuilder设置)副本,然后由底层SQLite数据库控制多线程读操作。

总结

greenDAO作为2011年开源的项目,整体设计是很优秀的,满足app对关系数据库大部分常用操作,架构可扩展性非常不错,对可变部分与公共部分的代码拆分,使其优于其他ORM框架。个人觉得如果有志于ORM轮子的同学可以从:1. SQL语句优化与校验 2. 多线程管理提高性能 这些方面去优化,当然,底层不使用关系数据库,直接面向对象数据库也是一个方向。

参考

  1. http://greenrobot.org/greendao/