在 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 WHEREclause:- 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 上有更多教程。




