现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧。对于Android平台来说,系统内置了丰富的API来供开发人员操作SQLite,我们可以轻松的完成对数据的存取。

下面就向大家介绍一下SQLite常用的操作方法,为了方便,我将代码写在了Activity的onCreate中:

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. //打开或创建test.db数据库
  5. SQLiteDatabase db = openOrCreateDatabase("test.db", Context.MODE_PRIVATE, null);
  6. db.execSQL("DROP TABLE IF EXISTS person");
  7. //创建person表
  8. db.execSQL("CREATE TABLE person (_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR, age SMALLINT)");
  9. Person person = new Person();
  10. person.name = "john";
  11. person.age = 30;
  12. //插入数据
  13. db.execSQL("INSERT INTO person VALUES (NULL, ?, ?)", new Object[]{person.name, person.age});
  14. person.name = "david";
  15. person.age = 33;
  16. //ContentValues以键值对的形式存放数据
  17. ContentValues cv = new ContentValues();
  18. cv.put("name", person.name);
  19. cv.put("age", person.age);
  20. //插入ContentValues中的数据
  21. db.insert("person", null, cv);
  22. cv = new ContentValues();
  23. cv.put("age", 35);
  24. //更新数据
  25. db.update("person", cv, "name = ?", new String[]{"john"});
  26. Cursor c = db.rawQuery("SELECT * FROM person WHERE age >= ?", new String[]{"33"});
  27. while (c.moveToNext()) {
  28. int _id = c.getInt(c.getColumnIndex("_id"));
  29. String name = c.getString(c.getColumnIndex("name"));
  30. int age = c.getInt(c.getColumnIndex("age"));
  31. Log.i("db", "_id=>" + _id + ", name=>" + name + ", age=>" + age);
  32. }
  33. c.close();
  34. //删除数据
  35. db.delete("person", "age < ?", new String[]{"35"});
  36. //关闭当前数据库
  37. db.close();
  38. //删除test.db数据库
  39. //      deleteDatabase("test.db");
  40. }

在执行完上面的代码后,系统就会在/data/data/[PACKAGE_NAME]/databases目录下生成一个“test.db”的数据库文件,如图:

上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用

  1. db.executeSQL(String sql);
  2. db.executeSQL(String sql, Object[] bindArgs);//sql语句中使用占位符,然后第二个参数是实际的参数集

除了统一的形式之外,他们还有各自的操作方法:

  1. db.insert(String table, String nullColumnHack, ContentValues values);
  2. db.update(String table, Contentvalues values, String whereClause, String whereArgs);
  3. db.delete(String table, String whereClause, String whereArgs);

以上三个方法的第一个参数都是表示要操作的表名;insert中的第二个参数表示如果插入的数据每一列都为空的话,需要指定此行中某一列的名称,系统将此列设置为NULL,不至于出现错误;insert中的第三个参数是ContentValues类型的变量,是键值对组成的Map,key代表列名,value代表该列要插入的值;update的第二个参数也很类似,只不过它是更新该字段key为最新的value值,第三个参数whereClause表示WHERE表达式,比如“age > ? and age < ?”等,最后的whereArgs参数是占位符的实际参数值;delete方法的参数也是一样。

下面来说说查询操作。查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式:

  1. db.rawQuery(String sql, String[] selectionArgs);
  2. db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy);
  3. db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);
  4. db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);

上面几种都是常用的查询方法,第一种最为简单,将所有的SQL语句都组织到一个字符串中,使用占位符代替实际参数,selectionArgs就是占位符实际参数集;下面的几种参数都很类似,columns表示要查询的列所有名称集,selection表示WHERE之后的条件语句,可以使用占位符,groupBy指定分组的列名,having指定分组条件,配合groupBy使用,orderBy指定排序的列名,limit指定分页参数,distinct可以指定“true”或“false”表示要不要过滤重复值。需要注意的是,selection、groupBy、having、orderBy、limit这几个参数中不包括“WHERE”、“GROUP BY”、“HAVING”、“ORDER BY”、“LIMIT”等SQL关键字。
最后,他们同时返回一个Cursor对象,代表数据集的游标,有点类似于JavaSE中的ResultSet。

下面是Cursor对象的常用方法:

  1. c.move(int offset); //以当前位置为参考,移动到指定行
  2. c.moveToFirst();    //移动到第一行
  3. c.moveToLast();     //移动到最后一行
  4. c.moveToPosition(int position); //移动到指定行
  5. c.moveToPrevious(); //移动到前一行
  6. c.moveToNext();     //移动到下一行
  7. c.isFirst();        //是否指向第一条
  8. c.isLast();     //是否指向最后一条
  9. c.isBeforeFirst();  //是否指向第一条之前
  10. c.isAfterLast();    //是否指向最后一条之后
  11. c.isNull(int columnIndex);  //指定列是否为空(列基数为0)
  12. c.isClosed();       //游标是否已关闭
  13. c.getCount();       //总数据项数
  14. c.getPosition();    //返回当前游标所指向的行数
  15. c.getColumnIndex(String columnName);//返回某列名对应的列索引值
  16. c.getString(int columnIndex);   //返回当前行指定列的值

在上面的代码示例中,已经用到了这几个常用方法中的一些,关于更多的信息,大家可以参考官方文档中的说明。

最后当我们完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。

上面就是SQLite的基本应用,但在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。

下面,我们就以一个实例来讲解具体的用法,我们新建一个名为db的项目,结构如下:

其中DBHelper继承了SQLiteOpenHelper,作为维护和管理数据库的基类,DBManager是建立在DBHelper之上,封装了常用的业务方法,Person是我们的person表对应的JavaBean,MainActivity就是我们显示的界面。

下面我们先来看一下DBHelper:

  1. package com.scott.db;
  2. import android.content.Context;
  3. import android.database.sqlite.SQLiteDatabase;
  4. import android.database.sqlite.SQLiteOpenHelper;
  5. public class DBHelper extends SQLiteOpenHelper {
  6. private static final String DATABASE_NAME = "test.db";
  7. private static final int DATABASE_VERSION = 1;
  8. public DBHelper(Context context) {
  9. //CursorFactory设置为null,使用默认值
  10. super(context, DATABASE_NAME, null, DATABASE_VERSION);
  11. }
  12. //数据库第一次被创建时onCreate会被调用
  13. @Override
  14. public void onCreate(SQLiteDatabase db) {
  15. db.execSQL("CREATE TABLE IF NOT EXISTS person" +
  16. "(_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR, age INTEGER, info TEXT)");
  17. }
  18. //如果DATABASE_VERSION值被改为2,系统发现现有数据库版本不同,即会调用onUpgrade
  19. @Override
  20. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  21. db.execSQL("ALTER TABLE person ADD COLUMN other STRING");
  22. }
  23. }

正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade方法,我们可以执行修改表结构等语句。

为了方便我们面向对象的使用数据,我们建一个Person类,对应person表中的字段,如下:

  1. package com.scott.db;
  2. public class Person {
  3. public int _id;
  4. public String name;
  5. public int age;
  6. public String info;
  7. public Person() {
  8. }
  9. public Person(String name, int age, String info) {
  10. this.name = name;
  11. this.age = age;
  12. this.info = info;
  13. }
  14. }

然后,我们需要一个DBManager,来封装我们所有的业务方法,代码如下:

  1. package com.scott.db;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import android.content.ContentValues;
  5. import android.content.Context;
  6. import android.database.Cursor;
  7. import android.database.sqlite.SQLiteDatabase;
  8. public class DBManager {
  9. private DBHelper helper;
  10. private SQLiteDatabase db;
  11. public DBManager(Context context) {
  12. helper = new DBHelper(context);
  13. //因为getWritableDatabase内部调用了mContext.openOrCreateDatabase(mName, 0, mFactory);
  14. //所以要确保context已初始化,我们可以把实例化DBManager的步骤放在Activity的onCreate里
  15. db = helper.getWritableDatabase();
  16. }
  17. /**
  18. * add persons
  19. * @param persons
  20. */
  21. public void add(List<Person> persons) {
  22. db.beginTransaction();  //开始事务
  23. try {
  24. for (Person person : persons) {
  25. db.execSQL("INSERT INTO person VALUES(null, ?, ?, ?)", new Object[]{person.name, person.age, person.info});
  26. }
  27. db.setTransactionSuccessful();  //设置事务成功完成
  28. } finally {
  29. db.endTransaction();    //结束事务
  30. }
  31. }
  32. /**
  33. * update person's age
  34. * @param person
  35. */
  36. public void updateAge(Person person) {
  37. ContentValues cv = new ContentValues();
  38. cv.put("age", person.age);
  39. db.update("person", cv, "name = ?", new String[]{person.name});
  40. }
  41. /**
  42. * delete old person
  43. * @param person
  44. */
  45. public void deleteOldPerson(Person person) {
  46. db.delete("person", "age >= ?", new String[]{String.valueOf(person.age)});
  47. }
  48. /**
  49. * query all persons, return list
  50. * @return List<Person>
  51. */
  52. public List<Person> query() {
  53. ArrayList<Person> persons = new ArrayList<Person>();
  54. Cursor c = queryTheCursor();
  55. while (c.moveToNext()) {
  56. Person person = new Person();
  57. person._id = c.getInt(c.getColumnIndex("_id"));
  58. person.name = c.getString(c.getColumnIndex("name"));
  59. person.age = c.getInt(c.getColumnIndex("age"));
  60. person.info = c.getString(c.getColumnIndex("info"));
  61. persons.add(person);
  62. }
  63. c.close();
  64. return persons;
  65. }
  66. /**
  67. * query all persons, return cursor
  68. * @return  Cursor
  69. */
  70. public Cursor queryTheCursor() {
  71. Cursor c = db.rawQuery("SELECT * FROM person", null);
  72. return c;
  73. }
  74. /**
  75. * close database
  76. */
  77. public void closeDB() {
  78. db.close();
  79. }
  80. }

我们在DBManager构造方法中实例化DBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;在添加多个Person信息时,我们采用了事务处理,确保数据完整性;最后我们提供了一个closeDB方法,释放数据库资源,这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。

我们获取数据库实例时使用了getWritableDatabase()方法,也许朋友们会有疑问,在getWritableDatabase()和getReadableDatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。

我们来看一下SQLiteOpenHelper中的getReadableDatabase()方法:

  1. public synchronized SQLiteDatabase getReadableDatabase() {
  2. if (mDatabase != null && mDatabase.isOpen()) {
  3. // 如果发现mDatabase不为空并且已经打开则直接返回
  4. return mDatabase;
  5. }
  6. if (mIsInitializing) {
  7. // 如果正在初始化则抛出异常
  8. throw new IllegalStateException("getReadableDatabase called recursively");
  9. }
  10. // 开始实例化数据库mDatabase
  11. try {
  12. // 注意这里是调用了getWritableDatabase()方法
  13. return getWritableDatabase();
  14. } catch (SQLiteException e) {
  15. if (mName == null)
  16. throw e; // Can't open a temp database read-only!
  17. Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
  18. }
  19. // 如果无法以可读写模式打开数据库 则以只读方式打开
  20. SQLiteDatabase db = null;
  21. try {
  22. mIsInitializing = true;
  23. String path = mContext.getDatabasePath(mName).getPath();// 获取数据库路径
  24. // 以只读方式打开数据库
  25. db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
  26. if (db.getVersion() != mNewVersion) {
  27. throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to "
  28. + mNewVersion + ": " + path);
  29. }
  30. onOpen(db);
  31. Log.w(TAG, "Opened " + mName + " in read-only mode");
  32. mDatabase = db;// 为mDatabase指定新打开的数据库
  33. return mDatabase;// 返回打开的数据库
  34. } finally {
  35. mIsInitializing = false;
  36. if (db != null && db != mDatabase)
  37. db.close();
  38. }
  39. }

在getReadableDatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mDatabase赋值为最新打开的数据库实例。既然有可能调用到getWritableDatabase()方法,我们就要看一下了:

  1. public synchronized SQLiteDatabase getWritableDatabase() {
  2. if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
  3. // 如果mDatabase不为空已打开并且不是只读模式 则返回该实例
  4. return mDatabase;
  5. }
  6. if (mIsInitializing) {
  7. throw new IllegalStateException("getWritableDatabase called recursively");
  8. }
  9. // If we have a read-only database open, someone could be using it
  10. // (though they shouldn't), which would cause a lock to be held on
  11. // the file, and our attempts to open the database read-write would
  12. // fail waiting for the file lock. To prevent that, we acquire the
  13. // lock on the read-only database, which shuts out other users.
  14. boolean success = false;
  15. SQLiteDatabase db = null;
  16. // 如果mDatabase不为空则加锁 阻止其他的操作
  17. if (mDatabase != null)
  18. mDatabase.lock();
  19. try {
  20. mIsInitializing = true;
  21. if (mName == null) {
  22. db = SQLiteDatabase.create(null);
  23. } else {
  24. // 打开或创建数据库
  25. db = mContext.openOrCreateDatabase(mName, 0, mFactory);
  26. }
  27. // 获取数据库版本(如果刚创建的数据库,版本为0)
  28. int version = db.getVersion();
  29. // 比较版本(我们代码中的版本mNewVersion为1)
  30. if (version != mNewVersion) {
  31. db.beginTransaction();// 开始事务
  32. try {
  33. if (version == 0) {
  34. // 执行我们的onCreate方法
  35. onCreate(db);
  36. } else {
  37. // 如果我们应用升级了mNewVersion为2,而原版本为1则执行onUpgrade方法
  38. onUpgrade(db, version, mNewVersion);
  39. }
  40. db.setVersion(mNewVersion);// 设置最新版本
  41. db.setTransactionSuccessful();// 设置事务成功
  42. } finally {
  43. db.endTransaction();// 结束事务
  44. }
  45. }
  46. onOpen(db);
  47. success = true;
  48. return db;// 返回可读写模式的数据库实例
  49. } finally {
  50. mIsInitializing = false;
  51. if (success) {
  52. // 打开成功
  53. if (mDatabase != null) {
  54. // 如果mDatabase有值则先关闭
  55. try {
  56. mDatabase.close();
  57. } catch (Exception e) {
  58. }
  59. mDatabase.unlock();// 解锁
  60. }
  61. mDatabase = db;// 赋值给mDatabase
  62. } else {
  63. // 打开失败的情况:解锁、关闭
  64. if (mDatabase != null)
  65. mDatabase.unlock();
  66. if (db != null)
  67. db.close();
  68. }
  69. }
  70. }

大家可以看到,几个关键步骤是,首先判断mDatabase如果不为空已打开并不是只读模式则直接返回,否则如果mDatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mDatabase并解锁,把新打开的数据库实例赋予mDatabase,并返回最新实例。

看完上面的过程之后,大家或许就清楚了许多,如果不是在遇到磁盘空间已满等情况,getReadableDatabase()一般都会返回和getWritableDatabase()一样的数据库实例,所以我们在DBManager构造方法中使用getWritableDatabase()获取整个应用所使用的数据库实例是可行的。当然如果你真的担心这种情况会发生,那么你可以先用getWritableDatabase()获取数据实例,如果遇到异常,再试图用getReadableDatabase()获取实例,当然这个时候你获取的实例只能读不能写了。

最后,让我们看一下如何使用这些数据操作方法来显示数据,下面是MainActivity.java的布局文件和代码:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <Button
  7. android:layout_width="fill_parent"
  8. android:layout_height="wrap_content"
  9. android:text="add"
  10. android:onClick="add"/>
  11. <Button
  12. android:layout_width="fill_parent"
  13. android:layout_height="wrap_content"
  14. android:text="update"
  15. android:onClick="update"/>
  16. <Button
  17. android:layout_width="fill_parent"
  18. android:layout_height="wrap_content"
  19. android:text="delete"
  20. android:onClick="delete"/>
  21. <Button
  22. android:layout_width="fill_parent"
  23. android:layout_height="wrap_content"
  24. android:text="query"
  25. android:onClick="query"/>
  26. <Button
  27. android:layout_width="fill_parent"
  28. android:layout_height="wrap_content"
  29. android:text="queryTheCursor"
  30. android:onClick="queryTheCursor"/>
  31. <ListView
  32. android:id="@+id/listView"
  33. android:layout_width="fill_parent"
  34. android:layout_height="wrap_content"/>
  35. </LinearLayout>
  1. package com.scott.db;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4. import java.util.List;
  5. import java.util.Map;
  6. import android.app.Activity;
  7. import android.database.Cursor;
  8. import android.database.CursorWrapper;
  9. import android.os.Bundle;
  10. import android.view.View;
  11. import android.widget.ListView;
  12. import android.widget.SimpleAdapter;
  13. import android.widget.SimpleCursorAdapter;
  14. public class MainActivity extends Activity {
  15. private DBManager mgr;
  16. private ListView listView;
  17. @Override
  18. public void onCreate(Bundle savedInstanceState) {
  19. super.onCreate(savedInstanceState);
  20. setContentView(R.layout.main);
  21. listView = (ListView) findViewById(R.id.listView);
  22. //初始化DBManager
  23. mgr = new DBManager(this);
  24. }
  25. @Override
  26. protected void onDestroy() {
  27. super.onDestroy();
  28. //应用的最后一个Activity关闭时应释放DB
  29. mgr.closeDB();
  30. }
  31. public void add(View view) {
  32. ArrayList<Person> persons = new ArrayList<Person>();
  33. Person person1 = new Person("Ella", 22, "lively girl");
  34. Person person2 = new Person("Jenny", 22, "beautiful girl");
  35. Person person3 = new Person("Jessica", 23, "sexy girl");
  36. Person person4 = new Person("Kelly", 23, "hot baby");
  37. Person person5 = new Person("Jane", 25, "a pretty woman");
  38. persons.add(person1);
  39. persons.add(person2);
  40. persons.add(person3);
  41. persons.add(person4);
  42. persons.add(person5);
  43. mgr.add(persons);
  44. }
  45. public void update(View view) {
  46. Person person = new Person();
  47. person.name = "Jane";
  48. person.age = 30;
  49. mgr.updateAge(person);
  50. }
  51. public void delete(View view) {
  52. Person person = new Person();
  53. person.age = 30;
  54. mgr.deleteOldPerson(person);
  55. }
  56. public void query(View view) {
  57. List<Person> persons = mgr.query();
  58. ArrayList<Map<String, String>> list = new ArrayList<Map<String, String>>();
  59. for (Person person : persons) {
  60. HashMap<String, String> map = new HashMap<String, String>();
  61. map.put("name", person.name);
  62. map.put("info", person.age + " years old, " + person.info);
  63. list.add(map);
  64. }
  65. SimpleAdapter adapter = new SimpleAdapter(this, list, android.R.layout.simple_list_item_2,
  66. new String[]{"name", "info"}, new int[]{android.R.id.text1, android.R.id.text2});
  67. listView.setAdapter(adapter);
  68. }
  69. public void queryTheCursor(View view) {
  70. Cursor c = mgr.queryTheCursor();
  71. startManagingCursor(c); //托付给activity根据自己的生命周期去管理Cursor的生命周期
  72. CursorWrapper cursorWrapper = new CursorWrapper(c) {
  73. @Override
  74. public String getString(int columnIndex) {
  75. //将简介前加上年龄
  76. if (getColumnName(columnIndex).equals("info")) {
  77. int age = getInt(getColumnIndex("age"));
  78. return age + " years old, " + super.getString(columnIndex);
  79. }
  80. return super.getString(columnIndex);
  81. }
  82. };
  83. //确保查询结果中有"_id"列
  84. SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_2,
  85. cursorWrapper, new String[]{"name", "info"}, new int[]{android.R.id.text1, android.R.id.text2});
  86. ListView listView = (ListView) findViewById(R.id.listView);
  87. listView.setAdapter(adapter);
  88. }
  89. }

这里需要注意的是SimpleCursorAdapter的应用,当我们使用这个适配器时,我们必须先得到一个Cursor对象,这里面有几个问题:如何管理Cursor的生命周期,如果包装Cursor,Cursor结果集都需要注意什么。

如果手动去管理Cursor的话会非常的麻烦,还有一定的风险,处理不当的话运行期间就会出现异常,幸好Activity为我们提供了startManagingCursor(Cursor cursor)方法,它会根据Activity的生命周期去管理当前的Cursor对象,下面是该方法的说明:

  1. /**
  2. * This method allows the activity to take care of managing the given
  3. * {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
  4. * That is, when the activity is stopped it will automatically call
  5. * {@link Cursor#deactivate} on the given Cursor, and when it is later restarted
  6. * it will call {@link Cursor#requery} for you.  When the activity is
  7. * destroyed, all managed Cursors will be closed automatically.
  8. *
  9. * @param c The Cursor to be managed.
  10. *
  11. * @see #managedQuery(android.net.Uri , String[], String, String[], String)
  12. * @see #stopManagingCursor
  13. */

文中提到,startManagingCursor方法会根据Activity的生命周期去管理当前的Cursor对象的生命周期,就是说当Activity停止时他会自动调用Cursor的deactivate方法,禁用游标,当Activity重新回到屏幕时它会调用Cursor的requery方法再次查询,当Activity摧毁时,被管理的Cursor都会自动关闭释放。

如何包装Cursor:我们会使用到CursorWrapper对象去包装我们的Cursor对象,实现我们需要的数据转换工作,这个CursorWrapper实际上是实现了Cursor接口。我们查询获取到的Cursor其实是Cursor的引用,而系统实际返回给我们的必然是Cursor接口的一个实现类的对象实例,我们用CursorWrapper包装这个实例,然后再使用SimpleCursorAdapter将结果显示到列表上。

Cursor结果集需要注意些什么:一个最需要注意的是,在我们的结果集中必须要包含一个“_id”的列,否则SimpleCursorAdapter就会翻脸不认人,为什么一定要这样呢?因为这源于SQLite的规范,主键以“_id”为标准。解决办法有三:第一,建表时根据规范去做;第二,查询时用别名,例如:SELECT id AS _id FROM person;第三,在CursorWrapper里做文章:

  1. CursorWrapper cursorWrapper = new CursorWrapper(c) {
  2. @Override
  3. public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
  4. if (columnName.equals("_id")) {
  5. return super.getColumnIndex("id");
  6. }
  7. return super.getColumnIndexOrThrow(columnName);
  8. }
  9. };

如果试图从CursorWrapper里获取“_id”对应的列索引,我们就返回查询结果里“id”对应的列索引即可。

最后我们来看一下结果如何:

Android中SQLite介绍的更多相关文章

  1. Android中SQLite数据库操作(1)——使用SQL语句操作SQLite数据库

    下面是最原始的方法,用SQL语句操作数据库.后面的"Android中SQLite数据库操作(2)--SQLiteOpenHelper类"将介绍一种常用的android封装操作SQL ...

  2. 我的Android六章:Android中SQLite数据库操作

    今天学习的内容是Android中的SQLite数据库操作,在讲解这个内容之前小编在前面有一篇博客也是讲解了SQLite数据库的操作,而那篇博客的讲解是讲述了 如何在Window中通过DOM来操作数据库 ...

  3. Android中SQLite应用详解

    上次我向大家介绍了SQLite的基本信息和使用过程,相信朋友们对SQLite已经有所了解了,那今天呢,我就和大家分享一下在Android中如何使用SQLite. 现在的主流移动设备像Android.i ...

  4. Android中SQLite应用详解(转)

    上次我向大家介绍了SQLite的基本信息和使用过程,相信朋友们对SQLite已经有所了解了,那今天呢,我就和大家分享一下在Android中如何使用SQLite. 现在的主流移动设备像Android.i ...

  5. Android 中 SQLite 性能优化

    数据库是应用开发中常用的技术,在Android应用中也不例外.Android默认使用了SQLite数据库,在应用程序开发中,我们使用最多的无外乎增删改查.纵使操作简单,也有可能出现查找数据缓慢,插入数 ...

  6. Android中SQLite应用具体解释

    如今的主流移动设备像Android.iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,或许就要使用到SQLite来存储我们大量的数据,所以我们就须要掌握移动设备上 ...

  7. android中sqlite数据库的基本使用和添加多张表

    看了很多关于android使用sqlite数据库的文章,很多都是介绍了数据库的建立和表的建立,而表通常都是只建立一张,而实际情况我们用到的表可能不止一张,那这种情况下我们又该怎么办呢,好了,下面我教大 ...

  8. Android中SQLite数据库小计

    2016-03-16 Android数据库支持 本文节选并翻译<Enterprise Android - Programing Android Database Applications for ...

  9. Android中SQLite的使用

    SQLite是Android中内置的数据库,SQLite是轻量级数据库,支持标准的SQL语法,并且支持ACID事物. 在Android中提供了SQLIteOPenHelper类,帮助我们使用SQLit ...

随机推荐

  1. SQL CHECK sql server免费监控单实例工具

    SQL Check 阅读目录 SQL Check? 主要特点 说说不足 下载地址 小结 一款实时性能监测工具 回到目录 SQL Check? 一款实时监测SQL数据库性能.实时排查的问题的免费工具. ...

  2. 01 javaSe 01 抽象类和接口

      抽象类 接口   目录(?)[-] 1 抽象类与接口是面向对象思想层面概念不是程序设计语言层面概念 2 抽象类是本体的抽象接口是行为的抽象 3 C中抽象类与接口的探讨     目录(?)[+]   ...

  3. MariaDB复制架构中应该注意的问题

    一.复制架构中应该注意的问题: 1.限制从服务器只读 在从服务器上设置read_only=ON,此限制对拥有SUPPER权限的用户均无效: 阻止所有用户(在从服务器执行一下命令并保持此线程,也就是执行 ...

  4. thinkphp使用阿里云OSS最新SDK,文件部署

    这文章是建立在你已经注册号阿里云的OSS,和创建好Bucket前提下: 其实阿里云的帮助与文档写的很详细,这里只说一下源码方式 1.phpsdk下载地址(摘自阿里云OSS的帮助与文档)(也有我自己下载 ...

  5. 第五课 Makefile文件的制作(补充)

    序言: 前面的几节课讲解Makefile的一些基本知识也做了一些小例子实践了几下,那么到现在普通的练习则是没有问题.但是如果做项目文件较多又分层次等等还是会碰上好多问题的,这节课补充一些知识. 知识点 ...

  6. $微信小程序开发实践点滴——Bmob基本REST API的python封装

    Refer:Bmob后端云REST API接口文档:http://docs.bmob.cn/data/Restful/a_faststart/doc/index.html 本文使用python对Bmo ...

  7. python学习笔记:函数参数

    1. 位置参数:一般的参数 2. 默认参数: def power(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s 参数里有默认赋 ...

  8. 【笔记】Maven使用入门

    参考<maven实战> 1.编写POM 2.编写主代码 3.编写测试代码 4.打包和运行 具体如下: 1.编写POM. <!-- XML头,指定了该xml文档的版本和编辑方式 --& ...

  9. jQuery 3D垂直多级菜单

    在线演示 本地下载

  10. 20145201 《Java程序设计》第三周学习总结

    20145201 <Java程序设计>第三周学习总结 教材学习内容总结 本周学习了课本第四.五章内容,即认识对象和对象封装. 第四章 认识对象 4.1类与对象 定义类 要产生对象必须先定义 ...