LitePal结合SQLCipher实现DB数据库操作和加密

2017/3/12 22:19:08


项目需求

公司做的一个需要大量本地数据操作的项目,并且需要对数据库进行加密处理。这里有两个点:

一、大量的数据库相关操作,so,我打算使用数据库操作框架—-LitePal;

二、 数据库加密,对于数据库加密,我打算使用SQLCipher。

好了,需求的问题说完了,就开始实际操作了。


LitePal 简介

LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发时最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成各种建表、増删改查的操作。并且LitePal很“轻”,jar包只有100k不到,而且近乎零配置,这一点和hibernate这类的框架有很大区别。目前LitePal的源码已经托管到了GitHub上,地址是 https://github.com/LitePalFramework/LitePal 。

LitePal是由郭霖开发并开源到GitHub上的一款Android数据库框架,以上引用出自郭霖blog上的介绍。
关于LitePal的具体使用可以参考郭霖的这几篇blog:

Android数据库高手秘籍(一)——SQLite命令

Android数据库高手秘籍(二)——创建表和LitePal的基本用法

Android数据库高手秘籍(三)——使用LitePal升级表

Android数据库高手秘籍(四)——使用LitePal建立表关联

Android数据库高手秘籍(五)——LitePal的存储操作

Android数据库高手秘籍(六)——LitePal的修改和删除操作

Android数据库高手秘籍(七)——体验LitePal的查询艺术

Android数据库高手秘籍(八)——使用LitePal的聚合函数

LitePal使用

关于LitePal的使用,前面列举的郭霖的blog已经说的很详细了,而且有原生和litepal的使用对比。但是这里我还是要记录一下,因为我只是想使用LitePal,原生的都知道。所以为了直观的查看LitePal的使用,我将郭神blog里面关于LitePal的使用摘抄出来。

一. 创建表,相关配置

1. 引入Jar包或源码

导入AS中,直接在build.gradle文件:
compile 'org.litepal.android:core:1.5.1'
不过为了后面的数据库加密,我使用的是导入源码的方式
LitePal结合SQLCipher实现DB数据库操作和加密_第1张图片

2.配置litepal.xml

通过反射建表,依据的就是这个配置文件,放在assets资源目录下

LitePal结合SQLCipher实现DB数据库操作和加密_第2张图片

内容如下:

LitePal结合SQLCipher实现DB数据库操作和加密_第3张图片

用于设定数据库的名字,用于设定数据库的版本号,用于设定所有的映射模型

3.配置LitePalApplication

就是把自己项目中的Application 继承LitePalApplication,当然别忘了在清单文件中设置Application。
public class MyApplication extends LitePalApplication {
...
}

4.使用模型(Model)建表

每一张表都应该对应一个模型(Model)
例如下面两张图,我在代码中创建一个model,注意要继承DataSupport,在数据库中映射出表结构:

LitePal结合SQLCipher实现DB数据库操作和加密_第4张图片

model中的属性就对应的是数据表中的列名

LitePal结合SQLCipher实现DB数据库操作和加密_第5张图片

二. 升级表和数据库

有时候数据库会进行一些更新,比如新增一个表或者某个表里面新增一个字段

1.新增数据表

操作非常简单,只需要新建一个对应的model然后在litepal.xml中将数据库版本+1即可。

2.新增数据表属性

同样的操作,在model里面新加一个属性,然后在litepal.xml中将数据库版本+1即可。

3.删除数据表属性

一般不进行这样的操作,不用的属性直接闲置即可。不过在litePal中也是支持的,只需要在model里面去除不需要的属性,然后在litepal.xml中将数据库版本+1即可。

4.删除数据表

操作同3,去掉对应的model,然后在litepal.xml中将数据库版本+1即可。

三. 建立表关联

使用原生SQL见表的时候,我们需要考虑表的外键(一张表有可能不止一个外键),还要建立中间表,确实比较麻烦,在这个项目中没有使用到表的关联,所以就摘抄一下郭霖blog上对表关联的相关介绍。

1、一对一的表关联

比如,在News类中可以得到一个对应的Introduction的实例,那么它们之间就是一对一关系了。

public class News {  
    ...  
    private Introduction introduction;  

    // 自动生成getset方法  
}  

2、多对一的表关联
比如,Comment和News是多对一的关系,那么应该怎么写呢?就是在News类中放多个Comment , 在Comment放一个News。因为一条评论只能对应一个新闻,但是一个新闻会有多个评论。这些关系只需要结合实际思考一下便知。

public class News {  
    ...  
    private Introduction introduction;  

    private List<Comment> commentList = new ArrayList<Comment>();  

    // 自动生成getset方法  
} 
public class Comment {  
    ...  
    private News news;  

    // 自动生成getset方法   
}  

3、多对多的表关联
新闻和新闻种类就是多对多的关系,一条新闻可能属于多个种类,比如它属于国内新闻同时属于教育新闻;而一个新闻种类更是可以包含多条新闻。
代码和上面一样,对应的类中都有多个对方

public class News {  
    ...  
    private Introduction introduction;  

    private List<Comment> commentList = new ArrayList<Comment>();  

    private List<Category> categoryList = new ArrayList<Category>();  

    // 自动生成getset方法  
} 
public class Category {  
    ...  
    private List<News> newsList = new ArrayList<News>();  

    // 自动生成getset方法  
}  

四. 数据存储

到关键地方了,首先是创建的实体类都要继承DataSupport,并生成get、set方法。这时存储数据的时候就异常简单了,以项目中存储练习记录为例,代码如下:

UserExercise userExercise = new UserExercise();
userExercise.setUser_id(userID);
userExercise.setJob_id(beanDb.getJob_id());
userExercise.setBegin_time(new Date(System.currentTimeMillis()));
userExercise.save(); 

save方法提供了一个boolean类型的返回值,用以判断是否存储成功。
同时LiitePal还提供很多不同的存储方法,如:

1.saveAll()—用来存储集合集合数据。

2.saveOrUpdate(“name=?”,name)—存储或更新,该条数据没有就保存,有就更新

3.每个方法还提供了对应的异步方法,saveAsync() 、 saveOrUpdateAsync()

五. LitePal修改数据

使用LitePal修改数据有两种方式,和一个需要注意的点。

  • 1.使用ContentValues 和 DataSupport.update()
  • 把news表中id为2的记录的标题改成“今日iPhone6发布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6发布");  
DataSupport.update(News.class, values, 2);  
  • 把news表中标题为“今日iPhone6发布”的所有新闻的标题改成“今日iPhone6 Plus发布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus发布");  
DataSupport.updateAll(News.class, values, "title = ?", "今日iPhone6发布");  
  • 把news表中标题为“今日iPhone6发布”且评论数量大于0的所有新闻的标题改成“今日iPhone6 Plus发布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus发布");  
DataSupport.updateAll(News.class, values, "title = ? and commentcount > ?", "今日iPhone6发布", "0");  
  • 把news表中所有新闻的标题都改成“今日iPhone6发布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus发布");  
DataSupport.updateAll(News.class, values);  
  • 2.使用model中的实体类直接修改
  • 把news表中id为2的记录的标题改成“今日iPhone6发布”
News updateNews = new News();  
updateNews.setTitle("今日iPhone6发布");  
updateNews.update(2);  
  • 把news表中标题为“今日iPhone6发布”且评论数量大于0的所有新闻的标题改成“今日iPhone6 Plus发布”
News updateNews = new News();  
updateNews.setTitle("今日iPhone6发布");  
updateNews.updateAll("title = ? and commentcount > ?", "今日iPhone6发布", "0");  
  • 3.修改某条记录为默认值的时候需要注意

    郭神的blog上是这样说的:
    如果我们想把某一条数据修改成默认值,比如说将评论数修改成0,只是调用updateNews.setCommentCount(0)这样是不能修改成功的,因为即使不调用这行代码,commentCount的值也默认是0。所以如果想要将某一列的数据修改成默认值的话,还需要借助setToDefault()方法。

  • 比如说我们想要把news表中所有新闻的评论数清零

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

六. LitePal删除数据

LitePal 中 delete()方法有int的返回值,表示被删除的记录数。

  • 1.使用 DataSupport.delete()

删除news表中id为2的记录

DataSupport.delete(News.class, 2);  

批量删除,比如把news表中标题为“今日iPhone6发布”且评论数等于0的所有新闻都删除掉

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

把news表中所有的数据全部删除掉

DataSupport.deleteAll(News.class); 
  • 2.使用model中的实体类的delete方法,但是需要持久化
    对象持久化,郭神blog中的解释是:

一个对象如果save过了之后,那就是持久化的了。除了调用save()方法之外,通过DataSupport中提供的查询方法从数据库中查出来的对象也是经过持久化的。

LitePal 中还提供了一个方法来判断对象是否已经持久化

DataSupport类中提供了一个isSaved()方法,这个方法返回true就表示该对象是经过持久化的,返回false则表示该对象未经过持久化。

News news;  
...  
if (news.isSaved()) {  
    news.delete();  
} 

七. LitePal查询数据

  • 简单查询
News news = DataSupport.find(News.class, 1);
News firstNews = DataSupport.findFirst(News.class); 
News lastNews = DataSupport.findLast(News.class);  
List newsList = DataSupport.findAll(News.class, 1, 3, 5, 7);  
long[] ids = new long[] { 1, 3, 5, 7 };  
List newsList = DataSupport.findAll(News.class, ids);  
List allNews = DataSupport.findAll(News.class);  
  • 连缀查询
List newsList = DataSupport.where("commentcount > ? and userid = ?", "0","21").find(News.class);  

只查询指定的两列

List newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0").find(News.class);  

按照指定列的倒叙查询,asc表示正序排序,desc表示倒序排序

List newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").find(News.class);  

只查前面的10条数据

List newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).find(News.class);  

分页查询,即使用偏移量查询,查询第11到第20条新闻

List newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).offset(10)  
        .find(News.class);  
  • 激进查询

查询news表中id为1的新闻,并且把这条新闻所对应的评论也一起查询出来,不过郭神并不推荐这样查询

News news = DataSupport.find(News.class, 1, true);  
List commentList = news.getCommentList();  
  • 原生查询
Cursor cursor = DataSupport.findBySQL("select * from news where commentcount>?", "0");  

有些时候,LitePal并不能很好的解决查询的需求,比如需要使用到group by的地方,就必须使用原生查询了。

八. LitePal的聚合函数

  • count()
    count()方法主要是用于统计行数的
int result = DataSupport.count(News.class);  
int result = DataSupport.where("commentcount = ?", "0").count(News.class); 
  • sum()
    sum()方法主要是用于对结果进行求合的,sum()方法只能对具有运算能力的列进行求合
int result = DataSupport.sum(News.class, "commentcount", int.class);  
  • average()
    average()方法主要是用于统计平均数的,average()方法也只能对具有运算能力的列进行求平均值
double result = DataSupport.average(News.class, "commentcount");  
  • max()
    max()方法主要用于求出某个列中最大的数值,max()方法也只能对具有运算能力的列进行求最大值的
int result = DataSupport.max(News.class, "commentcount", int.class);  
  • min()
    min()方法主要用于求出某个列中最小的数值,min()方法也只能对具有运算能力的列进行求最大值的
int result = DataSupport.min(News.class, "commentcount", int.class); 

SQLCipher加密数据库

Github地址:https://github.com/sqlcipher/android-database-sqlcipher

使用SQLCipher进行数据库加密,需要重写SqliteOpenHelper类,把继承类改为net.sqlcipher.database而不是谷歌官方的sqlite包。

参考blog:

http://www.cnblogs.com/begin1949/p/4985883.html

http://www.cnblogs.com/begin1949/p/4985900.html

重写SqliteOpenHelper类:

import android.content.Context;

import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public static final String CREATE_TABLE = "create table Book(name text,pages integer)";

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

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
    }
}

然后再代码中使用的时候,就需要输入密码,才能对数据库进行操作了。

   MyDatabaseHelper dbHelper = new MyDatabaseHelper(this, "demo.db", null, 1);
   SQLiteDatabase db = dbHelper.getWritableDatabase("psw");

LitePal 结合 SQLCipher使用

1.导入LitePal源码作为Module

2.在LitePal的Module中引入SQLCipher的jar包

LitePal结合SQLCipher实现DB数据库操作和加密_第6张图片

3.替换LitePal源码中对android.database.*包的引用

主要是将LitePal源码中 android.database.Cursor 和 android.database.sqlite.SQLiteDatabase的引用替换成 SQLCipherjar包下的对应类,net.sqlcipher.Cursor 和 net.sqlcipher.database.SQLiteDatabase

LitePal结合SQLCipher实现DB数据库操作和加密_第7张图片

将左边中使用到的全部替换。

4.在Connector类中设置密码

LitePal结合SQLCipher实现DB数据库操作和加密_第8张图片

如果密码psw_key=“”,即密码为空,数据库是可以打开的。打开后可以查看表,和表中的数据。

LitePal结合SQLCipher实现DB数据库操作和加密_第9张图片

如果密码psw_key = “16688238fdd8ce59*”,不为空的话,数据库会加密,使用数据库软件是打不开的

LitePal结合SQLCipher实现DB数据库操作和加密_第10张图片

修改代码,运行后导出数据库文件,打开后截图如下:

LitePal结合SQLCipher实现DB数据库操作和加密_第11张图片

无法打开。

5.MyApplication中的初始化

LitePal结合SQLCipher实现DB数据库操作和加密_第12张图片

注意先后顺序。

ok,到这里LitePal结合SQLCipher实现DB数据库操作和加密就写完了,不管写的清楚不清楚都是记录自己工作的工具。

你可能感兴趣的:(android)