您的位置:首页 > 移动开发 > Android开发

GreenDAO 3.x项目开发实战持续更新ing — Android ORM框架(三)

2016-08-09 09:34 615 查看

GreenDAO 3.x项目开发实战持续更新ing — Android ORM框架(三)

内容介绍:GreenDAO框架在实际开发中遇到的问题总结

最新更新时间:2016/8/6

官网:http://greenrobot.org/greendao/

版本:V3.0.1

说明:本篇会持续更新

博客地址:http://blog.csdn.net/kevindgk

版权声明:本文为原创文章,未经允许不得转载

联系方式:815852777@qq.com

GreenDAO 3x项目开发实战持续更新ing Android ORM框架三

前言

遇到的问题总结
自增长ID

数据库查询
基本查询方式

分页查询

多次查询

多线程查询

原生的查询方式

数据库分页时同时执行插入和查询操作引发的问题

数据库事务

关于Session

数据库加密

数据库工具类的封装

前言

GreenDAO 3.x使用起来十分方便快捷,而且效率也很高,很直观的感受就是:

1. 使用注解,集成特别简单;

2. CRUD效率高,速度快;

3. 和其他ORM框架一样,将面向关系转换成了面向对象的操作,更符合面向对象的思想;

我唯一所做的事情就是两件事:

第一步:将GreenDAO集成到项目中,即导入插件和依赖;

第二步:创建对应表的Bean类,并使用注解注释;

[]http://blog.csdn.net/kevindgk/article/details/52084046](“对于基本使用如果不了解,可以点击查看我的该文章 GreenDAO3.x的基本使用”)

第三步:简单的封装了一个数据库操作的工具类,主要作用有:数据库的初始化,调试阶段模拟插入数据等操作。

遇到的问题总结

自增长ID

很多数据库对于主键列都支持一种自增长id的特性,即如果某个数据列的类型是整型,而且该列作为主键列,则可以指定该列具有自增长功能。指定自增长功能通常用于设置逻辑主键列,Mysql使用auto_increment来设置自增长。sql语句如下:

create table test{
_id int auto_increment primary key
};


一旦设置了某列具有自增长的特性,则向该表中插入记录的时候可以不为该主键列设置,该列的值由数据库系统自动生成。这句话的意思就是说,如果设置了某列为主键,那么你向数据库里插入记录的时候不用为该列赋值,即该属性应该传入null。

但是,这里问题来了,我在greendao中给某个成员变量添加了@Id修饰,并且根据官方文档上的要求写了autoincrement = true;但是我在插入数据的时候,这个成员变量到底赋值多少呢:



官方文档上写的是,id可以是long或者是Long,但是在java中long默认会赋初值,不会给null,所以我们在这里要使用Long,然后在插入记录的时候赋值为null,或者干脆这么写:



注意,我创建对象的都是数据都是set进去的,所以这么写稍微简单些,如果是创建对象的时候构造方法里面传入数值,那么创建的时候id位置直接写null就行了。

小结:如果仅仅是作为id使用,那么用long和Long都行,如果是自增长的,必须使用Long。

数据库查询

基本查询方式

方式一

List joes = userDao.queryBuilder()
.where(Properties.FirstName.eq("Joe"))
.orderAsc(Properties.LastName)
.list();


方式二

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(int):查询返回的数量,即每一页显示的记录条数

Offset(int):查询结果的位移,该参数必须和limit结合使用,即从第offset条开始,向后取查询的limit条数据,如果没有设置该参数,默认从第0条开始取。

多次查询

一旦我们使用QueryBuilder创建了一个查询Query类,该类可以重复使用来进行多次查询操作。这种重复使用的方式要比创建新的查询类要高效,如果查询的参数没有变化,那么你可以直接调用它的list()方法,如果查询的参数改变了,你可以调用setParameterd这个方法来修改改变的参数。目前,参数是按照插入的顺序从0开始排序的,例子如下:

第一次查询出来姓名叫Joe,出生在1970年的人:

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


如果第二次查询和第一次查询条件相同,那么可以直接获取:

List joesOf1970 = query.list();


如果第二次查询和第一次查询的条件不相同,那么可以进行设置:

query.setParameter(0, "Maria"); // 修改第0个参数为Maria
query.setParameter(1, 1977);    // 修改第1个参数为1977
List mariasOf1977 = query.list();


如此一来,不需要重复的使用queryBuilder来创建query,即提升了查询速度,又节省的内存的消耗。

多线程查询

如果在多个线程中使用查询同一个Query,那么必须使用forCurrentThread()来获得当前线程的一个query实例,该实例对象和当前线程绑定,这样的话,就会使得设置参数这个方法是安全的,各个线程互不影响。如果一个线程去操作另一个线程绑定的query,那么就会报异常。如果多个并发线程使用相同的查询对象会导致程序死锁,我们应该避免死锁。

为了避免这种情况的发生,greendao提供了forCurrentThread这个方法来返回一个线程局部Query变量,在当前线程内部使用是安全的,该变量和使用queryBuilder创建的时候的初始参数相同。

builder.forCurrentThread().setParameter()


两种方式如下:



那么问题来了:既然说可以使用同一个Query,但是多线程查询又会导致死锁,所以只能保证多个线程不会并发执行的情况下才能使用,关键是一般查询都会放在listview中展示,而且有下拉刷新或者上拉加载,所以难免会有并发执行的情况,所以,感觉这个好处也不是很大。只能在查询较少数量,或者多线程不会并发执行的情况下执行(比如可以将多线程使用handler变成单线程执行的方式)。

原生的查询方式

如果QueryBuilder提供的查询方式不能满足您的需求,可以使用原生的SQL语句进行查询,greendao提供了两种原生的查询方式。

推荐的方式是使用QueryBuilder 和WhereCondition.StringCondition 来执行原生的sql语句,您可以传递任何的sql参数给WHERE来创建查询的builder。

Query query = userDao.queryBuilder().where(
new StringCondition("_ID IN " +
"(SELECT USER_ID FROM USER_MESSAGE WHERE READ_FLAG = 0)")).build();


另一种方式 是使用 the queryRaw or queryRawCreate methods。。。

Query query = userDao.queryRawCreate(
", GROUP G WHERE G.NAME=? AND T.GROUP_ID=G._ID", "admin");


我们就是为了将面向关系数据库改变成面向对象的方式,才使用的GreenDAO,所以不推荐使用原生的查询方式,而且在android端使用的是轻量级的数据库,存放少量数据或者临时数据,所以使用最多的情况都是进行单表操作,所以只要学会使用基本的操作和原理就ok了,至于多表查询,此处省略一万字。。。

数据库分页时,同时执行插入和查询操作引发的问题

使用场景:本地数据库+分页+倒序显示

有时候,如果需要同时查询和插入数据库数据,很容易会产生各种各样的问题,我们在设计的时候都会尽量避免这种情景。但是有时候仍然会遇到一种场景,就是自己做分页。比如,数据库里现在有100条数据,id是自增长的,我们根据id将数据库里面的数据倒序排序,然后第一页取出前10条数据进行展示,即总显示最新的数据,如下图所示:



这个时候我们取出来的是第91~100条数据,展示在recycleview中,我们分页使用的是SQLiteDatabase的query()方法,该方法的签名是:

public Cursor query(boolean distinct, String table, String[] columns,String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit) {
return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,groupBy, having, orderBy, limit, null);
}


在该方法中limit用于分页,相当于select语句limit关键字后面的部分,下面这个语句就是返回第6-15条数据:

select * from table LIMIT 5,10;  //从第5条记录向后查10条记录,即第6~15


所以回到我们刚才的话题,如果我们取出来了第91-100条数据之后,有数据插入进来了,就会造成数据库数据的变化:



如果这个时候上拉加载,再去拉去第二页数据的话,就会造成第91条数据重复显示了。

在服务端,一般是不会考虑这种事情的,但是如果我们自己本地数据库的话,有时候会遇到这种问题。

解决的办法:不使用正常的分页方法,即每次取(pageNum * PAGE_SIZE , PAGE_SIZE),而添加一个查询的条件,就是每次记录下来取出来的最后一条数据的id,即在本例子中,第一次取出来第100~91条数据,我下次再取得时候,添加一个过滤的条件,要求id<91,然后每次只需要取前10条即可。这样的话,就不用考虑最新插入的数据。



如图所示,每次查询的时候都要求id降序排列,并且要求小于上次查询的最后一条数据的id。最开始的时候,给这个lastId赋初值为

public static long lastId = Long.MAX_VALUE;


需要注意的地方就是,如果同时包含下拉刷新,那么刷新之前也要重新给lastId赋值一个最大值。

有图有真相:



数据库事务

关于Session

DaoSession 是greenDAO的一个关键接口,提供了表的操作对象集合,它还管理者实体类的身份范围(这个词不准确,不过看下面的内容就明白了)。

daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
noteDao = daoSession.getNoteDao();


注意,这个数据库的链接属于DaoMaster,所以多个daoSession会引用同一个数据库连接。创建session会非常迅速,但是每一个session都会分配内存,特别是实体类的会话缓存。

例如,如果有两次数据库查询,都是查询的同一条记录,即同一个对象,那么greendao返回的是同一个对象吗?答案是:不一定。查询结果会取决于identity scope,好吧我承认英语十三级的实力还是不够,不理解这个意思,但是官方的解释一看就明白了。默认情况下,多次查询同一条记录,greendao返回的是同一个对象。例如,查询USER表中的ID为42的User实体类,始终会返回同一个对象。其实,就是做了数据库一级缓存(查询)。如果一个实体类在内存中还存在(greendao使用的是软引用),那么下次查询的时候不会被创建,而是会迅速将这个已经存在的实体类返回,所以查询速度会非常快。这个其实就是数据库一级缓存,很多Java的ORM框架都有这个功能,有时候,还会做二级缓存等操作,如果想了解的,可以询问自己公司的数据库的同事。

由于GreenDAO的整体架构和Hibernate十分相似,所以感兴趣的同学可以自己找资料查看。通过了解Hibernate的整体框架,你会对DAO层有一个更加深入的了解,此处仅附上相关链接:Hibernate官方网站地址

数据库加密

数据库工具类的封装

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: