GreenDao3 使用说明

GreenDao 3

一个将对象映射到 SQLite 数据库中的轻量且快速的ORM解决方案

资料

Github
官网

android-database-sqlcipher Github
数据库加密
GreenDaoUpgradeHelper Github
数据库升级辅助

配置

导入

  • 配置项目的 build.gradle
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
    }
}

// 使用数据库升级辅助GreenDaoUpgradeHelper时添加
allprojects {
    repositories {
        maven { url "https://jitpack.io" }
    }
}
  • 配置模组的 build.gradle
apply plugin: 'org.greenrobot.greendao'

dependencies {
    compile 'org.greenrobot:greendao:3.2.2'

    // 使用数据库加密时添加
    compile 'net.zetetic:android-database-sqlcipher:3.5.6'

    // 使用数据库升级辅助GreenDaoUpgradeHelper时添加
    compile 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.0.1'
}

参数

  • 设置 Schema,在模组的 build.gradle 中添加:
    • schemaVersion:数据库schema版本号,通过*OpenHelpers迁移数据,schema改变值增加。默认为1
    • daoPackage:生成DAOs、DaoMaster、DaoSession的包名。默认为entities所在包名。
    • targetGenDir:生成DAOs、DaoMaster、DaoSession的目录。默认为build/generated/source/greendao
    • generateTests: 设置true自动生成单元测试。
    • targetGenDirTests: 设置生成单元测试目录。默认为src/androidTest/java
greendao {
    schemaVersion 1
    daoPackage 'com.example.greendaodemo.dao'
    targetGenDir 'src/main/java'
}

混淆

  • 配置模组的 proguard-rules.pro
### greenDAO 3
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties

# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use RxJava:
-dontwarn rx.**

基本用法

实体

@Entity(
        // schema 名,多个 schema 时设置关联实体。插件产生不支持,需使用产生器
        // schema = "myschema",

        // 标记一个实体是否处于活动状态,活动实体有 update、delete、refresh 方法。默认为 false
        active = false,

        // 表名,默认为类名
        nameInDb = "AWESOME_USERS",

        // 定义多列索引
        indexes = {
                @Index(value = "name DESC", unique = true)
        },

        // 标记是否创建表,默认 true。多实体对应一个表或者表已创建,不需要 greenDAO 创建时设置 false
        createInDb = true,

        // 是否产生所有参数构造器。默认为 true。无参构造器必定产生
        generateConstructors = true,

        // 如果没有 get/set 方法,是否生成。默认为 true
        generateGettersSetters = true
)
public class User {
    // 数据库主键,autoincrement设置自增,只能为 long/ Long 类型
    @Id(autoincrement = true)
    private Long id;

    // 唯一,默认索引。可另定义属性唯一索引设为主键
    @Unique
    private String userId;

    // 列名,默认使用变量名。默认变化:userName --> USER_NAME
    @Property(nameInDb = "USERNAME")
    private String userName;

    // 索引,unique设置唯一,name设置索引别名
    @Index(unique = true)
    private long fk_dogId;

    // 非空
    @NotNull
    private String horseName;

    // 忽略,不持久化,可用关键字transient替代
    @Transient
    private int tempUsageCount;

    // 对一,实体属性 joinProperty 对应外联实体ID
    @ToOne(joinProperty = "fk_dogId")
    private Dog dog;

    // 对多。实体ID对应外联实体属性 referencedJoinProperty
    @ToMany(referencedJoinProperty = "fk_userId")
    private List<Cat> cats;

    // 对多。@JoinProperty:name 实体属性对应外联实体属性 referencedName
    @ToMany(joinProperties = {
            @JoinProperty(name = "horseName", referencedName = "name")
    })
    private List<Horse> horses;

    // 对多。@JoinEntity:entity 中间表;中间表属性 sourceProperty 对应实体ID;中间表属性 targetProperty 对应外联实体ID
    @ToMany
    @JoinEntity(
            entity = JoinUserWithSheep.class,
            sourceProperty = "uId",
            targetProperty = "sId"
    )
    private List<Sheep> sheep;
}
  • @Generated:greenDao生产代码注解,手动修改报错
  • @Keep:替换@Generated,greenDao不再生成,也不报错。@Generated(无hash)也有相同的效果
  • @ToOne:joinProperty 和对象联动,同时改变。对象懒加载,第一次请求后缓存
  • @ToMany:集合懒加载并缓存,之后获取集合不查找数据库,即集合数据不变。须手动修改集合,或调用reset方法清理集合

关系

  • User 和 Cat 配合产生一对多关系
@Entity
public class Cat {
    @Id
    private Long id;

    private String name;

    private long fk_userId;

    @ToOne(joinProperty = "fk_userId")
    private User user;
}
  • User、Sheep 和中间类 JoinUserWithSheep 配合产生多对多关系
@Entity
public class Sheep {
    @Id
    private Long id;

    private String name;

    @ToMany
    @JoinEntity(
            entity = JoinUserWithSheep.class,
            sourceProperty = "sId",
            targetProperty = "uId"
    )
    private List<User> users;
}

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

    private Long uId;

    private Long sId;
}
  • 多个 TreeNode 配合产生树状关系
@Entity
public class TreeNode {
    @Id
    private Long id;

    private Long parentId;

    @ToOne(joinProperty = "parentId")
    private TreeNode parent;

    @ToMany(referencedJoinProperty = "parentId")
    private List<TreeNode> children;
}

初始化

// Application 中执行
// DevOpenHelper 每次数据库升级会清空数据,一般用于开发
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db", null);
Database db = helper.getWritableDb();
DaoSession daoSession = new DaoMaster(db).newSession();

// 在使用的地方获取 DAO
NoteDao noteDao = daoSession.getNoteDao();

查询日志

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

DaoSession 增删改查

// DaoSession 的方法转换成 Dao 的对应方法执行
<T> long    insert(T entity)
<T> long    insertOrReplace(T entity)
<T> void    delete(T entity)
<T> void    deleteAll(java.lang.Class<T> entityClass)
<T> void    update(T entity)
<T,K> T     load(java.lang.Class<T> entityClass, K key)
<T,K> java.util.List<T>     loadAll(java.lang.Class<T> entityClass)
<T> QueryBuilder<T>     queryBuilder(java.lang.Class<T> entityClass)
<T,K> java.util.List<T>     queryRaw(java.lang.Class<T> entityClass, java.lang.String where, java.lang.String... selectionArgs)
<T> void    refresh(T entity)
void clear()  // 清理缓存域
Database getDatabase()
AbstractDao<?, ?> getDao(Class<? extends Object> entityClass)
Collection<AbstractDao<?, ?>> getAllDaos()
void runInTx(Runnable runnable)
<V> V callInTx(Callable<V> callable)
<V> V callInTxNoException(Callable<V> callable)

Dao 增加

long    insert(T entity)  // 插入指定实体
void    insertInTx(T... entities)
void    insertInTx(java.lang.Iterable<T> entities)
void    insertInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey)
long    insertWithoutSettingPk(T entity)  // 插入指定实体,无主键
long    insertOrReplace(T entity)  // 插入或替换指定实体
void    insertOrReplaceInTx(T... entities)
void    insertOrReplaceInTx(java.lang.Iterable<T> entities)
void    insertOrReplaceInTx(java.lang.Iterable<T> entities, boolean setPrimaryKey)
void    save(T entity)  // 依赖指定的主键插入或修改实体
void    saveInTx(T... entities)
void    saveInTx(java.lang.Iterable<T> entities)

Dao 删除

void    deleteAll()  // 删除所有
void    delete(T entity)  // 删除指定的实体
void    deleteInTx(T... entities)
void    deleteInTx(java.lang.Iterable<T> entities)
void    deleteByKey(K key)  // 删除指定主键对应的实体
void    deleteByKeyInTx(K... keys)
void    deleteByKeyInTx(java.lang.Iterable<K> keys)

Dao 修改

void    update(T entity)
void    updateInTx(T... entities)
void    updateInTx(java.lang.Iterable<T> entities)

Dao 其它

void    refresh(T entity)  // 从数据库获取值刷新本地实体
long    count()  // 数量

boolean     detach(T entity)  // 从域中分离实体
void    detachAll()  // 从域中分离所有实体

AbstractDaoSession  getSession()
Database    getDatabase()
java.lang.String    getTablename()
java.lang.String[]  getAllColumns()
java.lang.String[]  getPkColumns()
java.lang.String[]  getNonPkColumns()
Property    getPkProperty()
Property[]  getProperties()

Dao 查询

java.util.List<T>   loadAll()
T   load(K key)
T   loadByRowId(long rowId)

QueryBuilder 查询

List joes = userDao.queryBuilder()  // 查询 User
                .where(Properties.FirstName.eq("Joe"))  // 首名为 Joe
                .orderAsc(Properties.LastName)  // 末名升序排列
                .list();  // 返回集合

// 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();
QueryBuilder<T>     queryBuilder()  // Dao

// QueryBuilder
QueryBuilder<T>     where(WhereCondition cond, WhereCondition... condMore)  // 条件,AND 连接
QueryBuilder<T>     whereOr(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore)  // 条件,OR 连接
QueryBuilder<T>     distinct()  // 去重,例如使用联合查询时
QueryBuilder<T>     limit(int limit)  // 限制返回数
QueryBuilder<T>     offset(int offset)  // 偏移结果起始位,配合limit(int)使用
QueryBuilder<T>     orderAsc(Property... properties)  // 排序,升序
QueryBuilder<T>     orderDesc(Property... properties)  // 排序,降序
QueryBuilder<T>     orderCustom(Property property, java.lang.String customOrderForProperty)  // 排序,自定义
QueryBuilder<T>     orderRaw(java.lang.String rawOrder)  // 排序,SQL 语句
QueryBuilder<T>     preferLocalizedStringOrder()  // 本地化字符串排序,用于加密数据库无效
QueryBuilder<T>     stringOrderCollation(java.lang.String stringOrderCollation)  // 自定义字符串排序,默认不区分大小写

WhereCondition  and(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore)  // 条件,AND 连接
WhereCondition  or(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore)  // 条件,OR 连接

Query 重复查询

// Joe,1970
Query query = userDao.queryBuilder().where(
    Properties.FirstName.eq("Joe"), Properties.YearOfBirth.eq(1970)
).build();
List joesOf1970 = query.list();

// Maria,1977
query.setParameter(0, "Maria");
query.setParameter(1, 1977);
List mariasOf1977 = query.list();
// QueryBuilder
Query<T>    build()
CursorQuery     buildCursor()
CountQuery<T>   buildCount()
DeleteQuery<T>  buildDelete()

// Query
// 设置查询参数,从 0 开始
Query<T>    setParameter(int index, java.lang.Object parameter)
Query<T>    setParameter(int index, java.lang.Boolean parameter)
Query<T>    setParameter(int index, java.util.Date parameter)
void    setLimit(int limit)  // 限制返回数
void    setOffset(int offset)  // 偏移结果起始位,配合limit(int)使用

// Query 绑定线程,执行非本线程的 Query 抛异常,调用获取本线程 Query
Query<T>    forCurrentThread()  // 获取本线程 Query

获取查询结果

// QueryBuilder、Query
T   unique()  // 返回唯一结果或者 null
T   uniqueOrThrow()  // 返回唯一非空结果,如果 null 则抛异常
java.util.List<T>   list()  // 返回结果集进内存
// 懒加载,须在 try/finally 代码中关闭。
LazyList<T>     listLazy()  // 第一次使用返回结果集,所有数据使用后会自动关闭
LazyList<T>     listLazyUncached()  // 返回虚拟结果集,数据库读取不缓存
CloseableListIterator<T>    listIterator()  // 懒加载数据迭代器,不缓存,所有数据使用后会自动关闭

// QueryBuilder、CountQuery
long    count()  // 获取结果数量

SQL 查询

// QueryBuilder.where() 配合 WhereCondition.StringCondition() 实现SQL查询
Query query = userDao.queryBuilder()
                .where(new WhereCondition.StringCondition("_ID IN (SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)"))
                .build();

// Dao.queryRawCreate() 实现SQL查询
Query query = userDao.queryRawCreate(  ", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin");
// Dao
java.util.List<T>   queryRaw(java.lang.String where, java.lang.String... selectionArg)
Query<T>    queryRawCreate(java.lang.String where, java.lang.Object... selectionArg)
Query<T>    queryRawCreateListArgs(java.lang.String where, java.util.Collection<java.lang.Object> selectionArg)

// WhereCondition.PropertyCondition
PropertyCondition(Property property, java.lang.String op)
PropertyCondition(Property property, java.lang.String op, java.lang.Object value)
PropertyCondition(Property property, java.lang.String op, java.lang.Object[] values)

// WhereCondition.StringCondition
StringCondition(java.lang.String string)
StringCondition(java.lang.String string, java.lang.Object value)
StringCondition(java.lang.String string, java.lang.Object... values)

DeleteQuery 删除查询

DeleteQuery<T>  buildDelete()  // QueryBuilder

进阶用法

联合查询

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

// 欧洲超过百万人口的城市
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();

// 爷爷叫林肯的人
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();
// QueryBuilder,联合查询
<J> Join<T,J>   join(java.lang.Class<J> destinationEntityClass, Property destinationProperty)
<J> Join<T,J>   join(Property sourceProperty, java.lang.Class<J> destinationEntityClass)
<J> Join<T,J>   join(Property sourceProperty, java.lang.Class<J> destinationEntityClass, Property destinationProperty)
<J> Join<T,J>   join(Join<?,T> sourceJoin, Property sourceProperty, java.lang.Class<J> destinationEntityClass, Property destinationProperty)

自定义类型

默认支持类型:byte[]StringDate
booleanintshortlongfloatdoublebyte
BooleanIntegerShortLongFloatDoubleByte

// enum 转换为 Integer
@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; // 使用稳定的 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) {
            // 判断返回 null
            return entityProperty == null ? null : entityProperty.id;
        }
    }
}

升级

使用 DevOpenHelper 每次升级数据库,表会删除重建,推荐开发使用。实际使用中建立类继承 DaoMaster.OpenHelper,实现 onUpgrade()

public class MySQLiteOpenHelper extends DaoMaster.OpenHelper {

    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
        super(context, name, factory);
    }

    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        if (oldVersion == newVersion) {
            Log.d("onUpgrade", "数据库是最新版本" + oldVersion + ",不需要升级");
            return;
        }
        Log.d("onUpgrade", "数据库从版本" + oldVersion + "升级到版本" + newVersion);
        switch (oldVersion) {
            case 1:
                String sql = "";
                db.execSQL(sql);
            case 2:
            default:
                break;
        }
    }
}

// 初始化使用 MySQLiteOpenHelper
MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this, "notes-db", null);
Database db = helper.getWritableDb();
DaoSession daoSession = new DaoMaster(db).newSession();

另有升级辅助库 GreenDaoUpgradeHelper,通过 MigrationHelper 在删表重建的过程中,使用临时表保存数据并还原。

public class MySQLiteOpenHelper extends DaoMaster.OpenHelper {
    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
        super(context, name, factory);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        MigrationHelper.migrate(db, TestDataDao.class, TestData2Dao.class, TestData3Dao.class);
    }
}

// 初始化
MigrationHelper.DEBUG = true; //如果你想查看日志信息,请将 DEBUG 设置为 true
MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this, "test.db", null);
DaoMaster daoMaster = new DaoMaster(helper.getWritableDatabase());

高阶用法

SQLCipher 加密

  • 使用getEncryptedReadableDb()getEncryptedWritableDb()获取加密的数据库
  • 256位AES加密,会提升APK的大小
  • Robolectric 测试时,须使用非加密数据库
public static final boolean ENCRYPTED = true;

MySQLiteOpenHelper helper = new MySQLiteOpenHelper(this, ENCRYPTED ? "notes-encrypted.db" : "notes.db", null);
Database db = ENCRYPTED ? helper.getEncryptedWritableDb("<your-secret-password>") : helper.getWritableDb();
daoSession = new DaoMaster(db).newSession();

RxJava 支持

// DaoSession
RxTransaction   rxTx()
RxTransaction   rxTxPlain()

// Dao
RxDao<T,K>  rx()
RxDao<T,K>  rxPlain()

// QueryBuilder
RxQuery<T>  rx()
RxQuery<T>  rxPlain()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容