安卓开发

Android数据库操作(三)——增、删、改、查、聚合函数

2017-06-25  本文已影响690人  _猜火车_

前言

本文参考转发摘自:郭霖博客http://blog.csdn.net/guolin_blog?viewmode=contents 强烈建议进入原博客查看学习

1、数据存储操作

1.1 传统的储存数据方式

public long insert(String table, String nullColumnHack, ContentValues values)  

可以看到,insert方法接收三个参数,第一个参数是表名,第二个参数通常都用不到,直接传null,第三个参数则是一个封装了待存储数据的ContentValues对象。

SQLiteDatabase db = dbHelper.getWritableDatabase();
 ContentValues values = new ContentValues();
  values.put("title", "这是一条新闻标题");
  values.put("content", "这是一条新闻内容");
  values.put("publishdate", System.currentTimeMillis());

  long id = db.insert("news", null, values); 

其中,调用ContentValues的put()方法来添加待存储数据,put()方法接收两个参数,第一个参数是数据库表中对应的列名,第二个参数就是要存储的值,最后调用一下insert()方法,这条新闻就会插入到news表当中了,并且该数据行对应的id会作为返回值进行返回。

1.2 使用LitePal存储数据

public class News extends DataSupport{  
      
    ......  
      
    // 自动生成get、set方法  
}  

可以看到,这里只是让News类继承自了DataSupport,其它什么都没有改变。另外几个Comment、Introduction、Category类也使用同样的改法,这里就不一一演示了。

News news=new News();
news.setTitle("这是一条新闻标题");
news.setContent("这是一条新闻内容");
news.setPublishDate(new Date());
 //调用sava方法保存(没有继承DataSupport是没有这个方法的)
 news.save();

怎么样?是不是非常简单,不需要SQLiteDatabase,不需要ContentValues,不需要通过列名组装数据,甚至不需要指定表名,只需要new出一个News对象,然后把要存储的数据通过setter方法传入,最后调用一下save()方法就好了,而这个save()方法自然就是从DataSupport类中继承而来的了。

if (news.save()) {  
    Toast.makeText(context, "存储成功", Toast.LENGTH_SHORT).show();  
} else {  
    Toast.makeText(context, "存储失败", Toast.LENGTH_SHORT).show();  
}  
News news = new News();  
news.setTitle("这是一条新闻标题");  
news.setContent("这是一条新闻内容");  
news.setPublishDate(new Date());  
news.saveThrows();  

使用saveThrows()方法来存储数据,一旦存储失败就会抛出一个DataSupportException异常,我们可以通过对这个异常进行捕获来处理存储失败的情况。

News news = new News();  
news.setTitle("这是一条新闻标题");  
news.setContent("这是一条新闻内容");  
news.setPublishDate(new Date());  
Log.d("TAG", "news id is " + news.getId());  
news.save();  
Log.d("TAG", "news id is " + news.getId());  

可以看到,这条新闻确实已经存储成功了,并且对应的id正是1,和我们前面打印的结果是一致的。

Comment comment1 = new Comment();
 comment1.setContent("好评!");
 comment1.setPublishDate(new Date());
 comment1.save();
        
 Comment comment2 = new Comment();
  comment2.setContent("赞一个");
  comment2.setPublishDate(new Date());
  comment2.save();
        
   News news = new News();
   news.getCommentList().add(comment1);
   news.getCommentList().add(comment2);
   news.setTitle("第二条新闻标题");
   news.setContent("第二条新闻内容");
   news.setPublishDate(new Date());
   news.setCommentCount(news.getCommentList().size());
   news.save();

可以看到,这里先是存储了一条comment1数据,然后存储一条comment2数据,接着在存储News之前先把刚才的两个Comment对象添加到了News的commentList列表当中,这样就表示这两条Comment是属于这个News对象的,最后再把News存储到数据库中,这样它们之间的关联关系就会自动建立了。让我们查看数据库表检查一下吧,首先看一下news表,如下所示:

可以看到,两条评论都已经成功存储到comment表中了,并且这两条评论的news_id都是2,说明它们是属于第二条新闻的。怎么样,仅仅是在存储数据之前建立好实体类之间的关系,再调用一下save()方法,那么数据之间的关联关系就会自动建立了,是不是非常简单?上面的代码只是多对一情况的一种用法,还有一对一和多对多的情况,其实用法都是差不多的,相信你已经能举一反三了。

List<News> newsList;  
...  
for (News news : newsList) {  
    news.save();  
}  
List<News> newsList;  
...  
DataSupport.saveAll(newsList);  

saveAll()方法接收一个Collection集合参数,只要把待存储的集合数据传入即可。这个方法可以完成和上面一段代码完全一样的功能,但效率却会高得多,而且写法也更加简单。

2、修改操作

2.1 传统的方式修改操作

public int update(String table, ContentValues values, String whereClause, String[] whereArgs) 

update()方法接收四个参数,第一个参数是表名,第二个参数是一个封装了待修改数据的ContentValues对象,第三和第四个参数用于指定修改哪些行,对应了SQL语句中的where部分

SQLiteDatabase db = dbHelper.getWritableDatabase();  
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6发布");  
db.update("news", values, "id = ?", new String[] {"2"});  

其作用相当于如下SQL语句:

update news set title='今日iPhone6发布' where id=2;  

2.2 使用LitePal修改操作

方式一:

public static int update(Class<?> modelClass, ContentValues values, long id)  

这个静态的update()方法接收三个参数,第一个参数是Class,传入我们要修改的那个类的Class就好,第二个参数是ContentValues对象,这三个参数是一个指定的id,表示我们要修改哪一行数据

ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6发布");  
DataSupport.update(News.class, values, 2);  

可以看出,总体来讲还是比原生的用法要简单一些的,首先我们避免掉了要去获取SQLiteDatabase对象的步骤,其次在指定修改某一条id记录的时候只需要传入这个id即可,语法更简练

public static int updateAll(Class<?> modelClass, ContentValues values, String... conditions)  

updateAll()方法表示修改多行记录,其中第一个参数仍然是Class,第二个参数还是ContentValues对象,第三个参数是一个conditions数组,用于指定修改哪些行的约束条件,返回值表示此次修改影响了多少行数据

ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus发布");  
DataSupport.updateAll(News.class, values, "title = ?", "今日iPhone6发布");  

前面都没什么好说的,重点我们看一下最后的这个 conditions数组,由于它的类型是一个String数组,我们可以在这里填入任意多个String参数,其中最前面一个String参数用于指定约束条件,后面所有的String参数用于填充约束条件中的占位符(即?号),比如约束条件中有一个占位符,那么后面就应该填写一个参数,如果有两个占位符,后面就应该填写两个参数,以此类推

ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus发布");  
DataSupport.updateAll(News.class, values, "title = ? and commentcount > ?", "今日iPhone6发布", "0");  

可以看出,通过占位符的方式来实现条件约束明显要比原生的API更加简单易用。

ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus发布");  
DataSupport.updateAll(News.class, values);  

方式二:

News updateNews = new News();  
updateNews.setTitle("今日iPhone6发布");  
updateNews.update(2); 
News updateNews = new News();  
updateNews.setTitle("今日iPhone6发布");  
updateNews.updateAll("title = ? and commentcount > ?", "今日iPhone6发布", "0");  

还是非常好理解的,这里我就不再详细解释了。

News updateNews = new News();  
updateNews.setToDefault("commentCount");  
updateNews.updateAll();  

3、删除操作

3.1 传统的删除数据方式

public int delete(String table, String whereClause, String[] whereArgs)  

delete()方法接收三个参数,第一个参数同样是表名,第二和第三个参数用于指定删除哪些行,对应了SQL语句中的where部分

SQLiteDatabase db = dbHelper.getWritableDatabase();  
db.delete("news", "commentcount = ?", new String[] {"0"});  

其作用相当于如下SQL语句:

delete from news where commentcount=0;  

3.2 使用LitePal删除数据

方式一:

public static int delete(Class<?> modelClass, long id)  

delete()方法接收两个参数,第一个参数是Class,传入我们要删除的那个类的Class就好,第二个参数是一个指定的id,表示我们要删除哪一行数据

DataSupport.delete(News.class, 2); 
int deleteCount = DataSupport.delete(News.class, 2);  
Log.d("TAG", "delete count is " + deleteCount);  

其中delete()方法的返回值表示被删除的记录数,打印结果如下所示:


public static int deleteAll(Class<?> modelClass, String... conditions)  

看起来很眼熟吧?非常简单,deleteAll()方法接收两个参数,第一个参数是Class,传入我们要删除的那个类的Class就好,第二个参数是一个conditions数组,用于指定删除哪些行的约束条件,返回值表示此次删除了多少行数据,用法和updateAll()方法是基本相同的

DataSupport.deleteAll(News.class, "title = ? and commentcount = ?", "今日iPhone6发布", "0");  
DataSupport.deleteAll(News.class); 

在不指定约束条件的情况下,deleteAll()方法就会删除表中所有的数据了

方式二:

News news = new News();  
news.delete();  

这里new出了一个News对象,这个对象明显是没有持久化的,那么此时调用delete()方法则不会删除任何数据

News news = new News();  
news.setTitle("这是一条新闻标题");  
news.setContent("这是一条新闻内容");  
news.save();  
...  
news.delete();  
News news;  
...  
if (news.isSaved()) {  
    news.delete();  
}  

4、查询数据

4.1 传统的查询数据方式

public Cursor rawQuery(String sql, String[] selectionArgs)  

其中,rawQuery()方法接收两个参数,第一个参数接收的就是一个SQL字符串,第二个参数是用于替换SQL语句中占位符(?)的字符串数组。rawQuery()方法返回一个Cursor对象,所有查询到的数据都是封闭在这个对象当中的,我们只要一一取出就可以了

public Cursor query(String table, String[] columns, String selection,  
            String[] selectionArgs, String groupBy, String having,  
            String orderBy)  

其中第一参数是表名,表示我们希望从哪张表中查询数据。第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作。第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式

SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor cursor = db.query("news", null, null, null, null, null, null);  

可以看到,将第一个表名参数指定成news,然后后面的六个参数我们都用不到,就全部指定成null。

SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor cursor = db.query("news", null, "commentcount>?", new String[]{"0"}, null, null, null);  

由于 第三和第四个参数是用于指定约束条件的,所以我们在第三个参数中指明了commentcount>?,然后在第四个参数中通过一个String数组来替换占位符,这样查到的结果就是news表中所有评论数大于零的新闻了。那么其它的几个参数呢?仍然用不到,所以还是只能传null。

List<News> newsList = new ArrayList<News>();  
if (cursor != null && cursor.moveToFirst()) {  
    do {  
        int id = cursor.getInt(cursor.getColumnIndex("id"));  
        String title = cursor.getString(cursor.getColumnIndex("title"));  
        String content = cursor.getString(cursor.getColumnIndex("content"));  
        Date publishDate = new Date(cursor.getLong(cursor.getColumnIndex("publishdate")));  
        int commentCount = cursor.getInt(cursor.getColumnIndex("commentcount"));  
        News news = new News();  
        news.setId(id);  
        news.setTitle(title);  
        news.setContent(content);  
        news.setPublishDate(publishDate);  
        news.setCommentCount(commentCount);  
        newsList.add(news);  
    } while (cursor.moveToNext());  
}  

4.2 通过LitePal查询数据

4.2.1 简单查询

News news = DataSupport.find(News.class, 1);  

天呐!有没有觉得太轻松了?仅仅一行代码,就可以把news表中id为1的记录查出来了,而且结果还是 自动封装到 News 对象 里的,也 不需要我们手动再从Cursor中去解析。如果是用原生的SQL语句,或者query()方法来写,至少要20行左右的代码才能完成同样的功能!

News firstNews = DataSupport.findFirst(News.class); 
News lastNews = DataSupport.findLast(News.class);  
List<News> newsList = DataSupport.findAll(News.class, 1, 3, 5, 7);  

可以看到,首先我们是调用的findAll()方法,然后这个方法的第一个参数仍然是指定的泛型类,但是后面的参数就很随意了,你可以传入任意个id进去,findAll()方法会把所有传入的id所对应的数据全部查出来,然后一起返回到List<News>这个泛型集合当中。

long[] ids = new long[] { 1, 3, 5, 7 };  
List<News> newsList = DataSupport.findAll(News.class, ids);  
List<News> allNews = DataSupport.findAll(News.class);  

看到没有,我们只需要把后面的参数都去掉,在不指定具体id的情况下,findAll()方法查询出的就是news表中的所有数据了,是不是语义性非常强?

4.2.2 连缀查询

List<News> newsList = DataSupport.where("commentcount > ?", "0").find(News.class);  
  • 可以看到,首先是调用了DataSupport的where()方法,在这里指定了查询条件。where()方法接收任意个字符串参数,其中第一个参数用于进行条件约束,从第二个参数开始,都是用于替换第一个参数中的占位符的。那这个where()方法就对应了一条SQL语句中的where部分。
select * from users where commentcount > 0;  
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0").find(News.class);  
  • 可以看到,这里我们新增了一个select()方法,这个方法接收任意个字符串参数,每个参数要求对应一个列名,这样就只会把相应列的数据查询出来了,因此select()方法对应了一条SQL语句中的select部分
select title,content from users where commentcount > 0; 
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").find(News.class);  
  • order()方法中接收一个字符串参数,用于指定查询出的结果按照哪一列进行排序,asc表示正序排序,desc表示倒序排序,因此order()方法对应了一条SQL语句中的order by部分。
select title,content from users where commentcount > 0 order by publishdate desc;  
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).find(News.class);  
  • 这里我们又连缀了一个limit()方法,这个方法接收一个整型参数,用于指定查询前几条数据,这里指定成10,意思就是查询所有匹配结果中的前10条数据。
select title,content from users where commentcount > 0 order by publishdate desc limit 10;  
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).offset(10)  
        .find(News.class);  
  • 可以看到,这里我们又添加了一个offset()方法,用于指定查询结果的偏移量,这里指定成10,就表示偏移十个位置,那么原来是查询前10条新闻的,偏移了十个位置之后,就变成了查询第11到第20条新闻了,如果偏移量是20,那就表示查询第21到第30条新闻,以此类推。因此,limit()方法和offset()方法共同对应了一条SQL语句中的limit部分。
select title,content from users where commentcount > 0 order by publishdate desc limit 10,10;  

4.2.3 激进查询

News news = DataSupport.find(News.class, 1, true);  
List<Comment> commentList = news.getCommentList();  

可以看到,这里并没有什么复杂的用法,也就是在find()方法的最后多加了一个true参数,就表示使用激进查询了。这会将和news表关联的所有表中的数据也一起查出来,那么comment表和news表是多对一的关联,所以使用激进查询一条新闻的时候,那么该新闻所对应的评论也就一起被查询出来了。

public class News extends DataSupport{  
    ...  
public List<Comment> getComments() {  
        return DataSupport.where("news_id = ?", String.valueOf(id)).find(Comment.class);  
    }  
}  

可以看到,我们在News类中添加了一个getComments()方法,而这个方法的内部就是使用了一句连缀查询,查出了当前这条新闻对应的所有评论。改成这种写法之后,我们就可以将关联表数据的查询延迟,当我们需要去获取新闻所对应的评论时,再去调用News的getComments()方法,这时才会去查询关联数据。这种写法会比激进查询更加高效也更加合理。

4.2.4 原生查询

Cursor cursor = DataSupport.findBySQL("select * from news where commentcount>?", "0"); 

findBySQL()方法接收任意个字符串参数,其中第一个参数就是SQL语句,后面的参数都是用于替换SQL语句中的占位符的,用法非常简单。另外,findBySQL()方法返回的是一个Cursor对象,这和原生SQL语句的用法返回的结果也是相同的。

5、聚合函数

5.1 传统的聚合函数用法

SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor c = db.rawQuery("select count(1) from news", null);  
if (c != null && c.moveToFirst()) {  
    int count = c.getInt(0);  
    Log.d("TAG", "result is " + count);  
}  
c.close(); 

可以看到,在rawQuery()方法中我们指定了一个聚合查询语句,其中count(1)就是用于去统计一共有多少行的。当然这里并不一定要用count(1),使用count(*)或者count(主键)都可以。然后rawQuery()方法返回的是一个Cursor对象,我们从这个Cursor当中取出第一行第一列的数据,这也就是统计出的结果了。

SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor c = db.rawQuery("select sum(commentcount) from news", null);  
if (c != null && c.moveToFirst()) {  
    int count = c.getInt(0);  
    Log.d("TAG", "result is " + count);  
}  
c.close();  

我们发现,代码基本是非常相似的,只不过查询语句当中count()函数替换成了sum()函数。当然了,sum()函数要求传入一个指定的列名,表示我们要汇总这一列的总合,因此这里我们传入了commentcount这一列。

5.2 使用LitePal的聚合函数

统计函数:count()

int result = DataSupport.count(News.class); 
int result = DataSupport.where("commentcount = ?", "0").count(News.class); 

求和函数:sum()

int result = DataSupport.sum(News.class, "commentcount", int.class);  

sum()方法的参数要稍微多一点,我们来一一看下。第一个参数很简单,还是传入的Class,用于指定去统计哪张表当中的数据。第二个参数是列名,表示我们希望对哪一个列中的数据进行求合。第三个参数用于指定结果的类型,这里我们指定成int型,因此返回结果也是int型。

平均数:average()

double result = DataSupport.average(News.class, "commentcount"); 

其中average()方法接收两个参数,第一个参数不用说,仍然是Class。第二个参数用于指定列名的,表示我们想要统计哪一列的平均数。需要注意的是,这里返回值的类型是double型,因为平均数基本上都是会带有小数的,用double类型可以最大程序保留小数位的精度。

最大值:max()

int result = DataSupport.max(News.class, "commentcount", int.class); 

可以看到,max()方法接收三个参数,第一个参数同样还是Class,用于指定去统计哪张表当中的数据。第二个参数是列名,表示我们希望统计哪个列中的最大值。第三个参数用于指定结果的类型,根据实际情况来选择传入哪种类型就行了。

最小值:min()

int result = DataSupport.min(News.class, "commentcount", int.class); 
上一篇 下一篇

猜你喜欢

热点阅读