Android內置了一個名為SQLite的關系型數據庫,這是一款輕量型的數據庫,操作十分簡便。SQLite與別的數據庫不同的是,它沒有數據類型。可以保存任何類型的數據到你所想要保存的任何表的任何列中。但它又支持常見的類型比如: NULL, VARCHAR, TEXT, INTEGER, BLOB, CLOB...等。
唯一的例外是:integer primary key 此字段只能存儲64位整數。
在JAVA項目中,要使用JDBC操作數據庫需要加載數據庫驅動,連接數據庫等操作。Android簡化了我們的數據庫操作,無需由我們進行數據庫驅動加載、連接等操作。
Android中進行數據庫操作,需要涉及到如下幾個類:
1)SQLiteOpenHelper:
在android.database.sqlite包下,這是一個抽象的幫助類,用於管理數據庫的創建及版本維護操作。
我們在需要獲取該類的對象時,需要自定義類繼承自SQLiteOpenHelper類,並實現其中的onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int),可以選擇性地實現 onOpen(SQLiteDatabase)。這個類會自動幫助我們在需要時打開數據庫,在不存在時創建數據庫,在必要時更新數據庫。
常用方法:
一般,在自定義的子類中調用父類中第一個構造方法即可。
構造函數的參數說明:
context:應用的上下文對象
name:要操作的數據庫的名稱
factory:cursor工廠類對象,一般指定為null
version:數據庫的版本號,必須大於等於1,由於控制數據庫的升級。
注意到,只有onCreate()和onUpgrade()是抽象方法,所以自定義子類繼承SQLiteOpenHelper時,一定要實現這兩個方法。
其中:
onCreate()方法會在數據庫不存在,第一次創建時調用,所以數據庫中的初始化操作,如創建表等操作需要在該方法中完成。
onUpgrade()方法,新的版本號比原來有提升時,調用,用以完成數據庫的升級操作,如新版本的app中,需要添加一張表,或者修改某個表,就需要在新版本的app創建SQLiteOpenHelper對象時,向其構造函數傳入一個更大的版本號,這個版本號會被newVersion接收。
getReadaleDatabase()方法,創建或打開一個數據庫,返回代表該數據庫的只讀的SQLiteDatabase對象
getWritableDatabase()方法,創建或打開一個數據庫,返回代表該數據庫的可讀可寫的SQLiteDatabase對象。
close()方法,用於關閉打開的數據庫對象。
2)SQLiteDatabase:
通過SQLiteOpenHelper對象獲取SQLiteDatabase對象后,便可以調用SQLiteDatabase類的相關方法進行數據庫的增刪改查操作了。
該類的常用方法有:
execSQL()方法用於執行SQL語句,可以用於執行不需要返回值的一些數據庫操作。
rawQuery()方法一般被用於執行需要返回值的查詢操作,查詢的結果保存在Cursor對象中。
除了直接執行SQL語句進行數據庫操作的方法之外,該類還封裝幾個更易用的增刪改查方法。
insert()方法,用於向數據庫中插入數據,參數說明:
table:指定要插入數據的表明
nullColumnHack:一般指定為null即可
注意:當values也為null,表示想向數據庫中插入一條空記錄時,該方法實際執行的SQL語句為insert into table(null) values(null);這樣一條sql語句是沒法執行的,所以,需要將nullColumnHack指定為任意一個可以為空的字段的字段名。如:nullColumnHack指定為”name”,values為null,此時SQL語句為insert into table(name) values(null);則可以正常執行了。
values:是一個ContentValues對象,用於存放要插入的各個字段名與值的對應關系。
delete()方法,用於刪除表中的記錄,參數說明:
table指定要操作的表名
whereClause指定where字句,可以包含占位符”?”,如“name = ?”,實際執行時,占位符會被第三個參數中對應索引的值替換
whereArgs:指定where字句中,占位符對應的值,如new String[]{“zhangsan”}
實際執行的語句就是delete from table where name = “zhangsan”
update()方法,用於執行表中記錄的更新操作,參數說明:
table,whereClause,whereArgs與delete相同含義
values用於指定對應字段名要更新的值的映射關系。
query()記錄查詢方法,最簡單的一個重載形式也有七個參數,都是select中的各個字句部分,如where子句部分,group by子句部分,having,order by等。參數說明:
table指定要查詢的表名
colmns指定要查詢的字段
selection:可以帶占位符的where子句部分
slectionArgs:where子句占位符對應的值
groupBy:group by子句部分
having:having子句部分
orderBy:order by 子句部分
等等
isOpen()判斷數據庫是否已打開。
beginTransaction()用於開啟事務操作
setTransactionSuccessful()方法用於標記事務操作成功
endTransaction()方法用於關閉事務,若事務標記成功,則提交事務操作,否則,則回滾失敗的事務操作。
其他方法,在需要時,可以查詢API幫助手冊。
3)Cursor:
Cursor是一個接口,其實現類用於存放SQL語句查詢的結果集。SQLiteDatabase的rawQuery()及query()方法均會返回該接口的對象,用以存儲操作查詢返回的結果集。
Cursor對象維持一個游標,默認指向結果集第一條記錄之前的位置,可以通過下面幾個方法,來移動游標,從而取得需要的記錄。當游標已在第一條記錄之前,調用moveToPreious(),或者已指向最后一條記錄,調用moveToNext()方法時,均會返回false()標識移動失敗。
getShort()、getString()、getInt()等getXXX()方法,用於根據當前記錄的字段的索引獲取字段的值。
getColumnName()用於根據索引獲取字段名
getColumnNames()用於獲取所有的字段名,其順序與在Cursor中保存的順序相同。
getCount()用於獲取當前Cursor對象中保存的記錄數。
getColumnCount()方法用於獲取記錄的字段總數
getColumnIndex()用於根據字段名獲取其索引,不存在時返回-1。
close()方法,用於關閉當前Cursor對象。
4)ContentValues:
該類用於存放鍵值對的數據,在數據庫的插入更新等操作中,可以使用字段名作為鍵,使用要插入或更新的字段值作為值。
常用的是put()方法,向ContentValues對象中存儲數據。
如:put(“name”,”zhangsan”);等
以上便是數據庫操作所涉及的幾個主要的類和接口。
下面通過一個具體的代碼,來學習使用這些個API:
第一步:
自定義類繼承SQLiteOpenHelper類,實現onCreate()和onUpgrade()方法:
1 public class MySqliteHelper extends SQLiteOpenHelper { 2 3 public static final String TAG = "MYSQLITEHELPER"; 4 5 public static final String CREATE_STUDENT = "create table t_student (" + 6 7 "id integer primary key, name varchar(20), gender varchar(10), age integer)"; 8 9 public MySqliteHelper(Context context, String name, CursorFactory factory, 10 11 int version) { 12 13 super(context, name, factory, version); 14 15 } 16 17 @Override 18 19 public void onOpen(SQLiteDatabase db) { 20 21 Log.i(TAG,"open db"); 22 23 super.onOpen(db); 24 25 } 26 27 @Override 28 29 public void onCreate(SQLiteDatabase db) { 30 31 Log.i(TAG,"create db"); 32 33 Log.i(TAG,"before excSql"); 34 35 db.execSQL(CREATE_STUDENT); 36 37 Log.i(TAG,"after excSql"); 38 39 } 40 41 @Override 42 43 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 44 45 46 } 47 48 }
第二步,定義實體類:
1 package cn.csc.sqlite.bean; 2 3 4 5 public class Student { 6 7 private int id; 8 9 private String name; 10 11 private String gender; 12 13 private int age; 14 15 16 17 public int getId() { 18 19 return id; 20 21 } 22 23 public void setId(int id) { 24 25 this.id = id; 26 27 } 28 29 public String getName() { 30 31 return name; 32 33 } 34 35 public void setName(String name) { 36 37 this.name = name; 38 39 } 40 41 public String getGender() { 42 43 return gender; 44 45 } 46 47 public void setGender(String gender) { 48 49 this.gender = gender; 50 51 } 52 53 public int getAge() { 54 55 return age; 56 57 } 58 59 public void setAge(int age) { 60 61 this.age = age; 62 63 } 64 65 66 67 public Student() { 68 69 super(); 70 71 } 72 73 public Student(int id, String name, String gender, int age) { 74 75 super(); 76 77 this.id = id; 78 79 this.name = name; 80 81 this.gender = gender; 82 83 this.age = age; 84 85 } 86 87 @Override 88 89 public String toString() { 90 91 return "Student [id=" + id + ", name=" + name + ", gender=" + gender 92 93 + ", age=" + age + "]"; 94 95 } 96 97 }
第三步,定義數據庫操作類:
1 public class StudentDao { 2 3 private SQLiteOpenHelper helper; 4 5 private Context context; 6 7 public StudentDao(Context context){ 8 9 this.context = context; 10 11 } 12 13 public void insert(Student stu){ 14 15 helper = new MySqliteHelper(context,"students.db", null, 1); 16 17 Log.i("MYSQLITEHELPER","before get db"); 18 19 SQLiteDatabase db = helper.getWritableDatabase(); 20 21 Log.i("MYSQLITEHELPER","after get db"); 22 23 db.execSQL("insert into t_student(name, gender, age) values(?,?,?)" , new Object[]{stu.getName(),stu.getGender(),stu.getAge()}); 24 25 db.close(); 26 27 } 28 29 }
第四步,定義測試類,測試StudentDao的insert()方法:
1 public class TestDao extends AndroidTestCase { 2 3 public void testInsert(){ 4 5 StudentDao dao = new StudentDao(getContext()); 6 7 dao.insert(new Student(0,"zhangsan", "male", 23)); 8 9 } 10 11 }
運行該測試方法,運行結果如下:
注意到,定義SQLiteOpenHelper對象,並不會創建數據庫,只有調用getWritableDatabase()或者getReadableDatabase()才會調用onCreate()方法,onCreate()內部執行了execSQL()方法,但是並沒有調用onOpen()方法。onCreate()方法執行完成后,才接着調用了onOpen()方法。
從File Explorer中可以看出,students.db存放在/data/data/應用包名/databases/下。
再次運行,由於數據庫已經存在,onCreate()方法就沒再被調用。
可以通過sqlite3命令來查看數據庫內容:
sqlite的簡單使用說明:
adb shell掛載虛擬機控制台
cd /data/data/cn.csc.sqlite/databases進入數據庫文件所在目錄
sqlite3 數據庫文件名: 管理數據庫文件名所指定的數據庫,如sqlite3 students.db,然后光標變為sqlite>,表示進入sqlite操作模式。
.tables 查看數據庫中所有的表
可以直接輸入sql語句並執行
.exit 退出sqlite操作模式
.schema 查看查看庫中所有表的DDL語句
.mode list|column|insert|line|tabs|tcl|csv 改變輸出格式
若覺得命令行用着不習慣,可以從File Explorer中把數據庫文件導出到電腦中,然后使用圖形化工具,如sqlite expert來查看管理數據庫。
下載地址:http://pan.baidu.com/s/1jG8G7QY
StudentDao中添加getAllStudents()方法:
1 public List<Student> getAllStudents(){ 2 3 List<Student> list = new ArrayList<Student>(); 4 5 helper = new MySqliteHelper(context,"students.db", null, 1); 6 7 SQLiteDatabase db = helper.getWritableDatabase(); 8 9 Cursor cursor = db.rawQuery("select id,name,gender,age from t_student", null); 10 11 if(cursor == null){ 12 13 return null; 14 15 } 16 17 while(cursor.moveToNext()){ 18 19 Student stu = new Student(cursor.getInt(0),cursor.getString(1),cursor.getString(2),cursor.getInt(3)); 20 21 Log.i("MYSQLITEHELPER",stu.toString()); 22 23 list.add(stu); 24 25 } 26 27 28 29 return list; 30 31 }
測試,輸出:
上面用的都是直接寫完整的SQL語句,下面添加幾個方法,調用SQLiteDatabase封裝的幾個簡單操作的API:
update()方法的使用
StudentDao類添加:修改指定id的學生的姓名:
1 public void updateNameById(int id, String newName){ 2 3 helper = new MySqliteHelper(context,"students.db", null, 1); 4 5 SQLiteDatabase db = helper.getWritableDatabase(); 6 7 ContentValues values = new ContentValues(); 8 9 values.put("name", newName); 10 11 db.update("t_student", values, "id=?", new String[]{id+""}); 12 13 }
測試代碼:
1 public void testUpdate(){ 2 3 StudentDao dao = new StudentDao(getContext()); 4 5 dao.updateNameById(1, "dqrcsc"); 6 7 }
運行前后:
delete()方法的使用:
StudentDao類添加:刪除指定id的學生
1 public void deleteById(int id){ 2 3 helper = new MySqliteHelper(context,"students.db", null, 1); 4 5 SQLiteDatabase db = helper.getWritableDatabase(); 6 7 db.delete("t_student", "id=?", new String[]{id+""}); 8 9 }
測試代碼:
1 public void testDelete(){ 2 3 StudentDao dao = new StudentDao(getContext()); 4 5 dao.deleteById(2); 6 7 }
運行前后:
insert()方法的使用:
StudentDao類中添加如下方法:
1 public void addStudent(Student stu){ 2 3 helper = new MySqliteHelper(context,"students.db", null, 1); 4 5 SQLiteDatabase db = helper.getWritableDatabase(); 6 7 ContentValues values = new ContentValues(); 8 9 values.put("name", stu.getName()); 10 11 values.put("gender", stu.getGender()); 12 13 values.put("age", stu.getAge()); 14 15 db.insert("t_student", null, values); 16 17 }
測試代碼:
1 public void testAddStudent(){ 2 3 StudentDao dao = new StudentDao(getContext()); 4 5 dao.addStudent(new Student(0,"csc","male",24)); 6 7 }
運行前后:
query()方法的使用:
1 public Student getStudentById(int id){ 2 3 Student stu = null; 4 5 helper = new MySqliteHelper(context,"students.db", null, 1); 6 7 SQLiteDatabase db = helper.getWritableDatabase(); 8 9 Cursor cursor = db.query("t_student", new String[]{"id","name","gender","age"}, "id=?", new String[]{id+""}, null, null, null); 10 11 if(cursor == null){ 12 13 return null; 14 15 } 16 17 if(cursor.moveToFirst()){ 18 19 stu = new Student(cursor.getInt(0),cursor.getString(1),cursor.getString(2),cursor.getInt(3)); 20 21 } 22 23 return stu; 24 25 }
測試代碼:
1 public void testGetStudentById(){ 2 3 StudentDao dao = new StudentDao(getContext()); 4 5 Student stu = dao.getStudentById(1); 6 7 Log.i("MYSQLITEHELPER",stu.toString()); 8 9 }
輸出:
關於事務的操作:
被用爛來的例子,就是銀行轉賬問題,一個賬戶轉出,一個賬戶轉入,兩個操作要么同時成功,要么同時失敗。
這里懶得再建一張表了,就轉年齡吧,其實換湯不換葯,原理完全一樣。
假設年齡可以在學生之間轉換,我要把自己的年齡轉10歲給lisi這個同學,要保證這整個操作的原子性,就需要用到事務。
在StudentDao中添加轉賬年齡的方法:
1 public void transAge(){ 2 3 helper = new MySqliteHelper(context,"students.db", null, 1); 4 5 SQLiteDatabase db = helper.getWritableDatabase(); 6 7 db.execSQL("update t_student set age = age - 10 where name = ?", new String[]{"dqrcsc"}); 8 9 int i = 1/0; 10 11 db.execSQL("update t_student set age = age + 10 where name = ?", new String[]{"lisi"}); 12 13 }
注意兩條update語句之間有個1/0的操作,會導致程序異常終止,只有第一條被執行。
執行前后:
可以看到,我的年齡減去10歲,而lisi的年齡並沒有加上10歲。
修改代碼,改用事務處理:
1 public void transAge(){ 2 3 helper = new MySqliteHelper(context,"students.db", null, 1); 4 5 SQLiteDatabase db = helper.getWritableDatabase(); 6 7 db.beginTransaction(); 8 9 try{ 10 11 db.execSQL("update t_student set age = age - 10 where name = ?", new String[]{"dqrcsc"}); 12 13 int i = 1/0; 14 15 db.execSQL("update t_student set age = age + 10 where name = ?", new String[]{"lisi"}); 16 17 db.setTransactionSuccessful(); 18 19 }finally{ 20 21 db.endTransaction(); 22 23 } 24 25 }
這一次,我的年齡沒有減少,lisi的年齡也沒有增加,保證了一致性。
關於更新數據庫版本的簡單示例:
如,由於業務需要,更新了APP,新版本的APP在數據庫中增加了一張教師表,現在修改onCreate()方法:
1 public static final String CREATE_TEACHER = "create table t_teacher(id integer primary key, name varchar(20))"; 2 3 public void onCreate(SQLiteDatabase db) { 4 5 // TODO Auto-generated method stub 6 7 db.execSQL(CREATE_STUDENT); 8 9 db.execSQL(CREATE_TEACHER); 10 11 }
運行,發現根本沒有增加t_teacher這張表,因為數據庫已存在,onCreate()方法不會被運行,這時,就需要用到onUpgrade()方法了:
1 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 2 3 // TODO Auto-generated method stub 4 5 if(oldVersion == 1 && newVersion == 2){ 6 7 db.execSQL(CREATE_TEACHER); 8 9 } 10 11 }
在StudentDao中添加addTeacher()方法:
1 public void addTeacher(){ 2 3 helper = new MySqliteHelper(context,"students.db", null, 2); 4 5 SQLiteDatabase db = helper.getWritableDatabase(); 6 7 db.execSQL("insert into t_teacher(name) values(?)",new String[]{"wanger"}); 8 9 }
注意,這里指定的版本號為2。
測試運行結果:
數據庫操作的學習,就簡單學到這里。