前言
對於一個應用程序而言,數據持久化是必不可少的,Android程序也不例外。這篇博客將介紹Android中關於SQLite的使用,SQLite是一種嵌入式的數據庫引擎,專門適用於資源有限的設備上進行適量的數據存儲,而Android就全面支持標准的SQLite數據庫。在本片博客中,將說明SQLite數據庫的創建以及維護,還有使用SQLite執行CRUD的兩種方式,以及SQLite中事務的使用,最后都會使用示例講解博客中所提到的概念性的內容。
SQLite
Android對SQLite數據庫,提供了完全的支持,而所有創建的SQLite數據庫,僅限於當前應用訪問,如果其他應用需要訪問,則必須提供的Content Provider的支持,並且SQLite數據庫會隨着Android應用的卸載而被刪除。SQLite是一個嵌入式的數據庫引擎,最后是以文件的形式保存數據的。從本質上來看,SQLite的操作方式只是一種更為便捷的文件操作,當應用程序創建或打開一個SQLite數據庫時,其實只是打開一個文件准備讀寫。因為SQLite僅適用於資源有限的小型設備,所以本身就不應該把大量數據存儲在設備的SQLite數據庫里,SQLite只適合存儲一些小型的數據。
為了使SQLite和其他數據庫間的兼容性最大化,SQLite支持對列上類型進行“類型近似”,列的類型近似指的是存儲在列上的數據進行推薦類型存儲。所以雖然SQLite內部只支持NULL、INTEGER、REAL(浮點書)、TEXT(文本)和BLOB(大二進制對象)這五種數據類型,但實際上SQLite完全可以接受varchar(n)、char(n)、decimal(p,s)、date等類型數據,只不過SQLite會在運算或保存時將它們轉換為上面五種數據類型中相應的類型。大多數數據庫的引擎都是使用靜態的、強類型的數據類型,數據的類型是由它的容器決定的,這個容器是指被存放的特定列。而SQLite使用的是動態類型,在SQLite中,值的數據類型跟值本身相關,而不是與它的容器相關,所以SQLite允許把各種類型的數據保存到任何類型字段中,開發者可以不用關心聲明該字段說使用的數據類型。但是有一種情況例外,定義為INTEGER PRIMARY KEY的字段只能存儲64位整數,當向這種字段保存除整數意外的其他類型的數據時,SQLite會產生錯誤。
SQLite數據庫創建與維護
從官方文檔上了解到,在Android項目中,創建SQLite數據庫推薦繼承SQLiteOpenHelper類,然后重寫其中的onCreate()方法,在onCreate()方法中,對執行數據庫創建的SQL語句。而SQLiteOpenHelper不僅僅用於SQLite數據的創建,還可以對其進行維護,以及獲得SQLiteDatabase這個數據庫操作對象。
SQLiteOpenHelper提供了兩個構造器,用於傳遞當前上下文對象以及SQLite數據庫版本信息,在SQLiteOpenHelper的繼承類的構造函數中,會調用它,構造器的簽名如下:
- SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version).
- SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactroy factory,int version,DatabaseErrorHandler errorHandler).
上面的構造函數中,都是用於創建一個SQLite數據庫,context為一個當前應用的上下文對象;name是數據庫名稱;factory是一個允許子類在查詢時使用的游標,一般不用傳Null;version是數據庫版本號;errorHandler是一個接口,傳遞當數據庫錯誤的時候,執行的補救方法。
在SQLiteOpenHelper中,可以進行SQLite數據庫的創建、維護、日志以及獲取可讀寫的數據庫對象,通過下面幾個常用方法得到支持:
- String getDatabaseName():獲取數據庫名。
- SQLiteDatabase getReadableDatabase():創建或者打開一個可讀的數據庫對象。
- SQLiteDatabase getWritableDatabase():創建或者打開一個可讀/寫的數據庫對象。
- abstract void onCreate(SQLiteDatabase db):當第一次調用SQLiteOpenHelper的時候執行,之后再次調用將不再執行,一般用於完成數據庫初始化的工作。
- void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion):當數據庫版本號發生向上更新時,被執行。
- void onDowngrade(SQLiteDatabase db,int oldVersion,int newVersion):當數據庫版本號發生向下更新時,被執行。
下面提供一個簡單的SQLiteOpenHelper的繼承類代碼,用於創建數據庫以及表結構:
1 package com.example.sqlitedbdemo.db; 2 3 import android.content.Context; 4 import android.database.sqlite.SQLiteDatabase; 5 import android.database.sqlite.SQLiteOpenHelper; 6 7 public class DbOpenHelper extends SQLiteOpenHelper { 8 private static String name = "mydb.db"; 9 private static int version = 1; 10 11 public DbOpenHelper(Context context) { 12 super(context, name, null, version); 13 } 14 15 @Override 16 public void onCreate(SQLiteDatabase db) { 17 // 只能支持基本數據類型 18 String sql = "create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))"; 19 db.execSQL(sql); 20 } 21 @Override 22 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 23 // TODO Auto-generated method stub 24 String sql="alter table person add sex varchar(8)"; 25 db.execSQL(sql); 26 } 27 }
Tips:當創建好SQLite數據庫的之后,可以在/data/data/<package name>/databases目錄下找到SQLite數據庫文件。
執行CRUD操作
當使用SQLiteOpenHelper的getReadableDatabase()或者getWritableDatabase()方法獲取到SQLiteDatabase對象,就可以對這個數據庫進行操作了。
對於熟悉SQL語句的開發者而言,其實只需要使用兩個方法,即可執行所有CRUD操作,以下方法提供多個重載方法:
- void execSQL():通過SQL語句執行一條非查詢語句。
- Cursor rawQuery():通過SQL語句執行一條查詢語句。
下面以一個示例講解一下單純使用SQL語句實現CRUD操作:
接口代碼:
1 package com.examle.sqlitedbdemo.service; 2 3 import java.util.List; 4 import java.util.Map; 5 6 public interface PersonService { 7 8 public boolean addPerson(Object[] params); 9 public boolean deletePerson(Object[] params); 10 public boolean updatePerson(Object[] params); 11 public Map<String, String> viewPerson(String[] selectionArgs); 12 public List<Map<String, String>> listPersonMaps(String[] selectionArgs); 13 }
接口的實現代碼:
1 package com.examle.sqlitedbdemo.dao; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7 8 import android.content.Context; 9 import android.database.Cursor; 10 import android.database.sqlite.SQLiteDatabase; 11 12 import com.examle.sqlitedbdemo.service.PersonService; 13 import com.example.sqlitedbdemo.db.DbOpenHelper; 14 15 public class PersonDao implements PersonService { 16 private DbOpenHelper helper = null; 17 18 public PersonDao(Context context) { 19 helper = new DbOpenHelper(context); 20 } 21 22 @Override 23 public boolean addPerson(Object[] params) { 24 boolean flag = false; 25 SQLiteDatabase database = null; 26 try { 27 // insert一條數據 28 String sql = "insert into person(name,address,sex) values(?,?,?)"; 29 database = helper.getWritableDatabase(); 30 // 執行SQL 31 database.execSQL(sql, params); 32 flag = true; 33 } catch (Exception e) { 34 e.printStackTrace(); 35 } finally { 36 if (database != null) { 37 // finally中關閉數據庫 38 database.close(); 39 } 40 } 41 return flag; 42 } 43 44 @Override 45 public boolean deletePerson(Object[] params) { 46 boolean flag = false; 47 SQLiteDatabase database = null; 48 try { 49 // 刪除一條數據 50 String sql = "delete from person where id=?"; 51 database = helper.getWritableDatabase(); 52 database.execSQL(sql, params); 53 flag = true; 54 } catch (Exception e) { 55 e.printStackTrace(); 56 } finally { 57 if (database != null) { 58 database.close(); 59 } 60 } 61 return flag; 62 } 63 64 @Override 65 public boolean updatePerson(Object[] params) { 66 boolean flag = false; 67 SQLiteDatabase database = null; 68 try { 69 // 更新一條數據 70 String sql = "update person set name=?,address=?,sex=? where id=?"; 71 database = helper.getWritableDatabase(); 72 // 執行SQL 73 database.execSQL(sql, params); 74 flag = true; 75 } catch (Exception e) { 76 e.printStackTrace(); 77 } finally { 78 if (database != null) { 79 database.close(); 80 } 81 } 82 return flag; 83 } 84 85 @Override 86 public Map<String, String> viewPerson(String[] selectionArgs) { 87 Map<String, String> map = new HashMap<String, String>(); 88 SQLiteDatabase database = null; 89 try { 90 // 查詢單條記錄 91 String sql = "select * from person where id=?"; 92 // 以只讀的形式打開數據庫 93 database = helper.getReadableDatabase(); 94 // 執行SQL語句,返回一個游標 95 Cursor cursor = database.rawQuery(sql, selectionArgs); 96 97 int colums = cursor.getColumnCount(); 98 while (cursor.moveToNext()) { 99 for (int i = 0; i < colums; i++) { 100 String cols_name = cursor.getColumnName(i); 101 String cols_value = cursor.getString(cursor 102 .getColumnIndex(cols_name)); 103 if (cols_value == null) { 104 cols_value = ""; 105 } 106 map.put(cols_name, cols_value); 107 } 108 } 109 } catch (Exception e) { 110 e.printStackTrace(); 111 } finally { 112 if (database != null) { 113 database.close(); 114 } 115 } 116 return map; 117 } 118 119 @Override 120 public List<Map<String, String>> listPersonMaps(String[] selectionArgs) { 121 List<Map<String, String>> list = new ArrayList<Map<String, String>>(); 122 String sql = "select * from person"; 123 SQLiteDatabase database = null; 124 try { 125 database = helper.getReadableDatabase(); 126 Cursor cursor = database.rawQuery(sql, selectionArgs); 127 int colums = cursor.getColumnCount(); 128 while (cursor.moveToNext()) { 129 Map<String, String> map = new HashMap<String, String>(); 130 for (int i = 0; i < colums; i++) { 131 String cols_name = cursor.getColumnName(i); 132 String cols_value = cursor.getString(cursor 133 .getColumnIndex(cols_name)); 134 if (cols_value == null) { 135 cols_value = ""; 136 } 137 map.put(cols_name, cols_value); 138 } 139 list.add(map); 140 } 141 } catch (Exception e) { 142 e.printStackTrace(); 143 } finally { 144 if (database != null) { 145 database.close(); 146 } 147 } 148 return list; 149 } 150 }
再寫一個測試類測試這個數據操作類是否有效,Android下JUnit的配置參見另外一篇博客:Android--JUnit單元測試:
1 package com.example.sqlitedbdemo.db; 2 3 import java.util.List; 4 import java.util.Map; 5 6 import com.examle.sqlitedbdemo.dao.PersonDao; 7 import com.examle.sqlitedbdemo.service.PersonService; 8 9 import android.test.AndroidTestCase; 10 import android.util.Log; 11 12 public class TestDb extends AndroidTestCase { 13 private final String TAG = "main"; 14 15 public TestDb() { 16 // TODO Auto-generated constructor stub 17 } 18 19 public void createDB() { 20 DbOpenHelper helper = new DbOpenHelper(getContext()); 21 helper.getWritableDatabase(); 22 } 23 24 public void insertDb() { 25 PersonService service = new PersonDao(getContext()); 26 Object[] params1 = { "張龍", "beijing", "male" }; 27 boolean flag = service.addPerson(params1); 28 Object[] params2 = { "趙虎", "shanghai", "male" }; 29 flag = flag&&service.addPerson(params2); 30 Object[] params3 = { "王朝", "HK", "male" }; 31 flag = flag&&service.addPerson(params3); 32 Object[] params4 = { "馬漢", "beijing", "female" }; 33 flag = flag&&service.addPerson(params4); 34 Log.i(TAG, "-----插入數據----->>" + flag); 35 } 36 37 public void deleteDb() { 38 PersonService service = new PersonDao(getContext()); 39 Object[] params = { 1 }; 40 boolean flag = service.deletePerson(params); 41 Log.i(TAG, "-----刪除數據----->>" + flag); 42 } 43 44 public void updateDb() { 45 PersonService service=new PersonDao(getContext()); 46 Object[] params = { "張三", "上海", "男","2" }; 47 boolean flag=service.updatePerson(params); 48 Log.i(TAG, "---------->>" + flag); 49 } 50 51 public void getDb(){ 52 PersonService service=new PersonDao(getContext()); 53 Map<String, String> map = service.viewPerson(new String[]{"2"}); 54 Log.i(TAG, "---------->>" + map.toString()); 55 } 56 57 public void listDb() { 58 PersonService service = new PersonDao(getContext()); 59 List<Map<String, String>> list = service.listPersonMaps(null); 60 Log.i(TAG, "---------->>" + list.toString()); 61 } 62 }
insertDB()后,如果是在模擬器上調試,可以使用FIle Explorer工具導出mydb.db文件,使用SQLite Expert Professional(這是一個SQLite的管理軟件,博客最后提供下載地址),打開數據庫:
執行deleteDb()刪除第一條數據:
執行updateDb()更新第二條數據:
執行getDb(),查詢第二條數據,執行listDb(),查詢全部數據,查看日志輸出:
而如果是從事Android開發,還有必要了解另外一種操作SQLite的方式,使用SQLiteDatabase所提供的方法實現CRUD操作。主要有以下幾個方法:
- long insert(String table ,String nullColumnHack,ContentValues values):插入一條數據。
- int delete(String table ,String whereCaluse,String[] whereArgs):根據條件,刪除數據。
- int updata(String table,ContentValues values,String whereCaluse,String[] whereArgs):根據條件,更新數據
- Cursor query(...):根據條件,查詢數據。提供多種重載方法,主要查詢不同的條件。
下面以一個示例程序講解一下使用SQLiteDatabase所提供的方法實現CRUD操作:
接口代碼:
1 package com.examle.sqlitedbdemo.service; 2 3 import java.util.List; 4 import java.util.Map; 5 6 import android.content.ContentValues; 7 8 public interface PersonService2 { 9 10 public boolean addPerson(ContentValues values); 11 12 public boolean deletePerson(String whereClause, String[] whereArgs); 13 14 public boolean updatePerson(ContentValues values, String whereClause, 15 String[] whereArgs); 16 17 public Map<String, String> viewPerson(String selection, 18 String[] selectionArgs); 19 20 public List<Map<String, String>> listPersonMaps(String selection, 21 String[] selectionArgs); 22 }
實現代碼:
1 package com.examle.sqlitedbdemo.dao; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7 8 import android.content.ContentValues; 9 import android.content.Context; 10 import android.database.Cursor; 11 import android.database.sqlite.SQLiteDatabase; 12 13 import com.examle.sqlitedbdemo.service.PersonService2; 14 import com.example.sqlitedbdemo.db.DbOpenHelper; 15 16 public class PersonDao2 implements PersonService2 { 17 private DbOpenHelper helper = null; 18 19 public PersonDao2(Context context) { 20 helper = new DbOpenHelper(context); 21 } 22 23 @Override 24 public boolean addPerson(ContentValues values) { 25 boolean flag = false; 26 SQLiteDatabase database = null; 27 long id = -1; 28 try { 29 database = helper.getWritableDatabase(); 30 // 執行insert,返回當前行ID 31 id = database.insert("person", null, values); 32 flag = (id != -1 ? true : false); 33 } catch (Exception e) { 34 e.printStackTrace(); 35 } finally { 36 if (database != null) { 37 database.close(); 38 } 39 } 40 return flag; 41 } 42 43 @Override 44 public boolean deletePerson(String whereClause, String[] whereArgs) { 45 boolean flag = false; 46 SQLiteDatabase database = null; 47 int count = 0; 48 try { 49 database = helper.getWritableDatabase(); 50 // 執行刪除操作,返回影響行數 51 count = database.delete("person", whereClause, whereArgs); 52 flag = (count > 0 ? true : false); 53 } catch (Exception e) { 54 e.printStackTrace(); 55 } finally { 56 if (database != null) { 57 database.close(); 58 } 59 } 60 return flag; 61 } 62 63 @Override 64 public boolean updatePerson(ContentValues values, String whereClause, 65 String[] whereArgs) { 66 boolean flag = false; 67 SQLiteDatabase database = null; 68 int count = 0; 69 try { 70 database = helper.getWritableDatabase(); 71 // 執行更新操作,返回影響行數 72 count = database.update("person", values, whereClause, whereArgs); 73 flag = (count > 0 ? true : false); 74 } catch (Exception e) { 75 e.printStackTrace(); 76 } finally { 77 if (database != null) { 78 database.close(); 79 } 80 } 81 return flag; 82 } 83 84 @Override 85 public Map<String, String> viewPerson(String selection, 86 String[] selectionArgs) { 87 SQLiteDatabase database = null; 88 Cursor cursor = null; 89 Map<String, String> map = new HashMap<String, String>(); 90 try { 91 database = helper.getReadableDatabase(); 92 // 設置查詢條件 93 cursor = database.query(true, "person", null, selection, 94 selectionArgs, null, null, null, null); 95 int cols_len = cursor.getColumnCount(); 96 while (cursor.moveToNext()) { 97 for (int i = 0; i < cols_len; i++) { 98 String cols_key = cursor.getColumnName(i); 99 String cols_value = cursor.getString(cursor 100 .getColumnIndex(cols_key)); 101 if (cols_value == null) { 102 cols_value = ""; 103 } 104 map.put(cols_key, cols_value); 105 } 106 } 107 } catch (Exception e) { 108 e.printStackTrace(); 109 } 110 return map; 111 } 112 113 @Override 114 public List<Map<String, String>> listPersonMaps(String selection, 115 String[] selectionArgs) { 116 List<Map<String, String>> list = new ArrayList<Map<String, String>>(); 117 SQLiteDatabase database = null; 118 Cursor cursor = null; 119 try { 120 database = helper.getReadableDatabase(); 121 cursor = database.query(false, "person", null, selection, 122 selectionArgs, null, null, null, null); 123 int cols_len = cursor.getColumnCount(); 124 while (cursor.moveToNext()) { 125 Map<String, String> map = new HashMap<String, String>(); 126 for (int i = 0; i < cols_len; i++) { 127 String cols_key = cursor.getColumnName(i); 128 String cols_value = cursor.getString(cursor 129 .getColumnIndex(cols_key)); 130 if (cols_value == null) { 131 cols_value = ""; 132 } 133 map.put(cols_key, cols_value); 134 } 135 list.add(map); 136 } 137 } catch (Exception e) { 138 e.printStackTrace(); 139 } 140 return list; 141 } 142 143 }
最后和上面一下,創建一個測試類來測試這個數據庫操作:
1 package com.example.sqlitedbdemo.db; 2 3 import java.util.List; 4 import java.util.Map; 5 6 import com.examle.sqlitedbdemo.dao.PersonDao2; 7 import com.examle.sqlitedbdemo.service.PersonService2; 8 9 import android.content.ContentValues; 10 import android.test.AndroidTestCase; 11 import android.util.Log; 12 13 public class TestDb2 extends AndroidTestCase { 14 private final String TAG = "main"; 15 16 public TestDb2() { 17 // TODO Auto-generated constructor stub 18 } 19 20 public void addPerson() { 21 PersonService2 service2 = new PersonDao2(getContext()); 22 ContentValues values1 = new ContentValues(); 23 values1.put("name", "張龍"); 24 values1.put("address", "beijing"); 25 values1.put("sex", "male"); 26 boolean flag = service2.addPerson(values1); 27 ContentValues values2 = new ContentValues(); 28 values2.put("name", "趙虎"); 29 values2.put("address", "shanghai"); 30 values2.put("sex", "male"); 31 flag = flag&&service2.addPerson(values2); 32 ContentValues values3 = new ContentValues(); 33 values3.put("name", "王朝"); 34 values3.put("address", "HK"); 35 values3.put("sex", "male"); 36 flag = flag&&service2.addPerson(values3); 37 ContentValues values4 = new ContentValues(); 38 values4.put("name", "王朝"); 39 values4.put("address", "HK"); 40 values4.put("sex", "male"); 41 flag = flag&&service2.addPerson(values4); 42 Log.i(TAG, "----------->>" + flag); 43 } 44 45 public void deletePerson() { 46 PersonService2 service2 = new PersonDao2(getContext()); 47 boolean flag = service2.deletePerson(" id =?", new String[]{"1"}); 48 Log.i(TAG, "----------->>" + flag); 49 } 50 51 public void updatePerson(){ 52 PersonService2 service2 = new PersonDao2(getContext()); 53 ContentValues values = new ContentValues(); 54 values.put("name", "張三"); 55 values.put("address", "上海"); 56 values.put("sex", "男"); 57 boolean flag=service2.updatePerson(values, " id=? ", new String[]{"2"}); 58 Log.i(TAG, "----------->>" + flag); 59 } 60 61 public void viewPerson(){ 62 PersonService2 service2 = new PersonDao2(getContext()); 63 Map<String, String> map=service2.viewPerson(" id=? ", new String[]{"2"}); 64 Log.i(TAG, "----------->>" + map.toString()); 65 } 66 public void listPerson(){ 67 PersonService2 service2 = new PersonDao2(getContext()); 68 List<Map<String, String>> list=service2.listPersonMaps(null,null); 69 Log.i(TAG, "----------->>" + list.toString()); 70 } 71 }
實現的功能和上面一樣,這里就不展示效果圖了,但是因為是上面兩種操作數據庫的方式是在一個應用中完成的,並且數據一樣,執行第二個測試類的時候,需要把之前創建的數據庫刪除,詳情參見源碼。
SQLite事務
SQLite的事務通過SQLiteDatabase中包含的兩個方法對其進行控制:beginTransaction(),開始事務;endTransaction(),結束事務。除此之外,SQLiteDatabase還提供了一個inTransaction()方法用來判斷當前上下文是否處於事務環境中。當程序執行endTransaction()方法時將會結束事務,到底是回滾事務還是提交事務取決於SQLiteDatabase是否調用了setTransactionSuccessful()方法來設置事務標志,如果程序事務執行中調用該方法設置了事務成功則提交事務,否則程序將回滾事務。
總結
上面就基本講解了SQLite在Android中的時候,雖然有兩種操作方式,並且直接使用SQL語句操作數據庫對於熟悉SQL語句的開發者開說,是非常貼心的,但是在Android中,還是有必要了解一下使用SQLiteDatabase提供的方法操作數據庫的方式,因為Android有一個內容提供者(Content Provider),可以使用外部應用訪問內部應用的數據,它傳遞數據的形式,大部分是與SQLiteDatabase內置方法的參數一致的,所以如果同一使用SQLiteDatabase提供的方法操作數據庫,是很方便的,無需額外轉換SQL語句。
SQLiteExpertSetup下載地址:part1、part2。
請支持原創,尊重原創,轉載請注明出處。謝謝。