2017/3/12 22:19:08
公司做的一个需要大量本地数据操作的项目,并且需要对数据库进行加密处理。这里有两个点:
一、大量的数据库相关操作,so,我打算使用数据库操作框架—-LitePal;
二、 数据库加密,对于数据库加密,我打算使用SQLCipher。
好了,需求的问题说完了,就开始实际操作了。
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的使用,前面列举的郭霖的blog已经说的很详细了,而且有原生和litepal的使用对比。但是这里我还是要记录一下,因为我只是想使用LitePal,原生的都知道。所以为了直观的查看LitePal的使用,我将郭神blog里面关于LitePal的使用摘抄出来。
1. 引入Jar包或源码
导入AS中,直接在build.gradle文件:
compile 'org.litepal.android:core:1.5.1'
不过为了后面的数据库加密,我使用的是导入源码的方式
2.配置litepal.xml
通过反射建表,依据的就是这个配置文件,放在assets资源目录下
内容如下:
用于设定数据库的名字,
用于设定数据库的版本号,
用于设定所有的映射模型
3.配置LitePalApplication
就是把自己项目中的Application 继承LitePalApplication,当然别忘了在清单文件中设置Application。
public class MyApplication extends LitePalApplication {
...
}4.使用模型(Model)建表
每一张表都应该对应一个模型(Model)
例如下面两张图,我在代码中创建一个model,注意要继承DataSupport,在数据库中映射出表结构:model中的属性就对应的是数据表中的列名
有时候数据库会进行一些更新,比如新增一个表或者某个表里面新增一个字段
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;
// 自动生成get、set方法
}
2、多对一的表关联
比如,Comment和News是多对一的关系,那么应该怎么写呢?就是在News类中放多个Comment , 在Comment放一个News。因为一条评论只能对应一个新闻,但是一个新闻会有多个评论。这些关系只需要结合实际思考一下便知。
public class News {
...
private Introduction introduction;
private List<Comment> commentList = new ArrayList<Comment>();
// 自动生成get、set方法
}
public class Comment {
...
private News news;
// 自动生成get、set方法
}
3、多对多的表关联
新闻和新闻种类就是多对多的关系,一条新闻可能属于多个种类,比如它属于国内新闻同时属于教育新闻;而一个新闻种类更是可以包含多条新闻。
代码和上面一样,对应的类中都有多个对方
public class News {
...
private Introduction introduction;
private List<Comment> commentList = new ArrayList<Comment>();
private List<Category> categoryList = new ArrayList<Category>();
// 自动生成get、set方法
}
public class Category {
...
private List<News> newsList = new ArrayList<News>();
// 自动生成get、set方法
}
到关键地方了,首先是创建的实体类都要继承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修改数据有两种方式,和一个需要注意的点。
- 把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);
- 把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 中 delete()方法有int的返回值,表示被删除的记录数。
删除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);
一个对象如果save过了之后,那就是持久化的了。除了调用save()方法之外,通过DataSupport中提供的查询方法从数据库中查出来的对象也是经过持久化的。
LitePal 中还提供了一个方法来判断对象是否已经持久化
DataSupport类中提供了一个isSaved()方法,这个方法返回true就表示该对象是经过持久化的,返回false则表示该对象未经过持久化。
News news;
...
if (news.isSaved()) {
news.delete();
}
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的地方,就必须使用原生查询了。
int result = DataSupport.count(News.class);
int result = DataSupport.where("commentcount = ?", "0").count(News.class);
int result = DataSupport.sum(News.class, "commentcount", int.class);
double result = DataSupport.average(News.class, "commentcount");
int result = DataSupport.max(News.class, "commentcount", int.class);
int result = DataSupport.min(News.class, "commentcount", int.class);
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源码中 android.database.Cursor 和 android.database.sqlite.SQLiteDatabase的引用替换成 SQLCipherjar包下的对应类,net.sqlcipher.Cursor 和 net.sqlcipher.database.SQLiteDatabase
将左边中使用到的全部替换。
如果密码psw_key=“”,即密码为空,数据库是可以打开的。打开后可以查看表,和表中的数据。
如果密码psw_key = “16688238fdd8ce59*”,不为空的话,数据库会加密,使用数据库软件是打不开的
修改代码,运行后导出数据库文件,打开后截图如下:
无法打开。
注意先后顺序。
ok,到这里LitePal结合SQLCipher实现DB数据库操作和加密就写完了,不管写的清楚不清楚都是记录自己工作的工具。