Android的默认数据库引擎是精简版。SQLite是一个轻量级的事务数据库引擎,磁盘存储空间和内存占用都很少,因此在Android,IOS等手机操作系统上创建数据库,它是一个完美的选择。
在处理SQLite时需要考虑的事情:
1.数据类型完整性的维护不能依赖SQLite,你可以把某一数据类型的值放在另外一种数据类型的列中(例如把字符串存在整数列)。
2.引用完整性不能依赖SQLite,没有外键(FOREIGN KEY)和JOIN语句。
3.SQLite默认安装不支持完整的Unicode,需要选装。
在本教程中,我们将创建一个简单的应用程序商店的员工数据库:
表
1.Employees 员工表
2.Dept 部门表
视图
ViewEmps:显示员工和他们的相关部门。
创建SQLite数据库
默认情况下,Android中的SQLite没有管理界面或应用程序去创建和管理数据库,所以我们要用代码来创建数据库。
首先,我们将创建一个类来处理所需的所有操作,如创建数据库,创建表,插入和删除记录等数据库处理。
第一步是创建一个类,继承SQLiteOpenHelper类。这个类提供了覆盖处理与数据库的两种方法:
1. OnCreate中(SQLiteDatabase db) :创建数据库时调用,在这里我们可以创建表和列,创建视图或触发器。
2.onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):
当我们作出修改,如修改,删除,创建新的表的数据库调用。
我们的类有如下成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class DatabaseHelper extends SQLiteOpenHelper { static final String dbName= "demoDB" ; static final String employeeTable= "Employees" ; static final String colID= "EmployeeID" ; static final String colName= "EmployeeName" ; static final String colAge= "Age" ; static final String colDept= "Dept" ; static final String deptTable= "Dept" ; static final String colDeptID= "DeptID" ; static final String colDeptName= "DeptName" ; static final String viewEmps= "ViewEmps" ; |
构造函数
1 2 3 | public DatabaseHelper(Context context) { super (context, dbName, null , 33 ); } |
super类的构造函数有如下参数:
Context son: 连接到数据库的上下文
dbName:数据库的名称
CursorFactory:有时候,我们可能会使用Cursor类的扩展类来进行一些额外的验证或操作,实现对数据库运行查询。在这种情况下,我们传递CursorFactory的实例,返回要使用的默认游标,而不是返回到我们的派生类的引用。在这个例子中,我们将使用标准的游标接口,因此,我们将CursorFactory参数设置为null。
Version:数据库架构的版本。构造函数创建一个新的空白数据库时需要指定名称和版本。
创建数据库
第一个覆盖父类的方法是onCreate(SQLiteDatabase db):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub db.execSQL( "CREATE TABLE " +deptTable+ " (" +colDeptID+ " INTEGER PRIMARY KEY , " + colDeptName+ " TEXT)" ); db.execSQL( "CREATE TABLE " +employeeTable+" ( "+colID+" INTEGER PRIMARY KEY AUTOINCREMENT, "+ colName+ " TEXT, " +colAge+ " Integer, " +colDept+" INTEGER NOT NULL ,FOREIGN KEY ( "+colDept+" ) REFERENCES "+deptTable+" ( "+colDeptID+" ));"); db.execSQL( "CREATE TRIGGER fk_empdept_deptid " + " BEFORE INSERT " + " ON " +employeeTable+ " FOR EACH ROW BEGIN" + " SELECT CASE WHEN ((SELECT " +colDeptID+ " FROM " +deptTable+" WHERE "+colDeptID+" = new . "+colDept+" ) IS NULL)"+ " THEN RAISE (ABORT,'Foreign Key Violation') END;" + " END;" ); db.execSQL( "CREATE VIEW " +viewEmps+ " AS SELECT " +employeeTable+ "." +colID+ " AS _id," + " " +employeeTable+ "." +colName+ "," + " " +employeeTable+ "." +colAge+ "," + " " +deptTable+ "." +colDeptName+ "" + " FROM " +employeeTable+ " JOIN " +deptTable+ " ON " +employeeTable+ "." +colDept+ " =" +deptTable+ "." +colDeptID ); //Inserts pre-defined departments InsertDepts(db); } |
该函数创建含列的表,视图和触发器。该函数在数据库创建时被调用。当数据库在磁盘上不存在时该方法被
调用,它在应用程序首次运行时执行,同一设备只执行一次。
升级数据库
有时候,我们需要通过改变架构来升级数据库,然后添加新的表或改变列的数据类型。这些在onUpgrade
(SQLiteDatabase db,int old Version,int newVerison)函数中完成。
1 2 3 4 5 6 7 8 9 10 11 12 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub db.execSQL( "DROP TABLE IF EXISTS " +employeeTable); db.execSQL( "DROP TABLE IF EXISTS " +deptTable); db.execSQL( "DROP TRIGGER IF EXISTS dept_id_trigger" ); db.execSQL( "DROP TRIGGER IF EXISTS dept_id_trigger22" ); db.execSQL( "DROP TRIGGER IF EXISTS fk_empdept_deptid" ); db.execSQL( "DROP VIEW IF EXISTS " +viewEmps); onCreate(db); } |
该函数在构造函数中的版本号参数有所改变时被调用。
当我们想添加一些修改到我们的数据库中,我们必须更改构造函数中的版本号的数字。
因此我们可以将版本号2传递给构造函数用于代替版本1:
1 2 3 4 5 | public DatabaseHelper(Context context) { super (context, dbName, null , 2 ); // TODO Auto-generated constructor stub } |
原来:super(context, dbName, null,1);
应用程序会理解为你想升级数据库,onUpgrade函数被调用。该方法的一个典型应用是删除表,然后创建表
做相应修改。
管理外键约束
我们提到过,在此之前,由默认的SQLite 3不支持外键约束,但是我们可以强制使用触发器这样的约束:我们将创建一个触发器,确保插入一个新雇员时,他/她的部门值在原部门表中已经存在。创建一个触发器的SQL语句:
1 2 3 4 5 | CREATE TRIGGER fk_empdept_deptid Before INSERT ON Employees FOR EACH ROW BEGIN SELECT CASE WHEN ((SELECT DeptID FROM Dept WHERE DeptID = new .Dept ) IS NULL) THEN RAISE (ABORT, 'Foreign Key Violation' ) END; END |
在onCreate方法中,我们创建这样的触发器:
1 2 3 4 5 6 7 8 9 | db.execSQL( "CREATE TRIGGER fk_empdept_deptid " + " BEFORE INSERT " + " ON " +employeeTable+ " FOR EACH ROW BEGIN" + " SELECT CASE WHEN ((SELECT " +colDeptID+ " FROM " +deptTable+" _ WHERE "+colDeptID+" = new . "+colDept+" ) IS NULL)"+ " THEN RAISE (ABORT,'Foreign Key Violation') END;" + " END;" ); |
执行SQL语句
现在让我们开始执行基本的SQL语句。您可以执行任何SQL语句,这不是一个查询,无论是插入,删除,更新或任何使用db.execSQL(String statement)方法一样。当我们创建数据库表时可以这样:
1 2 | db.execSQL( "CREATE TABLE " +deptTable+ " (" +colDeptID+ " INTEGER PRIMARY KEY , " + colDeptName+ " TEXT)" ); |
插入记录
我们使用下面的代码在数据库Dept表中插入记录:
1 2 3 4 5 6 7 8 9 10 | SQLiteDatabase db= this .getWritableDatabase(); ContentValues cv= new ContentValues(); cv.put(colDeptID, 1 ); cv.put(colDeptName, "Sales" ); db.insert(deptTable, colDeptID, cv); cv.put(colDeptID, 2 ); cv.put(colDeptName, "IT" ); db.insert(deptTable, colDeptID, cv); db.close(); |
注意,我们需要调用this.getWritableDatabase()打开数据库连接进行读写。ContentValues.put有两个参数Column Name 和 value,在执行完语句后关闭数据库是一个很好的做法。
更新值
我们有两种方式执行一个update语句:
1. 执行 db.execSQL
2. 执行 db.update
1 2 3 4 5 6 7 8 9 10 | public int UpdateEmp(Employee emp) { SQLiteDatabase db= this .getWritableDatabase(); ContentValues cv= new ContentValues(); cv.put(colName, emp.getName()); cv.put(colAge, emp.getAge()); cv.put(colDept, emp.getDept()); return db.update(employeeTable, cv, colID+ "=?" , new String []{String.valueOf(emp.getID())}); } |
update方法有以下参数:
1. String Table: 需要更新的表
2. ContentValues cv: 新的值
3. String where clause: WHERE子句制定更新的记录
4. String[] args: WHERE 子句参数
删除行
我们有两种方式执行delete语句:
1. 执行 db.execSQL
2. 执行 db.delete方法:
1 2 3 4 5 6 | public void DeleteEmp(Employee emp) { SQLiteDatabase db= this .getWritableDatabase(); db.delete(employeeTable,colID+ "=?" , new String [] {String.valueOf(emp.getID())}); db.close(); } |
delete方法的参数和update方法一样。
执行查询
有两种方法执行查询:
1. 执行 db.rawQuery
2. 执行 db.query
执行一个原始的查询得到所有部门:
1 2 3 4 5 6 7 8 | Cursor getAllDepts() { SQLiteDatabase db= this .getReadableDatabase(); Cursor cur=db.rawQuery( "SELECT " +colDeptID+" as _id, "+colDeptName+" from "+deptTable, new String [] {}); return cur; } |
rawQuery方法有两个参数:
1. String query:select语句
2. String[] selection args:select语句中WHERE子句的参数。
注意
1.查询结果返回游标对象。
2.在一个 select 语句中,如果表的主键列(ID列)有一个非_id的名称,那么你必须使用SELECT [列名] as _id,因为游标对象总是认为主键列的名称为_id,否则会抛出一个异常。
执行查询的另一种方法是,使用db.query方法。从视图中查询选择某个部门的所有员工:
1 2 3 4 5 6 7 8 | public Cursor getEmpByDept(String Dept) { SQLiteDatabase db= this .getReadableDatabase(); String [] columns= new String[]{ "_id" ,colName,colAge,colDeptName}; Cursor c=db.query(viewEmps, columns, colDeptName+ "=?" , new String[]{Dept}, null , null , null ); return c; } |
db.query有如下参数:
1. String Table Name: 数据表名称
2. String [ ] columns: 需要查询的列
3. String WHERE clause: where 子句,如果没有,可以传递null
4. String [ ] selection args: WHERE子句的参数
5. String Group by: 指定group by 子句
6. String Having: 指定HAVING子句
7. String Order By by: 指定Order By子句
游标管理
查询记录集返回在Cursor对象里,使用游标时有一些公共的方法:
boolean moveToNext(): 在记录集里将游标移到下一条记录,如果超过了最后一行则返回false.
boolean moveToFirst(): 将游标移到第一行,如果记录集为空则返回false.
boolean moveToPosition(int position): 移动游标到指定位置,如果位置不可达,则返回false
boolean moveToPrevious(): 将游标移到上一行,如果超过了第一行则返回false
boolean moveToLast(): 将游标移到最后一行,如果记录集为空则返回false.
还有一些检查游标位置的有用方法:boolean isAfterLast(), isBeforeFirst, isFirst, isLast 和 isNull(columnIndex)。此外,如果是只有一行的结果集,你需要检索的某些列的值,你可以这样做:
1 2 3 4 5 6 7 8 9 10 | public int GetDeptID(String Dept) { SQLiteDatabase db= this .getReadableDatabase(); Cursor c=db.query(deptTable, new String[]{colDeptID+ " as _id" ,colDeptName}, colDeptName+ "=?" , new String[]{Dept}, null , null , null ); //Cursor c=db.rawQuery("SELECT "+colDeptID+" as _id FROM "+deptTable+" //WHERE "+colDeptName+"=?", new String []{Dept}); c.moveToFirst(); return c.getInt(c.getColumnIndex( "_id" )); } |
我们用 Cursor.getColumnIndex(String ColumnName) 得到列的索引号,然后通过Cursor.getInt(int ColumnIndex)得到列的值。
还有,可以用getShort, getString, getDouble, getBlob得到字节数组的值。使用完游标后用close()关闭是一个好习惯。