一、SQLite:
(一)、简介:
除了可以使用文件或SharedPreferences存储数据,还可以选择使用SQLite数据库存储数据。
在Android平台上,集成了一个嵌入式关系型数据库——SQLite。
SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)等五种数据类型; 虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等数据类型,只不过在运算或保存时会转成对应的五种数据类型。
SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)等五种数据类型; 虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等数据类型,只不过在运算或保存时会转成对应的五种数据类型。
(二)、SQLite数据库的特点:
- Android通过 SQLite 数据库引擎来实现结构化数据的存储。在一个数据库应用程序中,任何类都可以通过名字对已经创建的数据库进行访问,但是在应用程序之外就不可以。
- SQLite 数据库是一种用C语言编写的嵌入式数据库,它是一个轻量级的数据库,最初为嵌入式设计的。它是在一些基础简单的语句处理上要比oracle / mysql快很多,而且其对内存的要求很低,在内存中只需要几百KB的存储空间。这是Android中采用 SQLite 数据库的主要原因。
- SQLite 支持事务处理功能。Transaction
- SQLite 处理速度比MySQL等著名的开源数据库系统更快。它没有服务器进程。
- SQLite 通过文件保存数据库,该文件是跨平台的,可以自由复制。一个文件就是一个数据库。数据库名即文件名。
- JDBC会消耗太多系统资源,所以JDBC对于手机并不合适,因此Android提供了新的API来使用 SQLite 数据库。
- SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用关心字段声明的数据类型是什么。 例如:可以在Integer类型的字段中存放字符串,或者在布尔型字段中存放浮点数,或者在字符型字段中存放日期型值。
有一种情况例外:
定义为INTEGER PRIMARY KEY的字段只能存储64位整数, 当向这种字段保存除整数以外的数据时,将会产生错误。 另外, SQLite 在解析CREATE TABLE 语句时,会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息,如下面语句会忽略 name字段的类型信息:
CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))
(二)、sqlite3工具:
.help
.databases
.tables
.schema
drop table 表名
save FILE
.exit
dynamic datatype
null , integer ,real ,text , blob(二进制大数据对象)
boolean 使用integer的0和1代替
时间可以使用text或者real类型
alter 表名 rename to
alter 表名 add column
二、SQLiteDatabase类:
SQLiteDatabase等同于JDBC中Connection和Statement的结合体。SQLiteDatabase既代表与数据库的连接,又只能用于执行sql语句操作。
(一)、操作 SQLite 数据库的步骤:【重要】
1、创建 SQLiteDatabase 对象,它代表与数据库的连接;
2、创建数据库中的表(执行CREATE);
3、调用SQLiteDatabase 对象执行数据库操作(执行DML);
4、对查询后的结果集Cursor进行处理。
(二)、操作 SQLite 数据库的步骤详细讲解:
1、创建 SQLite 数据库对象:SQLiteDatabase对象代表一个数据库。 SQLiteDatabase提供了几个静态方法来打开一个文件对应的数据库。
- static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags, DatabaseErrorHandler errorHandler)
- static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)
- static SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler)
- static SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory)
【备注:】打开数据库的参数:
1、SQLiteDatabase的构造方法是private,(单例模式吗?)所以只能通过openDatabase()来连接数据库;
2、参数中的path代表着数据库的路径(如果是在默认路径/data/data/<package_name>/databases/下,则这里只需要提供数据库名称);
3、factory代表着在创建Cursor对象时,使用的工厂类,如果为null的话,则使用默认的工厂(这里我们可以实现自己的工厂进行某些数据处理);
4、flags代表的是创建表时的一些权限设置,多个权限之间用|分隔:
- OPEN_READONLY :代表的是以只读方式打开数据库(常量值为:1)
- OPEN_READWRITE:代表以读写方式打开数据库(常量值为:0)
- CREATE_IF_NECESSARY:当数据库不存在时创建数据库
- NO_LOCALIZED_COLLATORS:打开数据库时,不根据本地化语言对数据库进行排序(常量值为:16)
【核心示例代码:】
//当数据库存在时:
String path = SDCardHelper.getSDCardRoot() + File.separator + "steven" + File.separator + "android_manual.db";
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null,SQLiteDatabase.OPEN_READWRITE);
Cursor cursor = db.rawQuery("select * from android_basic order by id desc limit 0 , 15",null);
//当数据库不存在时:
String path = SDCardHelper.getSDCardRoot() + File.separator + "steven" + File.separator + "words.db";
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(path, null);
db.execSQL("create table tb_words (_id integer primary key autoincrement ,english , chinese)");
2、创建数据库中的表:
核心示例代码:
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(path, null);
db.execSQL("create table tb_words (_id integer primary key autoincrement ,english , chinese)");
String path = SDCardHelper.getSDCardRoot() + File.separator + "steven" + File.separator + "words.db";
【备注:】SQLite的数据类型?
首先你会接触到一个让你惊讶的名词: Typelessness(无类型)。其实SQLite是无类型的。这意味着你可以保存任何类型的数据到数据库表的任何列中, 无论这列声明的数据类型是什么。对于SQLite来说对字段不指定类型是完全有效的。如:
create table dict(_id integer primary key autoincrement , word ,detail);
注意:这里一定要注意,SQLite数据库在一种情况下是要求类型匹配的,当我们建表是如create table table1(id integer primary key),sqlite对应一位integer primary key的列值允许你存储64位的整数。必须是INTEGER PRIMARY KEY AUTOINCREMENT。
诚然SQLite允许忽略数据类型, 但是仍然建议在你的Create Table语句中指定数据类型. 因为数据类型对于你和其他的程序员交流, 或者你准备换掉你的数据库引擎时能起到一个提示或帮助的作用。
3、调用SQLiteDatabase 对象的方法执行数据库操作:【重要】
- execSQL(String sql , Object[] args) 执行带占位符的sql语句(update,insert,delete语句)
- rawQuery( String sql , String[] args ) 执行带占位符的sql查询(select语句)
- beginTransaction() 开始事务
- endTransaction() 结束事务
【备注:】除了以上重要的四个方法外,Android考虑到不熟悉sql语句的开发者,提供了进一步封装后的一系列方法。但是以下这几个方法参数众多,但是一定也要掌握,在ContentProvider中会有类似的方法,要注意区分。
- insert()
- update()
- delete()
- query()
- move(int offset) 按偏移量来移动
- moveToFirst() 将记录指针移动到第一行
- moveToLast () 将记录指针移动到最后一行
- moveToNext () 将记录指针移动到下一行
- moveToPosition(int position) 将记录指针移动到指定的一行
- moveToPrevious() 将记录指针移动到上一行
- getCount() 返回Cursor的行数
- getColumnName(int index) 根据列的索引返回其相应的列名称
- getColumnIndex(String name) 根据列的名字返回其相应的索引
- getColumnNames() 返回一个保存有所有列名称的字符串数组
- getColumnCount() 返回列的总数
- close() 关闭游标结果集,释放资源
- getType() 获取字段的数据类型。分别有0、1、2、3、4这几个结果。
-
- 0: 代表null
- 1:代表int
- 2:代表float
- 3:代表String
- 4:代表blob
(三)、CursorAdapter与SimpleCursorAdapter:
1、类结构:
java.lang.Object ↳ android.widget.BaseAdapter ↳ android.widget.CursorAdapter
java.lang.Object
↳ android.widget.BaseAdapter
↳ android.widget.CursorAdapter
↳ android.widget.ResourceCursorAdapter
↳ android.widget.SimpleCursorAdapter
2、描述:
SimpleCurosrAdapter 是一个将 Cursor 中的 columns 与在 XML 布局文件中定义的 控件进行匹配的简易 adapter。或者说,SimpleCursorAdapter 允许绑定一个 Cursor 的 columns 到 ListView 上,并使用自定义的 layout 显示 List中的每个项目。SimpleCursorAdapter 作为中间桥梁,将从 sqlite 数据库中查询出来的数据直接显示到 ListView 中。
3、原型:
new SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to , int flags) ;
参数:
1)、Context context, 这个与 SimpleListItemFactory 相关的 ListView 所处运行上下文(context)。也就是这个 ListView 所在的 Activity。
2)、int layout, 显示 list item 的 布局文件。这个 layout 文件中至少要包含在 "to" 参数中命名的 views。
3)、Cursor c, 数据库的光标( Cursor )。如果 cursor 无效,则该参数可以为 null
4)、String[] from, 指定 column 中的哪些列的数据将绑定(显示)到 UI 中。如果 cursor 无效, 则该参数可为 null。
5)、int[] to, 指定用于显示 "from" 参数指定的数据列表的 views。 这些 views 必须都是 TextViews。 "from" 参数的前 N 个值(valus)和 "to" 参数的前 N 个 views 是一一对应的关系。如果 cursor 无效,则该参数可为 null。
6)、flags,用于定义适配器行为的标志位。
Flags used to determine the behavior of the adapter; may be any combination of
FLAG_AUTO_REQUERY
and FLAG_REGISTER_CONTENT_OBSERVER。
如果设置FLAG_REGISTER_CONTENT_OBSERVER(常量值:2),适配器会在Cursor上注册一个Observer,当通知到达时会调用 onContentChanged() 方法。
示例代码:
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.item_listview_main,
cursor, new String[] { "news_title","news_addtime" },
new int[] { R.id.text_item_title, R.id.text_item_addtime });
【备注:】
SimpleCursorAdapter类构造方法的第四个参数表示返回Cursor对象中的字段名,第五个参数表示要将该字段的值赋给那个组件。该组件在第二个参数中指定的布局文件中定义。
注意:在绑定数据时,Cursor对象返回的记录集中必须包含一个叫"_id"的字段,否则将无法完成数据绑定。也就是说SQL语句不能是select name from tb_contacts.如果在数据表中没有"_id"字段,可以采用其他方法来处理。
那么数据库存到哪里?系统在手机内存中的/data/data/<package name>/databases目录中创建数据库文件。
(四)、事务:【重要】
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完整地执行,要么完全地不执行。事务的目的是为了保证数据的一致性。 事务通过回滚(RollBack)保存了数据了一致性。
例如:
设想网上购物的一次交易,其付款过程至少包括以下几步数据库操作:
一、更新客户所购商品的库存信息
二、保存客户付款信息--可能包括与银行系统的交互
三、生成订单并且保存到数据库中
四、更新用户相关信息,例如购物数量等等
正常的情况下,这些操作将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果在这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等,都将导致交易失败。一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,比如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款,订单也没有生成。否则,数据库的信息将会一片混乱而不可预测。
数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。
使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功,如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 方法设置事务的标志为成功则提交事务,如果没有调用setTransactionSuccessful() 方法则回滚事务。使用例子如下:
SQLiteDatabase db = ....;
db.beginTransaction();//开始事务
try {
db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"人类", 4});
db.execSQL("update person set name=? where personid=?", new Object[]{"人类", 1});
db.setTransactionSuccessful();//调用此方法会在执行到endTransaction() 时提交当前事务,如果不调用此方法会回滚事务
} finally {
db.endTransaction();//由事务的标志决定是提交事务,还是回滚事务
}
db.close();
上面两条SQL语句在同一个事务中执行。
(五)、对SQLiteDatabase类进行封装:
1、封装MySQLiteDatabaseHelper 类核心代码:
public class MySQLiteDatabaseHelper{
private String DBPath = Environment.getExternalStorageDirectory().getAbsolutePath();
private String DBName = "android_manual.db";
private SQLiteDatabase db;
/**
* @作用: 构造方法。目的是创建数据库文件、创建SQLiteDatabase实例。
*/
public MySQLiteDatabaseHelper() {
getConnection();
}
/**
*
* @作用:获得一个数据库连接对象,即SQLiteDatabase对象。
* @return void
*/
public void getConnection() {
db = SQLiteDatabase.openDatabase(DBPath + File.seperator + DBName, null, 0);
}
/**
* @作用:执行带占位符的select语句,查询数据,返回Cursor
* @param sql
* @param selectionArgs
* @return Cursor
*/
public Cursor selectCursor(String sql, String[] selectionArgs) {
return db.rawQuery(sql, selectionArgs);
}
/**
* @作用:执行带占位符的select语句,返回结果集的个数
* @param sql
* @param selectionArgs
* @return int
*/
public int selectCount(String sql, String[] selectionArgs) {
Cursor cursor = db.rawQuery(sql, selectionArgs);
int count = cursor.getCount();
if (cursor != null) {
cursor.close();
}
return count;
}
/**
* @作用:执行带占位符的update、insert、delete语句,更新数据库,返回true或false
* @param sql
* @param bindArgs
* @return boolean
*/
public boolean updateData(String sql, String[] bindArgs) {
boolean flag = false;
try {
db.execSQL(sql, bindArgs);
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* @作用:执行带占位符的select语句,返回多条数据,放进List集合中。
* @param sql
* @param selectionArgs
* @return List<Map<String, Object>>
*/
public List<Map<String, Object>> selectData(String sql,String[] selectionArgs) {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Cursor cursor = db.rawQuery(sql, selectionArgs);
String[] cols_names = cursor.getColumnNames();
boolean flag = cursor.moveToFirst();
while (flag) {
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < cols_names.length; i++) {
String cols_value = cursor.getString(i);
map.put(cols_names[i], cols_value);
}
list.add(map);
flag = cursor.moveToNext();
}
if (cursor != null) {
cursor.close();
}
return list;
}
/**
* 作用:将cursor转成list集合
*
* @param cursor
* @return List<Map<String, Object>>
*/
public List<Map<String, Object>> cursorToList(Cursor cursor) {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
String[] cols_names = cursor.getColumnNames();
while (cursor.moveToNext()) {
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 0; i < cols_names.length; i++) {
Object cols_value = null;
switch (cursor.getType(i)) {
case 1:
cols_value = cursor.getInt(cursor
.getColumnIndex(cols_names[i]));
break;
case 2:
cols_value = cursor.getFloat(cursor
.getColumnIndex(cols_names[i]));
break;
case 3:
cols_value = cursor.getString(cursor
.getColumnIndex(cols_names[i]));
break;
case 4:
cols_value = cursor.getBlob(cursor
.getColumnIndex(cols_names[i]));
break;
default:
cols_value = null;
break;
}
map.put(cols_names[i], cols_value);
}
list.add(map);
}
if (cursor != null) {
cursor.close();
}
return list;
}
public void closeDb() {
if (db != null) {
db.close();
}
}
}
【备注:】
insert()方法中的第二个参数,该参数的作用是什么?
如果第三个参数values 为Null或者元素个数为0, 由于Insert()方法要求必须添加一条除了主键之外其它字段为Null值的记录,为了满足SQL语法的需要, insert语句必须给定一个字段名,
如:insert into person(name) values(NULL),倘若不给定字段名 , insert语句就成了这样:
insert into person() values(),显然这不满足标准SQL的语法。
对于字段名,建议使用主键之外的字段,如果使用了INTEGER类型的主键字段,执行类似insert into person(personid) values(NULL)的insert语句后,该主键字段值也不会为NULL。如果第三个参数values 不为Null并且元素的个数大于0 ,可以把第二个参数设置为null。