sqlite 支持 5 种数据类型
对于 Root 过的安装手机,可以随意访问 /data/data/
目录下的任意文件,在这里就可以查看到数据库中存储的所有数据。对于一般数据没啥问题,但如果涉及到一些账号密码,或者是聊天内容的时候,程序显然就面临着严重的安全漏洞和隐患了,因此这时候,对数据库进行加密就显得尤为重要。
greendao 支持数据库加密官网:http://greenrobot.org/greendao/documentation/database-encryption/
SQLCipher 官网:https://www.zetetic.net/sqlcipher/
在 Android 上添加 SQLCipher 依赖:https://www.zetetic.net/sqlcipher/sqlcipher-for-android/
在 build.gradle 中的 dependencies 里添加
dependencies {
//数据库加密
implementation 'net.zetetic:android-database-sqlcipher:4.2.0'
}
sqlcipher 在使用上和 android 自带的数据库并没有什么区别,唯一的区别就是打开的时候,需要提供密码。如果密码传 null,则等价于数据库不加密。
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteException;
sqlcipher 是对整个数据库进行加密,open时进行解密,所以应尽量减少打开的次数。
/**
* 初始化并打开数据库
* @param context 上下文
* @param dbName 数据库名称(不用加后缀)
* @param password 加密数据库密码(密码为null表示不加密)
*/
public void open(Context context, String dbName, String password) {
this.context = context;
this.dbName = dbName + ".db";
// 数据库会存放在 /data/data/<包名>/databases/ 目录下面
File dbFile = context.getDatabasePath(this.dbName);
File dir = dbFile.getParentFile();
//如果目录不存在,则递归创建
if (!dir.exists()) dir.mkdirs();
//加载用于加密的so库
SQLiteDatabase.loadLibs(context);
//打开或者创建数据库,通过密码对数据库进行解密,如果password为null,则等价于原生的未加密的数据库
db = SQLiteDatabase.openOrCreateDatabase(dbFile, password, null);
}
/**
* 关闭数据库
*/
public void close() {
//仅在数据库已创建,且已打开的情况下,关闭数据库
if(db != null && db.isOpen()) db.close();
}
/**
* 执行脚本(仅在error非空的时候,返回结果才有效)
*
* @param sql 待执行的SQL脚本
* @return 执行成功返回true,否则返回false
*/
public boolean execSQL(String sql) {
error = null;
try {
//如果有多条相关的语句同时执行,则最好加上事务,以便出错时能回滚到初始状态
// db.beginTransaction();
db.execSQL(sql);
// db.endTransaction();
return true;
} catch (SQLiteException e) {
e.printStackTrace();
error = e.toString();
return false;
}
}
/**
* 查询数据,{"colName":[],"type":[],"value":[[],[]]}}(仅在error非空的时候,返回结果才有效)
*/
public JSONObject queryToJson(String sql) {
error = null;
Cursor cursor = null;
JSONObject jsonObject = new JSONObject();
try {
cursor = db.rawQuery(sql, null);
//判断游标是否为空
//cursor的初始位置是从下标为-1的地方开始,一定要先moveFirst才能使用
if (cursor != null && cursor.moveToFirst()) {
int columnCount = cursor.getColumnCount();
if (columnCount > 0) {
//列名
JSONArray colNames = new JSONArray();
//每一列的类型
JSONArray typeNames = new JSONArray();
//列的值
JSONArray values = new JSONArray();
for (int i = 0; i < columnCount; i++) {
int type = cursor.getType(i);
String name = cursor.getColumnName(i);
colNames.put(name);
switch (type) {
case Cursor.FIELD_TYPE_NULL:
//通常情况下列的类型不为空。但由于SQLite采用的是动态数据类型,会根据存入值自动判断,因此在创建表的时候,字段类型是允许设为null的
typeNames.put("NULL");
break;
case Cursor.FIELD_TYPE_INTEGER:
typeNames.put("INTEGER");
break;
case Cursor.FIELD_TYPE_FLOAT:
typeNames.put("FLOAT");
break;
case Cursor.FIELD_TYPE_STRING:
typeNames.put("STRING");
break;
case Cursor.FIELD_TYPE_BLOB:
typeNames.put("BLOB");
break;
}
}
for (; !cursor.isAfterLast(); cursor.moveToNext()) {
JSONArray array = new JSONArray();
for (int i = 0; i < columnCount; i++) {
int type = cursor.getType(i);
try {
switch (type) {
case Cursor.FIELD_TYPE_NULL:
//当值为空时,走的是这里
array.put(null);
break;
case Cursor.FIELD_TYPE_INTEGER:
array.put(cursor.getInt(i));
break;
case Cursor.FIELD_TYPE_FLOAT:
array.put(cursor.getFloat(i));
break;
case Cursor.FIELD_TYPE_STRING:
array.put(cursor.getString(i));
break;
case Cursor.FIELD_TYPE_BLOB:
//json不能存byte[],因此只能用bcd编码或base64编码来存
array.put(BcdUtil.toString(cursor.getBlob(i)));
break;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
values.put(array);
}
jsonObject.put("colName", colNames);
jsonObject.put("type", typeNames);
jsonObject.put("value", values);
} else {
// 表的列为空
error = context.getString(R.string.table_column_empty);
}
} else {
//获取游标失败
error = context.getString(R.string.get_cursor_fail);
}
} catch (SQLiteException e) {
e.printStackTrace();
error = e.getMessage();
} catch (JSONException e) {
e.printStackTrace();
error = e.getMessage();
} finally {
if (cursor != null) {
cursor.close();
}
}
return jsonObject;
}
public void testDatabase() {
String createTable = "CREATE TABLE IF NOT EXISTS person ( id integer primary key autoincrement, name varchar(20), age integer, other null )";
String insertInto = "INSERT INTO person ( name, age, other ) VALUES ( '%s', %d, %s )";
String insertInto2 = "INSERT INTO person ( name, age, other ) VALUES ( 'bird', NULL, NULL )";
String insertInto3 = "INSERT INTO person ( name, age, other ) VALUES ( NULL, 5, NULL )";
String selectFrom = "SELECT * FROM person";
//sqlite 不支持 drop column,会抛出异常
String alertTable = "ALTER TABLE person DROP COLUMN age";
String updateItem = "UPDATE person set age = 99 where upper(name) = upper('bob')";
String dropTable = "DROP TABLE IF EXISTS person";
String[] names = {"Tina", "Bob", "Tom"};
int[] ages = {18, 30, 24};
Object[] others = {"'other'", 11, true};
dialog = DialogUtil.show(activity, data, "", null);
new Thread(()->{
//执行结果
boolean sqlResult;
JSONObject sqlSelect = null;
sb = new StringBuilder();
SQLiteHelper sqLiteHelper = SQLiteHelper.getInstance(activity, "test", null);
sqLiteHelper.execSQL(createTable);
sqlResult = sqLiteHelper.execSQL(createTable);
StringUtil.appendLine(sb, "创建表:" + sqlResult);
setDialogMessage(activity, sb.toString());
sqlSelect = sqLiteHelper.queryToJson(selectFrom);
StringUtil.appendLine(sb, "创建后的结果:" + sqlSelect);
setDialogMessage(activity, sb.toString());
for (int i = 0; i < names.length; i++) {
sqlResult = sqLiteHelper.execSQL(String.format(insertInto, names[i], ages[i], others[i]));
}
StringUtil.appendLine(sb, "插入数据:" + sqlResult);
setDialogMessage(activity, sb.toString());
sqlSelect = sqLiteHelper.queryToJson(selectFrom);
StringUtil.appendLine(sb, "插入后的结果:" + sqlSelect);
setDialogMessage(activity, sb.toString());
sqlResult = sqLiteHelper.execSQL(insertInto2);
StringUtil.appendLine(sb, "插入name=NULL的数据:" + sqlResult);
setDialogMessage(activity, sb.toString());
sqlSelect = sqLiteHelper.queryToJson(selectFrom);
StringUtil.appendLine(sb, "插入name=NULL后的结果:" + sqlSelect);
setDialogMessage(activity, sb.toString());
sqlResult = sqLiteHelper.execSQL(insertInto3);
StringUtil.appendLine(sb, "插入age=NULL的数据:" + sqlResult);
setDialogMessage(activity, sb.toString());
sqlSelect = sqLiteHelper.queryToJson(selectFrom);
StringUtil.appendLine(sb, "插入age=NULL后的结果:" + sqlSelect);
setDialogMessage(activity, sb.toString());
sqlResult = sqLiteHelper.execSQL(updateItem);
StringUtil.appendLine(sb, "更新数据:" + sqlResult);
setDialogMessage(activity, sb.toString());
sqlSelect = sqLiteHelper.queryToJson(selectFrom);
StringUtil.appendLine(sb, "跟新数据后的结果:" + sqlSelect);
setDialogMessage(activity, sb.toString());
sqlResult = sqLiteHelper.execSQL(alertTable);
StringUtil.appendLine(sb, "修改表结构:" + sqlResult);
setDialogMessage(activity, sb.toString());
sqlSelect = sqLiteHelper.queryToJson(selectFrom);
StringUtil.appendLine(sb, "修改表结构后的结果:" + sqlSelect);
setDialogMessage(activity, sb.toString());
sqlResult = sqLiteHelper.execSQL(dropTable);
StringUtil.appendLine(sb, "删除表:" + sqlResult);
setDialogMessage(activity, sb.toString());
sqlSelect = sqLiteHelper.queryToJson(selectFrom);
StringUtil.appendLine(sb, "删除表后的结果:" + sqlSelect);
setDialogMessage(activity, sb.toString());
}).start();
}
从测试结果中可以看出几点
将代码片段 Object[] others = {"'other'", 11, true};
替换为 Object[] others = { 11, "'other'", true};
后,卸载程序,然后再次安装运行可以发现,类型从 string 变为 integer,这说明,如果在创建表的时候将类型设为 null,那么它会根据第一个存进去的值,来修改列的类型。但不会改变 .schema
命令打印出的创建表的语句,对于存入的非列类型的值也不会报错。
https://blog.csdn.net/chy555chy/article/details/101511609