SQLite开发精要

1. SQLite数据库介绍

开源, 支持NULL, INTEGER, REAL(浮点数), TEXT(字符串), BLOB(二进制数据)这5种类型.
有一个特点是, 往某个字段存储数据时的实际类型可以和声明类型不一致,例如, 字段声明类型为INTEGER, 但你也可以把一个字符串存入这个字段, 不会出现错误, 但实际开发中, 为避免混乱, 并不会这么操作.

2. SQLite命令行操作

adb shell进去后, 运行“sqlite3”命令操作数据库, 但大部分手机里没有集成这个命令.
ubuntu下, 可以把*.db文件adb pull 出来, 然后用“sqliteman”工具查看数据库内容.

~/$ sqliteman downloads.db

创建表的原生sql语句:

create table film(_id integer primary key autoincrement, title text, length int, year int, starring text);

refer to:
http://www.jianshu.com/p/96ef716f9fdd

注:
在我的ubuntu上, 因为缺少一些依赖包的原因, sqliteman和sqlitebrowser都装不上, 暂时还解决不了, 想在chrome上找个sqlite查看工具, 也没找到能用的, 最后, 在firefox的add-on里, 搜到了一个插件, Sqlite Manager 可以用, 简直太好了.

主键字段的概念
主键用来唯一的标识某一条记录

几个注意点:

  1. 在sql语句字段的声明中, 只要声明为primary key,就说明这是一个主键字段
  2. 主键字段默认就包含了not null 和unique两个约束
  3. 主键应当是对用户没有意义的
  4. 如果在创建表时, 声明主键字段时加上autoincrement, 那么当添加一条记录时, 不用指定这个字段的值, 这条记录中这个字段的值被自动设置为"表的行数+1".

Sqlite中,一个自增长字段定义为INTEGER PRIMARY KEY AUTOINCREMENT,那么在插入一个新数据时,只需要将这个字段的值指定为NULL,即可由引擎自动设定其值,引擎会设定为最大的rowid+1。
使用自增长字段的话,引擎会自动产生一个sqlite_sequence表,里面的"seq"字段记录下每个表的自增长字段目前已使用的最大值.

sqlite_1.png

如果创建表时, 不指定"_id integer primary key autoincrement", sqlite也会为表默认添加上一个字段"rowid"作为主键, 但不建议这样做, 因为对数据库进行一些操作后, rowid的值会被修改.
具体参考这篇文章:
http://blog.sina.com.cn/s/blog_61f4999d0101b752.html
(不建议使用rowid作为sqlite主键)
From the official documentation: “Rowids can change at any time and without notice."

所以实际开发中, 创建表的操作, 一定要加上“_id integer primary key autoincrement”显式的创建一个字段名为"_id"的自增长主键字段.

3. 定义数据库的元数据

所谓定义数据库的元数据, 就是说写一个类, 里面定义一些字符串常量, 定义表的名字, 以及表中的各个字段的名字.
这样在之后的数据库操作时, 引用这些常量名就可以了, 这样写代码更加规范.

public class BookmarkColumns implements BaseColumns {
    public static final String TABLE_NAME = "history";
    public static final String URL = "url";
    public static final String VISITS = "visits";
    public static final String DATE = "date";
}

BaseColumns接口中, 默认给定了2个字段名.

public interface BaseColumns
{
    /**
     * The unique ID for a row.
     * <P>Type: INTEGER (long)</P>
     */
    public static final String _ID = "_id";

    /**
     * The count of rows in a directory.
     * <P>Type: INTEGER</P>
     */
    public static final String _COUNT = "_count";
}
4. 使用SQLiteOpenHelper创建数据库
public abstract class SQLiteOpenHelper, 是一个抽象类.

你需要写一个子类, 实现里面的onCreate(), onUpdate(), onDowngrade()方法.
这个类的目的是, 创建*.db文件中的表结构, 以及提供getReadableDatabase()和getWritableDatabase() 两个API,获取SQLiteDatabase对象.

public class MyDatabaseHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "pet.db";
    private static final int VERSION = 1;

    private static final String CREATE_TABLE_DOG = "CREATE TABLE dog(_id INTEGER PRIMARY KEY AUTOINCREMENT," +
                            "name TEXT, age INTEGER)";
    private static final String DROP_TABLE_DOG = "DROP TABLE IF EXISTS dog";

    public MyDatabaseHelper(Context context) {
        super(context, DB_NAME, null, VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_DOG);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL(DROP_TABLE_DOG);
        db.execSQL(CREATE_TABLE_DOG);
    }
}
5. 用SQLiteDatabase提供封装后的API实现, 增删改查数据.
public class DatabaseAdapter {


    public void add(Dog dog) {
        SQLiteDatabase db = dpHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(PetMetaData.DogTable.NAME, dog.getName());
        values.put(PetMetaData.DogTable.AGE, dog.getAge());

        //参数: 表名, null, 数据键值对
        db.insert(PetMetaData.DogTable.TABLE_NAME, null, values);
        db.close();
    }


    public void delete(int id) {
        SQLiteDatabase db = dpHelper.getWritableDatabase();
        String whereClause = PetMetaData.DogTable._ID + "=?";
        String[] whereArgs = {String.valueOf(id)};

        // ? 是占位符
        //参数: 表名, 删除的条件,条件的值
        db.delete(PetMetaData.DogTable.TABLE_NAME, whereClause, whereArgs);
        db.close();
    }

    public void update(Dog dog) {
        SQLiteDatabase db = dpHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(PetMetaData.DogTable.NAME, dog.getName());
        values.put(PetMetaData.DogTable.AGE, dog.getAge());

        String whereClause = PetMetaData.DogTable._ID + "=?";
        String[] whereArgs = {String.valueOf(dog.getId())};

        //参数: 表名, 数据键值对, 条件, 条件的值
        db.update(PetMetaData.DogTable.TABLE_NAME, values, whereClause, whereArgs);
        db.close();
    }

    public ArrayList<Dog> findAll() {
        SQLiteDatabase db = dpHelper.getReadableDatabase();
        String[] columns = {PetMetaData.DogTable._ID, PetMetaData.DogTable.NAME, PetMetaData.DogTable.AGE};

        
        //是否去除重复记录, 表名, 要查询的列, 查询条件, 查询条件的值, 分组条件, 分组条件的值, 排序, 分页条件.
        Cursor c = db.query(true, PetMetaData.DogTable.TABLE_NAME, columns, null, null, null, null, null, null);
    
        ArrayList<Dog> dogs = new ArrayList<Dog>();
        Dog dog = null;
    
        while(c.moveToNext()) {
            dog = new Dog();
            dog.setId(c.getInt(c.getColumnIndexOrThrow(PetMetaData.DogTable._ID)));
            dog.setName(c.getString(c.getColumnIndexOrThrow(PetMetaData.DogTable.NAME)));
            dog.setAge(c.getString(c.getColumnIndexOrThrow(PetMetaData.DogTable.NAME)));
        
            dogs.add(dog);
        }

        c.close();
        db.close();
    
        return dogs;
    }

    public Dog findById(int id) {
        SQLiteDatabase db = dpHelper.getReadableDatabase();
        String[] columns = {PetMetaData.DogTable._ID, PetMetaData.DogTable.NAME, PetMetaData.DogTable.AGE};
        //是否去除重复记录, 表名, 要查询的列, 查询条件, 查询条件的值, 分组条件, 分组条件的值, 排序, 分页条件.
        Cursor c = db.query(true, PetMetaData.DogTable.TABLE_NAME, columns, PetMetaData.DogTable._ID+"=?", id, null, null, null, null);

        Dog dog = null;
        if(c.moveToNext()) {
            dog = new Dog();
            dog.setId(c.getInt(c.getColumnIndexOrThrow(PetMetaData.DogTable._ID)));
            dog.setName(c.getString(c.getColumnIndexOrThrow(PetMetaData.DogTable.NAME)));
            dog.setAge(c.getString(c.getColumnIndexOrThrow(PetMetaData.DogTable.NAME)));
        }

        c.close();
        db.close();

        return dog;
    }

}
6. 使用原生SQL语句
public void rawAdd(Dog dog) {
    SQLiteDatabase db = dpHelper.getWritableDatabase();
    String sql = "insert into dog(name, age) values (?,?)";
    Object[] args = {dog.getName(), dog.getAge()};
    db.execSQL(sql, args);
    db.close();
}

public void rawDelete(int id) {
    SQLiteDatabase db = dpHelper.getWritableDatabase();
    String sql = "delete from dog where id =?";
    Object[] args = {id};
    db.execSQL(sql, args);
    db.close();
}

public void rawUpdate(Dog dog) {
    SQLiteDatabase db = dpHelper.getWritableDatabase();
    String sql = "update dog set name=?, age=? where id=?";
    Object[] args = {dog.getName(), dog.getAge(), dog.getId()};
    db.execSQL(sql, args);
    db.close();
}

public Dog rawFindById(int id) {
    SQLiteDatabase db = dpHelper.getReadableDatabase();
    String sql = "select _id, name, age from dog where _id=?";
    Cursor c = db.rawQuery(sql, new String[]{String.valueOf(id)});
    Dog dog = null;

    if(c.moveToNext()) {
        dog = new Dog();
        dog.setId(c.getInt(c.getColumnIndexOrThrow(PetMetaData.DogTable._ID)));
        dog.setName(c.getString(c.getColumnIndexOrThrow(PetMetaData.DogTable.NAME)));
        dog.setAge(c.getInt(c.getColumnIndexOrThrow(PetMetaData.DogTable.AGE)));
    }
    c.close();
    db.close();
    return dog;
}

public ArrayList<Dog> rawFindAll() {
    SQLiteDatabase db = dpHelper.getReadableDatabase();
    String sql = "select _id, name, age from dog";
    Cursor c = db.rawQuery(sql,null);
    ArrayList<Dog> dogs = new ArrayList<Dog>();

    Dog dog = null;
    while(c.moveToNext()) {
        dog = new Dog();
        dog.setId(c.getInt(c.getColumnIndexOrThrow(PetMetaData.DogTable._ID)));
        dog.setName(c.getString(c.getColumnIndexOrThrow(PetMetaData.DogTable.NAME)));
        dog.setAge(c.getInt(c.getColumnIndexOrThrow(PetMetaData.DogTable.AGE)));
        dogs.add(add);
    }
    c.close();
    db.close();
    return dogs;
}

这里有一个重要的优化点:
select sql语句中, 不要用"星号",要明示具体查询的列的名字. 因为"星号"是通配符, 在底层构建完整的sql语句时, 还是会把*转换为具体的列的名字, 用"星号"会影响一定的性能.

7. 使用事务

当多个sql语句要一起执行时, 可以使用事务,要么一起成功, 要么一起失败. 使用事务可以提高一些性能.

SQLiteDatabase db = dpHelper.getWritableDatabase();
db.beginTransaction(); //开始使用事务
try {
    db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"tony", 25});
    db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"yoyo", 28});
    db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"ahking", 35});

    //调用到此方法, 底层就是给事务的标志位设置成功标记. 
    //会在执行到db.endTransaction()时提交当前事务, 如果不调用此方法, db.endTransaction()会回滚事务.
    db.setTransactionSuccessful();

} finally {
    db.endTransaction(); //由事务的标志决定是提交事务, 还是回滚事务.
}
db.close();

游戏玩家管理案例

实际开发中, 按照文件下载时间的倒序去查询数据.

String sql = "select * from " + TAB_NAME + " ORDER BY" + " downloadtime" +  " DESC";

refer to:
http://www.runoob.com/sqlite/sqlite-order-by.html

http://wale.oyediran.me/2015/04/02/android-sqlite-dao-design/
Android SQLite DAO Design
DAO的简写: Data Access Object, 数据获取层.
这篇文章写的很简洁, 以后写数据库类, 按照这个模式去实现就可以了.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容