Android数据库

一、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

    创建数据库
    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界面比较简单,就不给出代码了。如下图:
    //为按钮注册监听的方法 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的手机来说的没有任何安全性可以,一旦被利用将会导致数据库数据的泄漏,所以我们该如何避免这种事情的发生呢?我们尝试这对数据库进行加密。

    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

    greenDAO是一种Android数据库ORM(object/relational mapping - 对象关系映射)框架,是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。与OrmLite、ActiveOrm、LitePal等数据库相比,单位时间内可以插入、更新和查询更多的数据,而且提供了大量的灵活通用接口。

  • 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类

    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基本用法