一、SQLite
1、SQLite介绍
1.1、简介
SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了样比起Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。
ACID:指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
1.2、特点
- 轻量级
它是进程内的数据库引擎,只需要带上它的一个动态库,就可以享受它的全部功能 - 不需要"安装"
- 单一文件
- 跨平台/可移植性
- 弱类型的字段
同一列中的数据可以是不同类型 - 开源
1.3、SQLite数据类型
- NULL: 这个值为空值
- VARCHAR(n):长度不固定且其最大长度为 n 的字串,n不能超过 4000。
- CHAR(n):长度固定为n的字串,n不能超过 254。
- INTEGER: 值被标识为整数,依据值的大小可以依次被存储为1,2,3,4,5,6,7,8
- REAL: 所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号
- TEXT: 值为文本字符串,使用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE).
- BLOB: 值是BLOB数据块,以输入的数据格式进行存储。如何输入就如何存储,不改变格式。
- DATE :包含了 年份、月份、日期。
- TIME: 包含了 小时、分钟、秒。
2、SQLiteDatabase的介绍
Android提供了创建和使用SQLite数据库的API。SQLiteDatabase代表一个数据库对象,提供了操作数据库的一些方法。在Android的SDK目录下有sqlite3工具,我们可以利用它创建数据库、创建表和执行一些SQL语句。下面是SQLiteDatabase的常用方法。
-
openOrCreateDatabase(String path,SQLiteDatabase.CursorFactory factory)
打开或创建path文件代表的SQLite数据库,factory一般赋值为null,使用默认的工厂。存在则打开,不存在则创建一个数据库;创建成功则返回一个SQLiteDatabase对象,否则抛出异常FileNotFoundException -
execSQL(String sql)
执行一条SQL语句 -
execSQL(String sql, Object[] bindArgs)
执行一条SQL语句,防止注入攻击 -
rawQuery(String sql, String[] selectionArgs) Cursor
执行查询语句 -
close()
关闭数据库 -
insert(String table,String nullColumnHack,ContentValues values) long
插入一条记录。nullColumnHack指定强行插入null值的数据列名,ContentValues 类似于Map;返回插入的行号,若发生错误返回-1 -
delete(String table,String whereClause,String[] whereArgs) int
删除一条记录 -
query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy[, String limit]) Cursor
查询一条记录。limit参数控制最多查询几条记录(用于控制分页的参数);distinct参数控制是否去除重复的值 -
update(String table,ContentValues values,String whereClause,String[] whereArgs) int
修改记录。whereArgs子句传入的参数 beginTransaction() void
-
endTransaction() void
只有当执行了setTransactionSuccessful()时,才会进行事物的提交,否则回滚 -
inTransaction() boolean
是否处于事务之中
2.1、Cursor
类似于ResultSet
-
move(int offset) void
向上或向下移动记录指针 moveToFirst() boolean
moveToLast() boolean
moveToNext() boolean
moveToPrevious() boolean
moveToPosition(int position) boolean
close() void
getCount() int
getXxx(int index) xxx
getColumnIndex(String columnName) int
2.2、ContentValues
类似于Map
keySet() Set<String>
put(String key,int value) void
get(String key) Object
getAsInteger(String key) Integer
3、SQLiteOpenHelper
3.1、概述
- 管理数据库的一个工具类,用于管理数据库的创建和版本更新
- 实际项目中,很少使用SQLiteDatabase的方法来打开数据库,通常会继承SQLiteOpenHelper开发子类,然后调用
getWritableDatabase()
、getReadableDatabase()
打开数据库。
3.2、方法
-
SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)
name:数据库名;CursorFactory置为null,version:数据库版本 -
onCreate(SQLiteDatabase db) void
第一次创建数据库的时候回调该方法,通常:新建表操作 -
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) void
当数据库版本更新时回调该方法,通常:删除表,再新建表 -
getReadableDatabase() SQLiteDatabase
先以读写的方式打开数据库,若磁盘满了,再以读的方式打开对应的SQLiteDatabase对象,若无则创建,此时才进行创建 -
getWritableDatabase() SQLiteDatabase
以读写的方式打开数据库对应的SQLiteDatabase对象,若无则创建 -
close() void
//关闭所有打开的SQLiteDatabase
4、实例
创建数据库
StuDBHelper.java
public class StuDBHelper extends SQLiteOpenHelper {
private static final String TAG = "TestSQLite";
public static final int VERSION = 1;
//必须要有构造函数
public StuDBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
int version) {
super(context, name, factory, version);
}
// 当第一次创建数据库的时候,调用该方法
public void onCreate(SQLiteDatabase db) {
String sql = "create table stu_table(id int,sname varchar(20),sage int,ssex varchar(10))";
//输出创建数据库的日志信息
Log.i(TAG, "create Database------------->");
//execSQL函数用于执行SQL语句
db.execSQL(sql);
}
//当更新数据库的时候执行该方法
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//输出更新数据库的日志信息
Log.i(TAG, "update Database------------->");
}
}
UI界面比较简单,就不给出代码了。如下图:
MainActivity.java
public class MainActivity extends Activity {
//省略部分代码
... ...
//为按钮注册监听的方法
private void setListener() {
createBtn.setOnClickListener(new CreateListener());
updateBtn.setOnClickListener(new UpdateListener());
insertBtn.setOnClickListener(new InsertListener());
ModifyBtn.setOnClickListener(new ModifyListener());
queryBtn.setOnClickListener(new QueryListener());
deleteBtn.setOnClickListener(new DeleteListener());
}
//创建数据库的方法
class CreateListener implements View.OnClickListener {
@Override
public void onClick(View v) {
//创建StuDBHelper对象
StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
//得到一个可读的SQLiteDatabase对象
SQLiteDatabase db = dbHelper.getReadableDatabase();
}
}
//更新数据库的方法
class UpdateListener implements View.OnClickListener {
@Override
public void onClick(View v) {
// 数据库版本的更新,由原来的1变为2
StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 2);
SQLiteDatabase db = dbHelper.getReadableDatabase();
}
}
//插入数据的方法
class InsertListener implements View.OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
//得到一个可写的数据库
SQLiteDatabase db = dbHelper.getWritableDatabase();
//方法1
//生成ContentValues对象 //key:列名,value:想插入的值
ContentValues cv = new ContentValues();
//往ContentValues对象存放数据,键-值对模式
cv.put("id", 1);
cv.put("sname", "xiaoming");
cv.put("sage", 21);
cv.put("ssex", "male");
//调用insert方法,将数据插入数据库
db.insert("stu_table", null, cv);
//方法2,效率更高点
String sb = "INSERT INTO stu_table(id,sname,sage,ssex) " +
" VALUES( ?, ?, ?, ?)";
SQLiteStatement statement = db.compileStatement(sb);//
statement.bindLong(1, 2);
statement.bindString(2, "xyh");
statement.bindLong(3, 22);
statement.bindString(4, "male");
statement.executeInsert();
//关闭数据库
db.close();
}
}
//查询数据的方法
class QueryListener implements View.OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
//得到一个可写的数据库
SQLiteDatabase db = dbHelper.getReadableDatabase();
//参数1:表名
//参数2:要想显示的列
//参数3:where子句
//参数4:where子句对应的条件值
//参数5:分组方式
//参数6:having条件
//参数7:排序方式
Cursor cursor = db.query("stu_table", new String[]{"id", "sname",
"sage", "ssex"}, "id=?", new String[]{"1"}, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("sname"));
String age = cursor.getString(cursor.getColumnIndex("sage"));
String sex = cursor.getString(cursor.getColumnIndex("ssex"));
System.out.println("query-->" + "姓名:" + name + " " + "年龄:"
+ age + " " + "性别:" + sex);
}
//关闭数据库
db.close();
}
}
//修改数据的方法
class ModifyListener implements View.OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
//得到一个可写的数据库
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("sage", "23");
//where 子句 "?"是占位符号,对应后面的"1",
String whereClause = "id=?";
String[] whereArgs = {String.valueOf(1)};
//参数1 是要更新的表名
//参数2 是一个ContentValeus对象
//参数3 是where子句
db.update("stu_table", cv, whereClause, whereArgs);
}
}
//删除数据的方法
class DeleteListener implements View.OnClickListener {
@Override
public void onClick(View v) {
StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
//得到一个可写的数据库
SQLiteDatabase db = dbHelper.getReadableDatabase();
String whereClauses = "id=?";
String[] whereArgs = {String.valueOf(2)};
//调用delete方法,删除数据
db.delete("stu_table", whereClauses, whereArgs);
}
}
}
二、SQLCipher
Sqlite数据库默认存放位置data/data/pakage/database目录下,对于已经ROOT的手机来说的没有任何安全性可以,一旦被利用将会导致数据库数据的泄漏,所以我们该如何避免这种事情的发生呢?我们尝试这对数据库进行加密。
1、简介
SQLCipher是一个在SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能,如果我们在项目中使用它来存储数据的话,就可以大大提高程序的安全性。
优势:
加密性能高、开销小,只要5-15%的开销用于加密
完全做到数据库100%加密
采用良好的加密方式(CBC加密模式)
使用方便,做到应用级别加密
采用OpenSSL加密库提供的算法
2、使用方法
- 导入包或者添加依赖
compile group: 'net.zetetic', name: 'android-database-sqlcipher', version: '3.5.9'
- 创建一个SQLiteOpenHelper 注意接下来所以有关Sqlite相关类全部引用net.sqlcipher.database的类
注意:SQLiteDatabase.loadLibs(context)这个千万别忘记调用
/**
* Created by xiang on 2018/1/26.
*/
import android.content.Context;
import android.util.Log;
import net.sqlcipher.SQLException;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteOpenHelper;
public class DBCipherHelper extends SQLiteOpenHelper {
private static final String TAG = "DatabaseHelper";
private static final String DB_NAME = "test_cipher_db";//数据库名字
public static final String DB_PWD = "whoislcj";//数据库密码
public static String TABLE_NAME = "person";// 表名
public static String FIELD_ID = "id";// 列名
public static String FIELD_NAME = "name";// 列名
private static final int DB_VERSION = 1; // 数据库版本
public DBCipherHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
//不可忽略的 进行so库加载,使用前调用即可
SQLiteDatabase.loadLibs(context);
}
public DBCipherHelper(Context context) {
this(context, DB_NAME, null, DB_VERSION);
}
/**
* 创建数据库
*/
@Override
public void onCreate(SQLiteDatabase db) {
//创建表
createTable(db);
}
private void createTable(SQLiteDatabase db) {
String sql = "CREATE TABLE " + TABLE_NAME + "(" + FIELD_ID
+ " integer primary key autoincrement , "
+ FIELD_NAME + " text not null);";
try {
db.execSQL(sql);
} catch (SQLException e) {
Log.e(TAG, "onCreate " + TABLE_NAME + "Error" + e.toString());
return;
}
}
/**
* 数据库升级
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
- 接下来的步骤和原来的完全一样,唯一区别
//获取写数据库
SQLiteDatabase db = dbHelper.getWritableDatabase(DBCipherHelper.DB_PWD);//用于加密的秘钥
//获取可读数据库
SQLiteDatabase db = dbHelper.getReadableDatabase(DBCipherHelper.DB_PWD);
三、GreenDAO
1、简介
greenDAO是一种Android数据库ORM(object/relational mapping - 对象关系映射)框架,是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。与OrmLite、ActiveOrm、LitePal等数据库相比,单位时间内可以插入、更新和查询更多的数据,而且提供了大量的灵活通用接口。
设计的主要目标:一个精简的库、性能最大化、内存开销最小化、易于使用的 APIs、对 Android 进行高度优化。
设计的主要特点:
- greenDAO 性能远远高于同类的 ORMLite,具体测试结果可见官网
- greenDAO 支持 protocol buffer(protobuf) 协议数据的直接存储,如果你通过 protobuf 协议与服务器交互,将不需要任何的映射。
- 与 ORMLite 等使用注解方式的 ORM 框架不同,greenDAO 使用「Code generation」的方式,这也是其性能能大幅提升的原因。
- 使用 SQL 语句进行查询容易出错,而且错误比较难以发现,使用 greenDAO 的话可以在编译阶段就发现错误。
2、如何开始
2.1、添加依赖
在project下的build.gradle中添加
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'//添加green的gradle插件
}
在app下的build.gradle中添加
apply plugin: 'com.android.application'
//使用greendao
apply plugin: 'org.greenrobot.greendao'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
defaultConfig {
applicationId "com.handsome.didi"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
//greendao配置
greendao {
//版本号,升级时可配置
schemaVersion 1
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
//greendao依赖
compile 'org.greenrobot:greendao:3.2.0'
}
2.2、创建Bean对象(表名和字段名)
@Entity
public class Shop {
//表示为购物车列表
public static final int TYPE_CART = 0x01;
//表示为收藏列表
public static final int TYPE_LOVE = 0x02;
//不能用int
@Id(autoincrement = true)
private Long id;
//商品名称
@Unique
private String name;
//商品价格
@Property(nameInDb = "price")
private String price;
//已售数量
private int sell_num;
//图标url
private String image_url;
//商家地址
private String address;
//商品列表类型
private int type;
}
创建完成之后,需要build gradle来完成我们的代码自动生成。自动生成的代码有
Bean实体的构造方法和get、set方法、DaoMaster、DaoSession、ShopDAO类
执行gradle构建之后的Shop.java
@Entity
public class Shop {
//表示为购物车列表
public static final int TYPE_CART = 0x01;
//表示为收藏列表
public static final int TYPE_LOVE = 0x02;
//不能用int,不指定会递增,指定了就用指定的值
@Id(autoincrement = true)
private Long id;
//商品名称
@Unique
private String name;
//商品价格
@Property(nameInDb = "price")
private String price;
//已售数量
private int sell_num;
//图标url
private String image_url;
//商家地址
private String address;
//商品列表类型
private int type;
@Generated(hash = 1304458862)
public Shop(Long id, String name, String price, int sell_num, String image_url,
String address, int type) {
this.id = id;
this.name = name;
this.price = price;
this.sell_num = sell_num;
this.image_url = image_url;
this.address = address;
this.type = type;
}
@Generated(hash = 633476670)
public Shop() {
}
//省略getter和setter
... ...
}
对Bean对象的注释进行解释
- @Entity:表明这个实体类会在数据库中生成一个与之相对应的表。
- @Id:对象的Id,使用Long类型作为EntityId,否则会报错。(autoincrement = true)表示主键会自增,如果false就会使用旧值
- @Property:可以自定义字段名,注意外键不能使用该属性,nameInDb属性表示数据库中的字段名
- @NotNull:属性不能为空
- @Transient:使用该注释的属性不会被存入数据库的字段中
- @Unique:该属性值必须在数据库中是唯一值
- @Generated:编译后自动生成的构造函数、方法等的注释,提示构造函数、方法等不能被修改
2.3、创建数据库(数据库名)
public class BaseApplication extends Application {
private static DaoSession daoSession;
@Override
public void onCreate() {
super.onCreate();
//配置数据库
setupDatabase();
}
/**
* 配置数据库
*/
private void setupDatabase() {
//此时并不会创建shop.db,底层就是SQLiteOpenHelper的构造方法
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "shop.db", null);
//获取可写数据库
SQLiteDatabase db = helper.getWritableDatabase();
//获取数据库对象
DaoMaster daoMaster = new DaoMaster(db);
//获取Dao对象管理者
daoSession = daoMaster.newSession();
}
public static DaoSession getDaoInstant() {
return daoSession;
}
}
可以发现,GreenDao已经将我们的数据库创建缩成几句话,代码会自动将Bean对象创建成表,不再是传统的手写SQL语句。这里的数据库创建只需要在Application中执行一次即可(也可以在别的地方),这里对几个类进行解释
- DevOpenHelper:创建SQLite数据库的SQLiteOpenHelper的具体实现
- DaoMaster:GreenDao的顶级对象,作为数据库对象、用于创建表和删除表
- DaoSession:管理所有的Dao对象,Dao对象中存在着增删改查等API
2.4、数据库的增删改查
public class LoveDao {
/**
* 添加数据,如果有重复则覆盖
*
* @param shop
*/
public static void insertLove(Shop shop) {
BaseApplication.getDaoInstant().getShopDao().insertOrReplace(shop);
}
/**
* 删除数据
*
* @param id
*/
public static void deleteLove(long id) {
BaseApplication.getDaoInstant().getShopDao().deleteByKey(id);
}
/**
* 更新数据
*
* @param shop
*/
public static void updateLove(Shop shop) {
BaseApplication.getDaoInstant().getShopDao().update(shop);
}
/**
* 查询条件为Type=TYPE_LOVE的数据
*
* @return
*/
public static List<Shop> queryLove() {
return BaseApplication.getDaoInstant().getShopDao()
.queryBuilder().where(ShopDao.Properties.Type.eq(Shop.TYPE_LOVE)).list();
}
/**
* 查询全部数据
*/
public static List<Shop> queryAll() {
return BaseApplication.getDaoInstant().getShopDao().loadAll();
}
}
GreenDao的封装之后短小精悍,语义明朗。下面对GreenDao中Dao对象其他API的介绍
AbstractDao方法:
- 增加单个数据
insert(T entity) long
insertOrReplace(T entity) long
- 增加多个数据
insertInTx(T... entities) void
insertOrReplaceInTx(T... entities) void
- 删除单个数据
delete(T entity) void
- 删除多个数据
deleteInTx(T... entities) void
- 删除数据ByKey(key就是@Id字段)
deleteByKey(K key) void
- 修改单个数据
update(T entity) void
- 修改多个数据
updateInTx(T... entities) void
- 查询全部
loadAll() List<T>
queryBuilder().list() List<T>
queryBuilder()返回的是QueryBuilder<T>
QueryBuilder<T> 方法:
- 查询附加单个条件或多个条件
where(WhereCondition cond, WhereCondition... condMore) QueryBuilder<T>
cond:如ShopDao.Properties.Id = 12
whereOr(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) QueryBuilder<T>
- 查询附加排序
orderDesc(Property... properties) QueryBuilder<T>
properties:如ShopDao.Properties.Id
orderAsc(Property... properties) QueryBuilder<T>
- 查询限制当页个数
limit(int limit) QueryBuilder<T>
- 查询总个数
count() long
- 出口
list() List<T>
Property 方法:
eq(Object value) WhereCondition
notEq(Object value) WhereCondition
like(String value) WhereCondition
-
between(Object value1, Object value2) WhereCondition
... ...
2.4、基于Sqlcipher和GreenDao的数据库加密
需要下载greendao的源码,替换其SQLiteDatabase等对象,具体参考:基于Sqlcipher和GreenDao的数据库加密
四、WCDB
WCDB 是腾讯出品的一个高效、完整、易用的移动数据库框架,基于SQLCipher,支持iOS, macOS和Android,还内建了Repair Kit用于修复损坏的数据库以及内置的防SQL注入。
项目地址:https://github.com/Tencent/wcdb
使用方法:
dependencies {
compile 'com.tencent.wcdb:wcdb-android:1.0.2'
}
和在 android 上使用 SQLite 一样,先创建一个 WcdbHelper 继承 SQLiteOpenHelper 类,注意包名这里是 com.tencent.wcdb.database 。
class WcdbHelper extends SQLiteOpenHelper{
public WcdbHelper(Context context, String name, byte[] password,
SQLiteDatabase.CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
super(context, name, password, factory, version, errorHandler);
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
由于 wcdb 是基于 SQLCipher,所以我们可以为数据库设置一个密码,我们在构造函数里指定密码即可,wcdb 允许 byte[] 作为密码, SQLCipher 使用 String 作为密码。
File cdb = new File(getExternalFilesDir("db"), "hello.wcdb");
WcdbHelper wcdbHelper = new WcdbHelper(
getApplicationContext(),
cdb.getAbsolutePath(),
"password".getBytes(), null, 1, null
);
接下来在这张表里创建一条数据:
String sql = "INSERT INTO \"main\".\"address_book\" (\"name\", \"age\", \"address\") VALUES (?, ?, ?)";
wcdbHelper.getWritableDatabase().execSQL(sql, new String[]{"dummy", "28", "Hubei wuhan"});
这样,在 hello.wcdb 这个数据库中就存在了一条数据,当我们像打开 sqlite 数据库时打开这个 wcdb 数据库时候,提示错误,这是正常的,因为它被加密了,这样无论手机是否 root 或是我们把数据库存放在外置存储中,对于我们 app 的敏感数据来说,他们依然是安全的。
总得来说,如果会使用 sqlite 的话,那么 wcdb 也不在话下,并且迁移成本也很小。唯一的不足是,wcdb 官方提到的 winq 查询在 android 上并不支持,但是官方也说在今后会提供其他方式的 orm 以方便数据库操作。
参考文献
Android 操作SQLite基本用法
Android数据存储之SQLCipher数据库加密
Android应用性能优化之使用SQLiteStatement优化SQLite操作
Android实战——GreenDao3.2的使用,爱不释手
greenDAO 3.2 初探
在Android上使用微信开源数据库框架WCDB