在 Android 中使用 SQLite 数据库
关于在 Android 中使用 SQLite 的教程。
引言
Android 的默认数据库引擎是 Lite。SQLite 是一个轻量级的事务性数据库引擎,占用磁盘存储和内存空间少,因此它是创建 Android、iOS 等许多移动操作系统数据库的理想选择。
处理 SQLite 时需要考虑的事项
- SQLite 不维护数据类型完整性,您可以将某种数据类型的值放入另一种数据类型的列中(例如,在整数列中放入字符串,反之亦然)。
- SQLite 不维护引用完整性,没有
FOREIGN KEY
约束或JOIN
语句。 - SQLite 完全支持 Unicode 是可选的,默认不安装。
在本教程中,我们将创建一个简单的数据库应用程序来存储员工数据。数据库包含
表格
员工
部门
视图
ViewEmps
:用于显示员工及其相关部门。
创建 SQLite 数据库
默认情况下,Android 上的 SQLite 没有管理界面或应用程序来创建和管理数据库,因此我们将通过代码自行创建数据库。首先,我们将创建一个类来处理与数据库相关的所有操作,例如创建数据库、创建表、插入和删除记录等。第一步是创建一个继承自 SQLiteOpenHelper
类的类。此类提供了两个需要重写的方法来处理数据库
onCreate(SQLiteDatabase db)
:数据库创建时调用,我们可以在这里创建表和列,创建视图或触发器。onUpgrade(SQLiteDatabse db, int oldVersion, int newVersion)
:当我们对数据库进行修改时调用,例如修改、删除、创建新表。
我们的类将具有以下成员
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";
构造函数
public DatabaseHelper(Context context) {
super(context, dbName, null,33);
}
超类的构造函数具有以下参数
Context con
:附加到数据库的上下文dataBaseName
:数据库的名称CursorFactory
:有时,我们可能会使用一个扩展Cursor
类的类来实现一些额外的验证或对数据库运行的查询进行操作。在这种情况下,我们传递一个CursorFactory
的实例来返回我们派生类的引用,以代替默认游标使用。在此示例中,我们将使用标准的 Cursor 接口检索结果,因此CursorFactory
参数将为null
。Version
:数据库模式的版本。构造函数使用指定的名称和版本创建一个新的空白数据库。
创建数据库
第一个需要重写的超类方法是 onCreate(SQLiteDatabase db)
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);
}
该方法创建带有列的表、一个视图和一个触发器。数据库创建时会调用此方法。因此,我们创建表并指定列。当数据库在磁盘上不存在时调用此方法,它在设备上仅执行一次,当应用程序首次在该设备上运行时。
升级数据库
有时,我们想通过更改模式、添加新表或更改列数据类型来升级数据库。这可以通过重写 onUpdate(SQLiteDatabase db,int old Version,int newVerison)
方法来完成
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
public DatabaseHelper(Context context) {
super(context, dbName, null,2);
// TODO Auto-generated constructor stub
}
而不是 1
super(context, dbName, null,2);
应用程序会识别您要升级数据库,并且会调用 onUpgrade
方法。此方法的典型实现是删除表然后用额外的修改重新创建它们。
管理外键约束
我们之前提到 SQLite 3 默认不支持外键约束,但是我们可以通过使用 TRIGGERS 来强制执行此类约束:我们将创建一个触发器,以确保在插入新 Employee
时,其 Dept
值存在于原始 Dept
表中。创建此类触发器的 SQL 语句如下
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
方法中,我们像这样创建了这个触发器
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;");
更新
您可以通过重写 onOpen 方法来启用数据库中的外键,从而强制执行引用完整性,如下所示
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
if (!db.isReadOnly()) {
// Enable foreign key constraints
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
执行 SQL 语句
现在我们开始执行基本的 SQL 语句。您可以使用 db.execSQL(String statement)
方法执行任何 非查询 SQL 语句,无论是 insert
、delete
、update
还是其他任何语句,就像我们在创建数据库表时那样
db.execSQL("CREATE TABLE "+deptTable+" ("+colDeptID+ " INTEGER PRIMARY KEY , "+
colDeptName+ " TEXT)");
插入记录
我们使用以下代码将记录插入数据库,例如将记录插入 Dept
表
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
有两个参数:列名
和要插入的 值
。另外,在执行语句后关闭数据库是一个好习惯。
更新值
要执行 update
语句,我们有两种方法
- 执行
db.execSQL
- 执行
db.update
方法
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
方法具有以下参数
String Table
:要更新值的表ContentValues cv
:包含新值的 content values 对象String where clause
:用于指定要更新哪个记录的WHERE
子句String[] args
:WHERE
子句的参数
删除行
与 update
类似,要 execute
一个 delete
语句,我们有两种方法
- 执行
db.execSQL
- 执行
db.delete
方法
public void DeleteEmp(Employee emp)
{
SQLiteDatabase db=this.getWritableDatabase();
db.delete(employeeTable,colID+"=?", new String [] {String.valueOf(emp.getID())});
db.close();
}
delete
方法与 update
方法具有相同的参数。
执行查询
要执行查询,有两种方法
- 执行
db.rawQuery
方法 - 执行
db.query
方法
执行原始查询以检索所有部门
Cursor getAllDepts()
{
SQLiteDatabase db=this.getReadableDatabase();
Cursor cur=db.rawQuery("SELECT "+colDeptID+" as _id,
"+colDeptName+" from "+deptTable,new String [] {});
return cur;
}
rawQuery
方法有两个参数
String query
:select
语句String[] selection args
:如果select
语句中包含WHERE
子句,则为参数
注释
- 查询结果返回在
Cursor
对象中。 - 在
select
语句中,如果表的主键列(id 列)的名称不是_id
,则您必须使用别名,形式为SELECT
[列名] as_id
,因为Cursor
对象总是期望主键列的名称为_id
,否则会抛出异常。
执行查询的另一种方法是使用 db.query
方法。从视图中选择某个部门的所有员工的查询如下所示
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
具有以下参数
String Table Name
:运行查询的表名String [ ]
columns:查询的投影,即要检索的列String WHERE
clause:where
子句,如果没有则传递null
String [ ]
selection args:WHERE
子句的参数String Group by
:指定 group by 子句的字符串String Having
:指定HAVING
子句的字符串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)
。另外,如果您有一个只有一行结果集并且需要检索特定列的值,可以这样做
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
方法用于将值作为字节数组返回。在使用完游标后关闭它是一个好习惯。
在此 处 下载一个关于 Android 数据库使用示例的演示应用程序。
我的博客 http://android-pro.blogspot.com 上有更多教程。