第14回 哦,用数据库!
2015-12-11 11:31
260 查看
张飞说:“二哥,这些数据存储方式我倒是都有了个了解,不过这个数据库看起来很深邃啊。我们似乎有必要深入学习一下!”
关羽说:“是呀,数据库是比较复杂的。Android上自带有sqllite数据库。存储内容较多的程序都需要创建数据库来管理数据。所以对数据库的操作是非常重要的一项技能!”
张飞说:“妈呀,听着各种高深啊。二哥,要不数据库这块就靠你了,我也不会sql语句,需要用数据库的话二哥做好了直接给我接口用就好了。”
关羽说:“这要是大哥在的话又该糊你什么东西一脸了,你遇事怎么总想着逃避呢?要拿出你当年长板桥喝退百万雄兵、万夫莫当的气概来,对技术难关要迎难而上!”
张飞问道:“我长板桥喝退百万雄兵?什么剧情?”
关羽答道:“唉,算了,不跟你讲了,讲了你也不明白。”
张飞说:“原来如此!二哥说的在理啊!”
关羽吐槽道:“我说啥了,你就懂了……我的意思是对于研发人员来说,越难的技术越有价值,正因为难所以很多人学不精,你学通了之后就更有优势。要把苦难当做宝藏一样来挖掘!”
张飞点头道:“就跟鸡骨头一样,越难啃的越有味道!”
关羽赞道:“三弟果然进步神速,都会比喻了!”
在事务处理方面,SQLite通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只有一个可以写入数据。在某个进程或线程想执行数据库写操作之前,必须获得独占锁。在获得独占锁之后,其他的读或写操作将不会再执行。
SQLite采用动态数据类型,当某个值插入到数据库时,SQLite将会检查它的类型,如果该类型与关联的列不匹配,SQLite则会尝试将该值转换成该列的类型,如果不能转换,则该值将作为本身的类型存储,SQLite称这为“弱类型”。但有一个特例,如果是INTEGER PRIMARY KEY,则其他类型不会被转换,会报一个“datatypemismatch”的错误。概括来讲,SQLite支持NULL、INTEGER、REAL、TEXT和BLOB数据类型,分别代表空值、整型值、浮点值、字符串文本和二进制对象。
现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎。在为移动设备开发应用程序时,经常会用到SQLite来存储大量的数据,所以掌握移动设备上的SQLite开发技巧很重要。对于Android平台来说,系统内置了丰富的API来供开发人员操作SQLite,可以轻松的完成对数据的存取。
Expert Professional编辑数据库、查看数据库。
图14-1 SQLite Expert Professional使用界面
表14-1 SQliteOpenHelper相关方法
void execSQL(String sql)和void execSQL(String sql,Object[] bindArgs)这两个方法用于执行SQL语句。第一个参数为SQL语句,第二个参数为SQL语句中占位符参数的值,参数值在数组中的顺序要和占位符的位置对应。如下代码实现了将刘备的相关信息插入到数据库:
SQLiteDatabase db = ....;
db.execSQL("insert into heros_table (hero_name, hero_weapon,hero_attack, hero_defence) values(?,?,?,?)", new Object[]{"刘备", "仁义双股剑","100","100"});
db.close();
Cursor rawQuery(String sql, String[]selectionArgs)方法的第一个参数为select语句;第二个参数为select语句中占位符参数的值,如果select语句没有使用占位符,该参数可以设置为null。rawQuery()方法用于查找数据,返回值是结果集游标Cursor。查找攻击力等于100,防御力为130英雄的代码如下所示:
Cursor cursor = db.rawQuery("select * fromheros_tablewhere hero_attack = ? and hero_defence =?", newString[]{"100", "130"});
除了直接执行SQL的execSQL()和rawQuery()方法,SQLiteDatabase还专门提供了对应于添加、删除、更新、查询的操作方法:insert()、delete()、update()和query(),为不熟悉SQL语法的用户使用。
Insert()方法用于添加数据,各个字段的数据使用ContentValues进行存放。ContentValues类似于map类型,它提供了存取数据对应的put(String key, Object value)和getAsObject(String key)方法,key为字段名称,value为字段值,支持各种常用的数据类型,如:String、Integer等。插入一个英雄数据的代码如下所示:
SQLiteDatabase db = this.getWritableDatabase();
// ContentValues 存储数据列元素
ContentValues cv = newContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 插入数据返回列位置
long row = db.insert(TABLE_NAME, null, cv);
不管insert(TABLE_NAME, null, cv)第三个参数是否包含数据,执行insert()方法必然会添加一条记录,如果第三个参数为空,会添加一条除主键之外其他字段值为NULL的记录。insert()方法内部实际上通过构造insert SQL语句完成数据的添加,insert()方法的第二个参数用于指定空值字段的名称。如果第三个参数values 为NULL或者元素个数为0,由于insert()方法要求必须添加一条除了主键之外其它字段为NULL值的记录,为了满足SQL语法的需要,insert语句必须给定一个字段名,如:insertinto
person(name) values(NULL)。倘若不给定字段名,insert语句就成了这样:insert into person() values(),显然这不满足标准SQL的语法。对于字段名,建议使用主键之外的字段,如果使用了INTEGER类型的主键字段,执行类似insertinto person(personid) values(NULL)的insert语句后,该主键字段值也不会为NULL。如果第三个参数values不为NULL并且元素的个数大于0,可以把第二个参数设置为NULL。
delete()方法
delete()方法主要用于删除数据,例如删除英雄id等于3的数据,其代码如下所示:
SQLiteDatabase db = this.getWritableDatabase();
String where = HERO_ID + " = ?";
String[] whereValue = {
Integer.toString(3)
};
// 删除数据表名、删除条件、删除的位置
db.delete(TABLE_NAME, where, whereValue);
update()方法
update()方法用于数据的更新,例如将英雄id等于1的数据更新,其代码如下所示:
SQLiteDatabase db = this.getWritableDatabase();
String where = HERO_ID +" = ?";
String[] whereValue = {
Integer.toString(1)
};
ContentValues cv = new ContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 更新数据,表、更新的数据、更新的条件、更新的位置
db.update(TABLE_NAME, cv, where, whereValue);
query()方法
query()方法实际上是把select语句拆分成了若干个组成部分,然后作为方法的输入参数。query方法的输入参数较多,下面详细讲解query(table, columns, selection, selectionArgs, groupBy,having, orderBy, limit)方法各参数的含义:
l table:表名。相当于select语句from关键字后面的部分。如果是多表联合查询,可以用逗号将两个表名分开。
l columns:要查询出来的列名。相当于select语句select关键字后面的部分。
l selection:查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符“?”。
l selectionArgs:对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常。
l groupBy:相当于select语句group by关键字后面的部分。
l having:相当于select语句having关键字后面的部分。
l orderBy:相当于select语句order by关键字后面的部分。
l limit:指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分。
query()方法代码示例如下:
Cursorcursor = db.query("heros_table ", newString[]{"hero_id,hero_weapon, hero_defence "}, " hero_weapon like ?", newString[]{"%剑%"}, null, null, " hero_id desc", "1,2");
上面代码用于从英雄表中查找武器字段含有“剑”的记录,匹配的记录按英雄id降序排序,对排序后的结果略过第1条记录,只获取2条记录。
表14-2 Cursor方法
public SimpleCursorAdapter (Context contex, int layout, Cursor c,String[]from, int[] to)
l context:当前程序的上下文对象。
l layout:用来描述如何显示在适配器控件上的布局文件的R类引用。
l from:由需要显示出来的列名组成的字符串数组。
l to:由layout所指定的布局文件中子控件的id所组成的整型数组,与from相对应。
下面的代码显示了如何构造一个SimpleCursorAdapter用于显示数据库的信息:
String uriString ="content://contacts/people/";
// 获取到游标
Cursor myCursor= managedQuery(Uri.parse(uriString), null,null, null, null);
// 数据的来源,号码和名字
String[] fromColumns = new String[] {People.NUMBER,People.NAME};
// 号码和名字显示到的UI组件
int[] toLayoutIDs = newint[] { R.id.nameTextView, R.id.numberTextView};
SimpleCursorAdapter myAdapter;
// 新建SimpleCursorAdapter适配器,将数据和UI组件联系上
myAdapter = newSimpleCursorAdapter(this,R.layout.simplecursorlayout,myCursor,fromColumns,toLayoutIDs);
myListView.setAdapter(myAdapter);
上面的代码中,SimpleCursorAdapter将联系人表和ListView关联起来,在ListView中显示联系人的姓名和电话号码。
图14-2 SQLite示例工程结构图
l Context.createDatabase(String name,int version ,intmode,CursorFactory factory):创建一个新的数据库并返回一个SQLiteDatabase对象,假如数据库不能被创建,抛出FileNotFoundException异常。
l Context.openOrCreateDatabase(String name,intmode,CursorFactory factory):该方法是打开一个数据库,如果没有的话,则会创建。
l Context.deleteDatabase(String name):删除指定名称的数据库,假如数据库成功删除则返回true,失败则为false(例如数据库不存在)。
在执行完上面的数据库创建代码后,系统就会在“/data/data/[PACKAGE_NAME]/databases”目录下生成一个“HEROS.db”的数据库文件,如图14-3所示:
图14-3 SQLite 数据库位置
当完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。
String DB_PATH = "/data"
+ Environment.getDataDirectory().getAbsolutePath() + "/"
+ packageName+"/databases"+/name; //在手机里存放数据库的位置
File database=newFile(DB_PATH);
//判断数据库文件是否存在,若不存在则执行导入,否则直接打开数据库
if(!(database.exists())) {
database.mkdirs();
}
InputStream is = this.context.getAssets().open(dbName);
//欲导入的数据库
FileOutputStream fos = newFileOutputStream(dbfile);
byte[] buffer = newbyte[BUFFER_SIZE];
int count = 0;
while ((count =is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
fos.close();
is.close();
也可以调用如下代码打开存储在SD卡上的数据库:
File name = newFile("/sdcard/ HEROS.db");
return SQLiteDatabase.openOrCreateDatabase(name, null);
图14-4运行结果
首先,创建一个ListAdapter类,该类继承BaseAdapter,作为数据库数据显示的适配器。代码如下所示:
ListAdapter.java代码清单14-4-3:
/**
* 列表适配器.
* @author关羽:编程的烦恼就12个字:放不下,想不开,看不透,忘不了。
*/
public class ListAdapter extendsBaseAdapter {
// 主界面的句柄
private Context mContext;
// 操作数据库集合类
private Cursor mCursor;
// 列表适配器构造函数
public ListAdapter(Context context, Cursor cursor) {
mContext =context;
mCursor =cursor;
}
// 获取列表长度
@Override
public int getCount() {
return mCursor.getCount();
}
// 根据索引获取集合中的一个对象
@Override
public Object getItem(intposition) {
return null;
}
// 获取的行ID列表中的指定位置
@Override
public long getItemId(intposition) {
return 0;
}
/**
* @author关羽:我是大英雄,哈哈,Hold 不住啊!
* @param position position就是位置从0开始
* @param convertView convertView是ListView中每一项要显示的view
* @param parent parent就是父窗体了
* @return通常return 的view也就是convertView
*/
@Override
public View getView(intposition, View convertView, ViewGroup parent) {
TextViewmTextView = newTextView(mContext);
mTextView.setTextSize(20);
mCursor.moveToPosition(position);
mTextView.setText("英雄名字:" +mCursor.getString(1) + "英雄武器:" + mCursor.getString(2) + ""
+"英雄攻击力:" + mCursor.getString(3) + "英雄防御力" + mCursor.getString(4));
return mTextView;
}
}
上面代码自定义了一个BaseAdapter,包含一个Cursor类成员变量。通过该Cursor可以获取数据库HEROS表中的数据。在getView()方法中,实例化了一个TextView用于显示数据库HEROS表中英雄名字、英雄武器、英雄攻击力和英雄防御力信息。
然后,新建一个Activity,命名为MainActivity,在MainActivity中添加了增加、删除、更新按钮,用于操作数据库。代码如下所示:
MainActivity.java代码清单14-4-3:
/**
* @author关羽:这年头什么也靠不住,只有自己靠自己,简称:我…靠!
*/
public class MainActivity extendsActivity implementsOnItemClickListener {
// 数据库操作类
private HerosDB mHerosDB;
// 数据库数据源
private Cursor mCursor;
// 英雄名
private EditText heroName;
// 英雄武器
private EditText heroWeapon;
// 英雄攻击力
private EditText heroAttack;
// 英雄防御力
private EditText heroDefence;
// 存储数据按钮
private Button saveButton;
// 删除数据按钮
private Button deleteButton;
// 更新数据按钮
private Button updateButton;
// 存储数据监听
private OnClickListener saveButtonListener = null;
// 删除数据监听
private OnClickListener deleteButtonListener = null;
// 更新数据监听
private OnClickListener updateButtonListener = null;
// 列表
private ListView list;
// 操作的数据的Id
private int Hero_ID = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setListener();
findView();
}
/**
* @author关羽:三弟会放一起,我也会放一起,哈哈,我可不能被三弟比下去
*/
private void findView() {
list =(ListView)findViewById(R.id.list);
updateButton = (Button)findViewById(R.id.updateButton);
updateButton.setOnClickListener(updateButtonListener);
saveButton= (Button)findViewById(R.id.saveButton);
saveButton.setOnClickListener(saveButtonListener);
deleteButton= (Button)findViewById(R.id.deleteButton);
deleteButton.setOnClickListener(deleteButtonListener);
heroName =(EditText)findViewById(R.id.name);
heroWeapon= (EditText)findViewById(R.id.weapon);
heroAttack= (EditText)findViewById(R.id.attack);
heroDefence= (EditText)findViewById(R.id.defence);
mHerosDB = new HerosDB(this);
mCursor =mHerosDB.select();
list.setAdapter(newListAdapter(this, mCursor));
list.setOnItemClickListener(this);
}
/**
* @author关羽设置监听
*/
private void setListener() {
updateButtonListener = newOnClickListener() {
@Override
public void onClick(View v) {
// 更新
update();
}
};
saveButtonListener = newOnClickListener() {
@Override
public void onClick(View v) {
// 添加数据
add();
}
};
deleteButtonListener = newOnClickListener() {
@Override
public void onClick(View v) {
// 删除数据
delete();
}
};
}
public void add() {
// 读取数据
String name= heroName.getText().toString();
Stringweapon = heroWeapon.getText().toString();
Stringattack = heroAttack.getText().toString();
Stringdefence = heroDefence.getText().toString();
// 防止数据为空
if (name.equals("") ||weapon.equals("") || attack.equals("") || defence.equals("")){
return;
}
// 插入数据
mHerosDB.insert(name, weapon, attack, defence);
// 执行查询,创建光标再次刷新其内容。这可以在任何时候用,包括通话后停用。
mCursor.requery();
// 更新列表
list.invalidateViews();
heroName.setText("");
heroWeapon.setText("");
heroAttack.setText("");
heroDefence.setText("");
Toast.makeText(this,"插入英雄成功了!", Toast.LENGTH_SHORT).show();
}
public void delete() {
// 没有数据
if (Hero_ID == 0) {
return;
}
// 删除制定Id数据
mHerosDB.delete(Hero_ID);
// 执行查询,创建光标再次刷新其内容。这可以在任何时候用,包括通话后停用。
mCursor.requery();
// 更新列表
list.invalidateViews();
heroName.setText("");
heroWeapon.setText("");
heroAttack.setText("");
heroDefence.setText("");
Toast.makeText(this,"删除英雄成功了!", Toast.LENGTH_SHORT).show();
}
public void update() {
// 读取数据
String name= heroName.getText().toString();
Stringweapon = heroWeapon.getText().toString();
Stringattack = heroAttack.getText().toString();
Stringdefence = heroDefence.getText().toString();
// 防止数据为空
if (name.equals("") ||weapon.equals("") || attack.equals("") ||defence.equals("")) {
return;
}
// 更新指定id数据
mHerosDB.update(Hero_ID, name, weapon, attack, defence);
// 执行查询,创建光标再次刷新其内容。这可以在任何时候用,包括通话后停用。
mCursor.requery();
// 更新列表
list.invalidateViews();
heroName.setText("");
heroWeapon.setText("");
heroAttack.setText("");
heroDefence.setText("");
Toast.makeText(this,"更新英雄成功了!", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 点击移动到指定的位置
mCursor.moveToPosition(position);
// 更新操作id
Hero_ID =mCursor.getInt(0);
heroName.setText(mCursor.getString(1));
heroWeapon.setText(mCursor.getString(2));
heroAttack.setText(mCursor.getString(3));
heroDefence.setText(mCursor.getString(4));
}
}
MainActivity提供了存储、删除、更新三个按钮,这三个按钮的功能分别在add()、delete()、update()这三个方法中进行了实现。
数据库的主要操作代码定义在HerosDB类里面。在实际开发中,为了能够更好的管理和维护数据库,会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,封装业务逻辑,如HerosDB类。在HerosDB类中为表名和列名定义字符串静态常量和常见数据库操作方法,代码如下所示:
HerosDB.java代码清单14-4-3:
/**
* 数据库操作类.
* @author关羽:我当年也是个痴情的种子,结果下了场雨……淹死了。
*/
public class HerosDB extendsSQLiteOpenHelper {
// 数据库名字
privatestatic finalString DATABASE_NAME= "HEROS.db";
// 数据库版本
private staticfinalintDATABASE_VERSION = 1;
// 数据库表
private staticfinalString TABLE_NAME= "heros_table";
// 数据库英雄表英雄列表id
publicstatic final String HERO_ID= "heros_id";
// 数据库英雄表英雄列字段名
publicstatic final String HERO_NAME= "hero_name";
// 数据库英雄表英雄武器列字段名
publicstatic final String HERO_WEAPON= "hero_weapon";
// 数据库英雄表攻击力列字段名
publicstatic final String HERO_ATTACK= "hero_attack";
// 数据库英雄防御力列字段名
publicstatic final String HERO_DEFENCE= "hero_defence";
// 构造函数
public HerosDB(Context context) {
super(context, DATABASE_NAME,null, DATABASE_VERSION);
}
// 创建table
@Override
public void onCreate(SQLiteDatabase db) {
String sql= "CREATE TABLE " + TABLE_NAME+ " (" + HERO_ID
+ " INTEGER primary key autoincrement," + HERO_NAME + "text, " + HERO_WEAPON
+" text, " + HERO_ATTACK+ " text, " + HERO_DEFENCE+ " text" + ");";
db.execSQL(sql);
}
// 更新接口
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, intnewVersion) {
String sql= "DROP TABLE IF EXISTS " + TABLE_NAME;
db.execSQL(sql);
onCreate(db);
}
/**
* @author关羽查询语句
* @return
*/
public Cursor select() {
// 获得数据库操作类
SQLiteDatabase db = this.getReadableDatabase();
Cursorcursor = db.query(TABLE_NAME, null, null, null, null, null, null);
return cursor;
}
/**
* @author关羽插入操作
*/
public long insert(String name, String weapon, String attack, Stringdefence) {
SQLiteDatabase db = this.getWritableDatabase();
//ContentValues 存储数据列元素
ContentValues cv = newContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 插入数据返回列位置
long row = db.insert(TABLE_NAME,null, cv);
return row;
}
/**
* @author关羽插入操作删除数据
*/
public void delete(intid) {
SQLiteDatabase db = this.getWritableDatabase();
Stringwhere = HERO_ID + " =?";
String[]whereValue = {
Integer.toString(id)
};
// 删除数据表名、删除条件、删除的位置
db.delete(TABLE_NAME, where, whereValue);
}
/**
* @author关羽修改数据
*/
public void update(intid, String name, String weapon, String attack, String defence) {
SQLiteDatabase db = this.getWritableDatabase();
String where = HERO_ID + " = ?";
String[]whereValue = {
Integer.toString(id)
};
ContentValues cv = newContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 更新数据,表、更新的数据、更新的条件、更新的位置
db.update(TABLE_NAME, cv, where, whereValue);
}
}
细心的读者可能会注意到,这里我们并没有指定_id列的值。这是因为SQLite数据库中将所有声明为“INTEGER PRIMARY KEY”的列自动识别成自增列。在插入一行数据的时候,若不指定自增列的数据或给自增列传入NULL值时,会自动给自增列赋一个所有行中此列里的最大值加1的数。若添加的是第一行则从数字1开始。而这里的_id就是一个自增列,所以在插入这行数据后,此行数据_id列的值为1。
关羽:老飞啊,你的想法很独特嘛。当然可以,你可以将图片转换成byte[]数组存入到数据库中去,再从数据库中取出来转换成图像显示出来。但是不建议这样使用,一般图片,文件和二进制数据,我们是不建议存在数据库中的,因为这会带来很多的问题。对数据库的读/写的速度永远都赶不上文件系统处理的速度。同时也会使得,数据库备份变的巨大,越来越耗时间,给自己行个方便吧,在数据库里只简单的存放一个磁盘上你的文件的相对路径。
张飞:二哥真是功力深厚呀,那为什么要给每个表增加一个id?
关羽:表包含一个自动增加的键域,从而为每一行提供唯一的索引,保证了每一行数据的唯一性,同时增加了索引,一定程度上可以加快查找速度。
张飞:二哥我想更新我的表怎么办?
关羽:SQLite提供了ALTER TABLE命令,允许用户重命名或添加新的字段到已有表中,但是不能从表中删除字段噢。
张飞:二哥,我觉得用Sqlite数据库时,经常需要进行机械性的CRUD操作,可不可以进行了一下封装,去除这些繁琐的操作?
关羽:现在已经有不少第三方Android数据库的持久层开发包啦,例如Androrm、ormlite。
张飞:二哥,我的数据库应用现在总是崩溃,怎么办?
关羽:数据库应用的调试不同于一般的程序,要考虑到数据在数据库的状况,出现崩溃不要慌,检查SQL语句,检查数据和表的结构是否一致,检查数据库表是否更新上了。还有常见的导致崩溃的问题,例如创建多个数据库,反复创建同一张表,SQL语句特殊字符没有转义,不注意空格等。
张飞:二哥最后一个问题!
关羽:你说吧!
张飞:今天大哥为什么不在?
关羽:额……大哥可能休产假去了吧,今天我只能勉为其难了。
关羽说:“是呀,数据库是比较复杂的。Android上自带有sqllite数据库。存储内容较多的程序都需要创建数据库来管理数据。所以对数据库的操作是非常重要的一项技能!”
张飞说:“妈呀,听着各种高深啊。二哥,要不数据库这块就靠你了,我也不会sql语句,需要用数据库的话二哥做好了直接给我接口用就好了。”
关羽说:“这要是大哥在的话又该糊你什么东西一脸了,你遇事怎么总想着逃避呢?要拿出你当年长板桥喝退百万雄兵、万夫莫当的气概来,对技术难关要迎难而上!”
张飞问道:“我长板桥喝退百万雄兵?什么剧情?”
关羽答道:“唉,算了,不跟你讲了,讲了你也不明白。”
张飞说:“原来如此!二哥说的在理啊!”
关羽吐槽道:“我说啥了,你就懂了……我的意思是对于研发人员来说,越难的技术越有价值,正因为难所以很多人学不精,你学通了之后就更有优势。要把苦难当做宝藏一样来挖掘!”
张飞点头道:“就跟鸡骨头一样,越难啃的越有味道!”
关羽赞道:“三弟果然进步神速,都会比喻了!”
1.1. SQLite数据库简介
SQLite是一种流行的轻量级关系型数据库。SQLite是D.Richard Hipp用C语言编写的开源嵌入式数据库引擎。它支持大多数的SQL92标准,并且可以在所有主要的操作系统上运行。SQLite通过利用虚拟机和虚拟数据库引擎(VDBE),使调试、修改和扩展SQLite的内核变得更加方便。这也使得SQL语句都被编译成易读的、可以在SQLite虚拟机中执行的程序集。SQLite可以支持高达2TB大小的数据库,每个数据库都是以单个文件的形式存在,这些数据都是以B-Tree的数据结构形式存储在磁盘上。在事务处理方面,SQLite通过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程可以在同一时间从同一数据库读取数据,但只有一个可以写入数据。在某个进程或线程想执行数据库写操作之前,必须获得独占锁。在获得独占锁之后,其他的读或写操作将不会再执行。
SQLite采用动态数据类型,当某个值插入到数据库时,SQLite将会检查它的类型,如果该类型与关联的列不匹配,SQLite则会尝试将该值转换成该列的类型,如果不能转换,则该值将作为本身的类型存储,SQLite称这为“弱类型”。但有一个特例,如果是INTEGER PRIMARY KEY,则其他类型不会被转换,会报一个“datatypemismatch”的错误。概括来讲,SQLite支持NULL、INTEGER、REAL、TEXT和BLOB数据类型,分别代表空值、整型值、浮点值、字符串文本和二进制对象。
现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎。在为移动设备开发应用程序时,经常会用到SQLite来存储大量的数据,所以掌握移动设备上的SQLite开发技巧很重要。对于Android平台来说,系统内置了丰富的API来供开发人员操作SQLite,可以轻松的完成对数据的存取。
关羽:大哥,SQLite可以解析大部分标准SQL语句,如:查询语句、插入语句、更新语句和删除语句噢。 |
1.2. SQLite Expert Professional简介
SQLite Expert Professional是一款可视化的数据库管理工具,允许用户在 SQLite 服务器上执行创建、编辑、复制、提取等操作。SQLite Expert支持所有的图形界面的SQLite特征。它包括可视化查询生成器,SQL编辑与语法冲突检查功能、代码自动补全功能和强大的table和view导入导出功能。如图14-1所示,我们利用SQLite Expert Professional建立了HEROS数据库,在数据库中建立了heros_table表,表包括heros_id、hero_name、hero_weapon、hero_attack、hero_defence列。在实际开发中,可以利用SQLiteExpert Professional编辑数据库、查看数据库。
图14-1 SQLite Expert Professional使用界面
1.3. SQLite数据库操作类详解
在实际使用SQLite数据库时,往往需要在首次使用软件时创建出应用使用到的数据库表结构和添加一些初始化记录,或者在软件升级的时对数据表结构进行更新。Android系统提供了多个数据库操作类,用于在用户的手机上自动创建出应用程序需要的数据库表,通过这些类就可以对数据进行管理了,下面将详细的介绍这些类。1.3.1.SQLiteOpenHelper
SQLiteOpenHelper是一个辅助抽象类,主要用于生成数据库,并对数据库的版本进行管理。通过使用SQLiteOpenHelper可以隐去在数据库打开之前需要判断数据库是否需要创建或更新的逻辑。当在程序中调用该类的方法getWritableDatabase()或者getReadableDatabase()时,如果没有数据库,那么Android系统就会自动生成一个数据库。SQLiteOpenHelper是一个抽象类,需要继承,并重写其方法。一般,在该类中包装数据库的创建、打开、更新和关闭操作,定义表的名字、列的名字和列的索引。SQliteOpenHelper的相关方法如表14-1所示:表14-1 SQliteOpenHelper相关方法
方法 | 解释 |
public SQLiteOpenHelper (Context context, String name, SQLiteDatabase.CursorFactory factory, int version) | 创建一个帮助对象,打开或者管理数据库,该方法通常快速返回数据库对象。 context:表示上下文用来打开或创建数据库;name:表示数据库文件名;factory:表示用来创建对象游标,默认为null;version:表示数据库的序号。 |
public SQLiteOpenHelper (Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) | 创建一个帮助对象,打开或者管理数据库。 context:表示用来打开或创建数据库;name:表示数据库文件名;factory:表示用来创建对象游标,默认为null;version:表示数据库的序号;errorHandler:表示当SQlite报告一个数据库毁坏错误时,DatabaseErrorHandler会被调用。 |
public synchronized void close () | 关闭任何打开的数据库对象。 |
public String getDatabaseName () | 返回正被打开的通过构造函数传递进来的SQLite数据库的名字。 |
public synchronized SQLiteDatabase getReadableDatabase () | 创建或打开一个数据库。这和getWritableDatabase()返回的对象是同一个,一些因素要求数据库只能以read-only的方式被打开,比如磁盘满了。在这种情况下,一个只读的数据库对象将被返回。 |
public synchronized SQLiteDatabase getWritableDatabase () | 创建或打开一个数据库,用于读写。一旦成功打开,数据库将被缓存,所以需要写入数据的时候可以调用这个方法。 返回值:一个有效的读写数据库对象直到close()被调用。 |
public abstract void onCreate (SQLiteDatabase db) | 当第一次创建数据库时调用。表格的创建和初始化表格的个数在这里完成。 db:表示数据库。 |
public void onDowngrade (SQLiteDatabase db, int oldVersion, int newVersion) | 当数据库需要降低版本时调用,只要当前版本比被请求的版本新,它就会被调用。尽管如此,这个方法不是抽象的,它并不强制用户去实现它。如果不被重写,默认的实现将会拒绝降级并且抛出一个SQLiteException。 db:表示数据库;oldVersionThe:表示旧版本数据库;newVersionThe:表示新版本数据库。 |
public void onOpen (SQLiteDatabase db) | 当数据库打开时调用。 db:表示数据库。 |
public abstract void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) | 当数据库升级时调用。这个方法一般用于丢弃数据库表,增加新的数据库表等数据库升级操作。 db:表示数据库;oldVersionThe:表示旧版本数据库;newVersionThe:表示新版本数据库。 |
1.3.2.SQLiteDatabase
SQLiteDatabase类继承SQLiteClosable类,不仅能够创建、删除和执行SQL命令,而且可以完成对数据的添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)操作,简称CRUD。下面详细讲解SQLiteDatabase如何使用execSQL()和rawQuery()方法执行SQL语句完成数据的CRUD操作,和利用SQLiteDatabase类的方法insert()、delete()、update()和query()完成数据的CRUD操作。void execSQL(String sql)和void execSQL(String sql,Object[] bindArgs)这两个方法用于执行SQL语句。第一个参数为SQL语句,第二个参数为SQL语句中占位符参数的值,参数值在数组中的顺序要和占位符的位置对应。如下代码实现了将刘备的相关信息插入到数据库:
SQLiteDatabase db = ....;
db.execSQL("insert into heros_table (hero_name, hero_weapon,hero_attack, hero_defence) values(?,?,?,?)", new Object[]{"刘备", "仁义双股剑","100","100"});
db.close();
关羽:大哥,如果把用户输入的内容组拼到上面的insert语句,需要注意字符转义的问题。当用户输入的内容含有单引号时,需要对单引号进行转义,也就是把单引号转换成两个单引号。有些时候用户往往还会输入像“&”这些特殊SQL符号,为保证组拼好的SQL语句语法正确,必须对SQL语句中的这些特殊SQL符号都进行转义,防止SQL语句存在语法错误。 |
Cursor cursor = db.rawQuery("select * fromheros_tablewhere hero_attack = ? and hero_defence =?", newString[]{"100", "130"});
除了直接执行SQL的execSQL()和rawQuery()方法,SQLiteDatabase还专门提供了对应于添加、删除、更新、查询的操作方法:insert()、delete()、update()和query(),为不熟悉SQL语法的用户使用。
Insert()方法用于添加数据,各个字段的数据使用ContentValues进行存放。ContentValues类似于map类型,它提供了存取数据对应的put(String key, Object value)和getAsObject(String key)方法,key为字段名称,value为字段值,支持各种常用的数据类型,如:String、Integer等。插入一个英雄数据的代码如下所示:
SQLiteDatabase db = this.getWritableDatabase();
// ContentValues 存储数据列元素
ContentValues cv = newContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 插入数据返回列位置
long row = db.insert(TABLE_NAME, null, cv);
不管insert(TABLE_NAME, null, cv)第三个参数是否包含数据,执行insert()方法必然会添加一条记录,如果第三个参数为空,会添加一条除主键之外其他字段值为NULL的记录。insert()方法内部实际上通过构造insert SQL语句完成数据的添加,insert()方法的第二个参数用于指定空值字段的名称。如果第三个参数values 为NULL或者元素个数为0,由于insert()方法要求必须添加一条除了主键之外其它字段为NULL值的记录,为了满足SQL语法的需要,insert语句必须给定一个字段名,如:insertinto
person(name) values(NULL)。倘若不给定字段名,insert语句就成了这样:insert into person() values(),显然这不满足标准SQL的语法。对于字段名,建议使用主键之外的字段,如果使用了INTEGER类型的主键字段,执行类似insertinto person(personid) values(NULL)的insert语句后,该主键字段值也不会为NULL。如果第三个参数values不为NULL并且元素的个数大于0,可以把第二个参数设置为NULL。
delete()方法
delete()方法主要用于删除数据,例如删除英雄id等于3的数据,其代码如下所示:
SQLiteDatabase db = this.getWritableDatabase();
String where = HERO_ID + " = ?";
String[] whereValue = {
Integer.toString(3)
};
// 删除数据表名、删除条件、删除的位置
db.delete(TABLE_NAME, where, whereValue);
update()方法
update()方法用于数据的更新,例如将英雄id等于1的数据更新,其代码如下所示:
SQLiteDatabase db = this.getWritableDatabase();
String where = HERO_ID +" = ?";
String[] whereValue = {
Integer.toString(1)
};
ContentValues cv = new ContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 更新数据,表、更新的数据、更新的条件、更新的位置
db.update(TABLE_NAME, cv, where, whereValue);
query()方法
query()方法实际上是把select语句拆分成了若干个组成部分,然后作为方法的输入参数。query方法的输入参数较多,下面详细讲解query(table, columns, selection, selectionArgs, groupBy,having, orderBy, limit)方法各参数的含义:
l table:表名。相当于select语句from关键字后面的部分。如果是多表联合查询,可以用逗号将两个表名分开。
l columns:要查询出来的列名。相当于select语句select关键字后面的部分。
l selection:查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符“?”。
l selectionArgs:对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常。
l groupBy:相当于select语句group by关键字后面的部分。
l having:相当于select语句having关键字后面的部分。
l orderBy:相当于select语句order by关键字后面的部分。
l limit:指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分。
query()方法代码示例如下:
Cursorcursor = db.query("heros_table ", newString[]{"hero_id,hero_weapon, hero_defence "}, " hero_weapon like ?", newString[]{"%剑%"}, null, null, " hero_id desc", "1,2");
上面代码用于从英雄表中查找武器字段含有“剑”的记录,匹配的记录按英雄id降序排序,对排序后的结果略过第1条记录,只获取2条记录。
1.3.3.Cursor
Cursor类主要用于保存查询返回的结果,类似于JDBC中的ResultSet,提供对结果集进行向前、向后或随机的访问功能。Cursor本身是一个接口类,提供了对结果集访问的一些抽象方法,根据功能的不同在其子类有着不同的实现。要控制查询时返回的Cursor类型,可以自定义一个继承自CursorFactory类通过实现其newCursor()方法来返回需要的Cursor子类对象,但在CursorFactory传入null的默认情况下,查询操作会返一个指向第一行数据之前的SQLiteCursor的对象。下面详细介绍Cursor类的方法,利用这些方法可以很容易地完成提取或者遍历结果集的操作,如表14-2所示:表14-2 Cursor方法
方法 | 解释 |
close () | 关闭游标,释放资源。 |
copyStringToBuffer (int columnIndex, CharArrayBuffer buffer) | 在缓冲区中检索请求的列的文本,将将其存储。 |
getColumnCount () | 返回所有列的总数。 |
getColumnIndex (String columnName) | 返回指定列的名称,如果不存在返回-1。 |
getColumnIndexOrThrow (String columnName) | 从零开始返回指定列名称,如果不存在将抛出IllegalArgumentException 异常。 |
getColumnName (int columnIndex) | 从给定的索引返回列名。 |
getColumnNames () | 返回一个字符串数组的列名。 |
getCount () | 返回Cursor 中的行数。 |
moveToFirst () | 移动光标到第一行。 |
moveToLast () | 移动光标到最后一行。 |
moveToNext () | 移动光标到下一行。 |
moveToPosition (int position) | 移动光标到一个绝对的位置。 |
moveToPrevious () | 移动光标到上一行。 |
getPosition() | 返回当前的游标的位置。 |
isFirst() | 判断是否是第一条记录。 |
isLast() | 判断是否是最后一条记录。 |
1.3.4.SimpleCursorAdapter
SimpleCursorAdapter是一个简单的Adapter,在许多时候需要将数据库表中的数据显示在ListView、Gallery等组件中。虽然可以直接使用Adapter对象处理,但工作量很大,为此Android SDK提供了一个专用于数据绑定的Adapter类:SimpleCursorAdapter。SimpleCursorAdapter与SimpleAdapter用法相近,只是将List对象换成了Cursor对象。SimpleCursorAdapter类构造方法的第四个参数from表示Cursor对象中的字段,而SimpleAdapter类构造方法的第四个参数from表示Map对象中的key。除此之外,这两个Adapter类在使用方法完全相同。SimpleCursorAdapter构造方法如下所示:public SimpleCursorAdapter (Context contex, int layout, Cursor c,String[]from, int[] to)
l context:当前程序的上下文对象。
l layout:用来描述如何显示在适配器控件上的布局文件的R类引用。
l from:由需要显示出来的列名组成的字符串数组。
l to:由layout所指定的布局文件中子控件的id所组成的整型数组,与from相对应。
下面的代码显示了如何构造一个SimpleCursorAdapter用于显示数据库的信息:
String uriString ="content://contacts/people/";
// 获取到游标
Cursor myCursor= managedQuery(Uri.parse(uriString), null,null, null, null);
// 数据的来源,号码和名字
String[] fromColumns = new String[] {People.NUMBER,People.NAME};
// 号码和名字显示到的UI组件
int[] toLayoutIDs = newint[] { R.id.nameTextView, R.id.numberTextView};
SimpleCursorAdapter myAdapter;
// 新建SimpleCursorAdapter适配器,将数据和UI组件联系上
myAdapter = newSimpleCursorAdapter(this,R.layout.simplecursorlayout,myCursor,fromColumns,toLayoutIDs);
myListView.setAdapter(myAdapter);
上面的代码中,SimpleCursorAdapter将联系人表和ListView关联起来,在ListView中显示联系人的姓名和电话号码。
1.4. SQLite实例
通过上节大家学习了SQLite的基本信息和相关类,本节,我关云长要展现我高大威猛的形象,就让我关云长建立一个数据库,通过操作数据库,让大家一起来学习如何使用SQLite。新建一个Android项目,结构如图14-2所示:图14-2 SQLite示例工程结构图
1.4.1.创建数据库
工程的第一步就是创建数据库,Android SDK提供了几个重要的方法用于数据库的创建和删除:l Context.createDatabase(String name,int version ,intmode,CursorFactory factory):创建一个新的数据库并返回一个SQLiteDatabase对象,假如数据库不能被创建,抛出FileNotFoundException异常。
l Context.openOrCreateDatabase(String name,intmode,CursorFactory factory):该方法是打开一个数据库,如果没有的话,则会创建。
l Context.deleteDatabase(String name):删除指定名称的数据库,假如数据库成功删除则返回true,失败则为false(例如数据库不存在)。
在执行完上面的数据库创建代码后,系统就会在“/data/data/[PACKAGE_NAME]/databases”目录下生成一个“HEROS.db”的数据库文件,如图14-3所示:
图14-3 SQLite 数据库位置
当完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。
1.4.2.导入数据库
在实际操作中,数据库可能需要存入大量的数据,而这些数据通过Android SDK提供的接口录入不免麻烦很多,一般来说事先通过工具建立好数据库,将数据库在程序第一次初始化时导入,较为方便。下面一段代码显示了如何导入数据库,主要利用文件输入输出流,将指定目录下的数据库文件复制到应用程序的特定目录下。String DB_PATH = "/data"
+ Environment.getDataDirectory().getAbsolutePath() + "/"
+ packageName+"/databases"+/name; //在手机里存放数据库的位置
File database=newFile(DB_PATH);
//判断数据库文件是否存在,若不存在则执行导入,否则直接打开数据库
if(!(database.exists())) {
database.mkdirs();
}
InputStream is = this.context.getAssets().open(dbName);
//欲导入的数据库
FileOutputStream fos = newFileOutputStream(dbfile);
byte[] buffer = newbyte[BUFFER_SIZE];
int count = 0;
while ((count =is.read(buffer)) > 0) {
fos.write(buffer, 0, count);
}
fos.close();
is.close();
也可以调用如下代码打开存储在SD卡上的数据库:
File name = newFile("/sdcard/ HEROS.db");
return SQLiteDatabase.openOrCreateDatabase(name, null);
1.4.3. 操作数据库
本节通过一个实例展示如何操作数据库,实例实现了将英雄的名字、武器、攻击力和防御力的数据插入、更新、删除到数据库的功能,运行程序,结果如图14-4所示:图14-4运行结果
首先,创建一个ListAdapter类,该类继承BaseAdapter,作为数据库数据显示的适配器。代码如下所示:
ListAdapter.java代码清单14-4-3:
/**
* 列表适配器.
* @author关羽:编程的烦恼就12个字:放不下,想不开,看不透,忘不了。
*/
public class ListAdapter extendsBaseAdapter {
// 主界面的句柄
private Context mContext;
// 操作数据库集合类
private Cursor mCursor;
// 列表适配器构造函数
public ListAdapter(Context context, Cursor cursor) {
mContext =context;
mCursor =cursor;
}
// 获取列表长度
@Override
public int getCount() {
return mCursor.getCount();
}
// 根据索引获取集合中的一个对象
@Override
public Object getItem(intposition) {
return null;
}
// 获取的行ID列表中的指定位置
@Override
public long getItemId(intposition) {
return 0;
}
/**
* @author关羽:我是大英雄,哈哈,Hold 不住啊!
* @param position position就是位置从0开始
* @param convertView convertView是ListView中每一项要显示的view
* @param parent parent就是父窗体了
* @return通常return 的view也就是convertView
*/
@Override
public View getView(intposition, View convertView, ViewGroup parent) {
TextViewmTextView = newTextView(mContext);
mTextView.setTextSize(20);
mCursor.moveToPosition(position);
mTextView.setText("英雄名字:" +mCursor.getString(1) + "英雄武器:" + mCursor.getString(2) + ""
+"英雄攻击力:" + mCursor.getString(3) + "英雄防御力" + mCursor.getString(4));
return mTextView;
}
}
上面代码自定义了一个BaseAdapter,包含一个Cursor类成员变量。通过该Cursor可以获取数据库HEROS表中的数据。在getView()方法中,实例化了一个TextView用于显示数据库HEROS表中英雄名字、英雄武器、英雄攻击力和英雄防御力信息。
然后,新建一个Activity,命名为MainActivity,在MainActivity中添加了增加、删除、更新按钮,用于操作数据库。代码如下所示:
MainActivity.java代码清单14-4-3:
/**
* @author关羽:这年头什么也靠不住,只有自己靠自己,简称:我…靠!
*/
public class MainActivity extendsActivity implementsOnItemClickListener {
// 数据库操作类
private HerosDB mHerosDB;
// 数据库数据源
private Cursor mCursor;
// 英雄名
private EditText heroName;
// 英雄武器
private EditText heroWeapon;
// 英雄攻击力
private EditText heroAttack;
// 英雄防御力
private EditText heroDefence;
// 存储数据按钮
private Button saveButton;
// 删除数据按钮
private Button deleteButton;
// 更新数据按钮
private Button updateButton;
// 存储数据监听
private OnClickListener saveButtonListener = null;
// 删除数据监听
private OnClickListener deleteButtonListener = null;
// 更新数据监听
private OnClickListener updateButtonListener = null;
// 列表
private ListView list;
// 操作的数据的Id
private int Hero_ID = 0;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setListener();
findView();
}
/**
* @author关羽:三弟会放一起,我也会放一起,哈哈,我可不能被三弟比下去
*/
private void findView() {
list =(ListView)findViewById(R.id.list);
updateButton = (Button)findViewById(R.id.updateButton);
updateButton.setOnClickListener(updateButtonListener);
saveButton= (Button)findViewById(R.id.saveButton);
saveButton.setOnClickListener(saveButtonListener);
deleteButton= (Button)findViewById(R.id.deleteButton);
deleteButton.setOnClickListener(deleteButtonListener);
heroName =(EditText)findViewById(R.id.name);
heroWeapon= (EditText)findViewById(R.id.weapon);
heroAttack= (EditText)findViewById(R.id.attack);
heroDefence= (EditText)findViewById(R.id.defence);
mHerosDB = new HerosDB(this);
mCursor =mHerosDB.select();
list.setAdapter(newListAdapter(this, mCursor));
list.setOnItemClickListener(this);
}
/**
* @author关羽设置监听
*/
private void setListener() {
updateButtonListener = newOnClickListener() {
@Override
public void onClick(View v) {
// 更新
update();
}
};
saveButtonListener = newOnClickListener() {
@Override
public void onClick(View v) {
// 添加数据
add();
}
};
deleteButtonListener = newOnClickListener() {
@Override
public void onClick(View v) {
// 删除数据
delete();
}
};
}
public void add() {
// 读取数据
String name= heroName.getText().toString();
Stringweapon = heroWeapon.getText().toString();
Stringattack = heroAttack.getText().toString();
Stringdefence = heroDefence.getText().toString();
// 防止数据为空
if (name.equals("") ||weapon.equals("") || attack.equals("") || defence.equals("")){
return;
}
// 插入数据
mHerosDB.insert(name, weapon, attack, defence);
// 执行查询,创建光标再次刷新其内容。这可以在任何时候用,包括通话后停用。
mCursor.requery();
// 更新列表
list.invalidateViews();
heroName.setText("");
heroWeapon.setText("");
heroAttack.setText("");
heroDefence.setText("");
Toast.makeText(this,"插入英雄成功了!", Toast.LENGTH_SHORT).show();
}
public void delete() {
// 没有数据
if (Hero_ID == 0) {
return;
}
// 删除制定Id数据
mHerosDB.delete(Hero_ID);
// 执行查询,创建光标再次刷新其内容。这可以在任何时候用,包括通话后停用。
mCursor.requery();
// 更新列表
list.invalidateViews();
heroName.setText("");
heroWeapon.setText("");
heroAttack.setText("");
heroDefence.setText("");
Toast.makeText(this,"删除英雄成功了!", Toast.LENGTH_SHORT).show();
}
public void update() {
// 读取数据
String name= heroName.getText().toString();
Stringweapon = heroWeapon.getText().toString();
Stringattack = heroAttack.getText().toString();
Stringdefence = heroDefence.getText().toString();
// 防止数据为空
if (name.equals("") ||weapon.equals("") || attack.equals("") ||defence.equals("")) {
return;
}
// 更新指定id数据
mHerosDB.update(Hero_ID, name, weapon, attack, defence);
// 执行查询,创建光标再次刷新其内容。这可以在任何时候用,包括通话后停用。
mCursor.requery();
// 更新列表
list.invalidateViews();
heroName.setText("");
heroWeapon.setText("");
heroAttack.setText("");
heroDefence.setText("");
Toast.makeText(this,"更新英雄成功了!", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 点击移动到指定的位置
mCursor.moveToPosition(position);
// 更新操作id
Hero_ID =mCursor.getInt(0);
heroName.setText(mCursor.getString(1));
heroWeapon.setText(mCursor.getString(2));
heroAttack.setText(mCursor.getString(3));
heroDefence.setText(mCursor.getString(4));
}
}
MainActivity提供了存储、删除、更新三个按钮,这三个按钮的功能分别在add()、delete()、update()这三个方法中进行了实现。
数据库的主要操作代码定义在HerosDB类里面。在实际开发中,为了能够更好的管理和维护数据库,会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,封装业务逻辑,如HerosDB类。在HerosDB类中为表名和列名定义字符串静态常量和常见数据库操作方法,代码如下所示:
HerosDB.java代码清单14-4-3:
/**
* 数据库操作类.
* @author关羽:我当年也是个痴情的种子,结果下了场雨……淹死了。
*/
public class HerosDB extendsSQLiteOpenHelper {
// 数据库名字
privatestatic finalString DATABASE_NAME= "HEROS.db";
// 数据库版本
private staticfinalintDATABASE_VERSION = 1;
// 数据库表
private staticfinalString TABLE_NAME= "heros_table";
// 数据库英雄表英雄列表id
publicstatic final String HERO_ID= "heros_id";
// 数据库英雄表英雄列字段名
publicstatic final String HERO_NAME= "hero_name";
// 数据库英雄表英雄武器列字段名
publicstatic final String HERO_WEAPON= "hero_weapon";
// 数据库英雄表攻击力列字段名
publicstatic final String HERO_ATTACK= "hero_attack";
// 数据库英雄防御力列字段名
publicstatic final String HERO_DEFENCE= "hero_defence";
// 构造函数
public HerosDB(Context context) {
super(context, DATABASE_NAME,null, DATABASE_VERSION);
}
// 创建table
@Override
public void onCreate(SQLiteDatabase db) {
String sql= "CREATE TABLE " + TABLE_NAME+ " (" + HERO_ID
+ " INTEGER primary key autoincrement," + HERO_NAME + "text, " + HERO_WEAPON
+" text, " + HERO_ATTACK+ " text, " + HERO_DEFENCE+ " text" + ");";
db.execSQL(sql);
}
// 更新接口
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, intnewVersion) {
String sql= "DROP TABLE IF EXISTS " + TABLE_NAME;
db.execSQL(sql);
onCreate(db);
}
/**
* @author关羽查询语句
* @return
*/
public Cursor select() {
// 获得数据库操作类
SQLiteDatabase db = this.getReadableDatabase();
Cursorcursor = db.query(TABLE_NAME, null, null, null, null, null, null);
return cursor;
}
/**
* @author关羽插入操作
*/
public long insert(String name, String weapon, String attack, Stringdefence) {
SQLiteDatabase db = this.getWritableDatabase();
//ContentValues 存储数据列元素
ContentValues cv = newContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 插入数据返回列位置
long row = db.insert(TABLE_NAME,null, cv);
return row;
}
/**
* @author关羽插入操作删除数据
*/
public void delete(intid) {
SQLiteDatabase db = this.getWritableDatabase();
Stringwhere = HERO_ID + " =?";
String[]whereValue = {
Integer.toString(id)
};
// 删除数据表名、删除条件、删除的位置
db.delete(TABLE_NAME, where, whereValue);
}
/**
* @author关羽修改数据
*/
public void update(intid, String name, String weapon, String attack, String defence) {
SQLiteDatabase db = this.getWritableDatabase();
String where = HERO_ID + " = ?";
String[]whereValue = {
Integer.toString(id)
};
ContentValues cv = newContentValues();
cv.put(HERO_NAME, name);
cv.put(HERO_WEAPON, weapon);
cv.put(HERO_ATTACK, attack);
cv.put(HERO_DEFENCE, defence);
// 更新数据,表、更新的数据、更新的条件、更新的位置
db.update(TABLE_NAME, cv, where, whereValue);
}
}
细心的读者可能会注意到,这里我们并没有指定_id列的值。这是因为SQLite数据库中将所有声明为“INTEGER PRIMARY KEY”的列自动识别成自增列。在插入一行数据的时候,若不指定自增列的数据或给自增列传入NULL值时,会自动给自增列赋一个所有行中此列里的最大值加1的数。若添加的是第一行则从数字1开始。而这里的_id就是一个自增列,所以在插入这行数据后,此行数据_id列的值为1。
1.5. 玄德有话说
张飞:二哥,我想存储图片和音频文件到数据库中,行不行?关羽:老飞啊,你的想法很独特嘛。当然可以,你可以将图片转换成byte[]数组存入到数据库中去,再从数据库中取出来转换成图像显示出来。但是不建议这样使用,一般图片,文件和二进制数据,我们是不建议存在数据库中的,因为这会带来很多的问题。对数据库的读/写的速度永远都赶不上文件系统处理的速度。同时也会使得,数据库备份变的巨大,越来越耗时间,给自己行个方便吧,在数据库里只简单的存放一个磁盘上你的文件的相对路径。
张飞:二哥真是功力深厚呀,那为什么要给每个表增加一个id?
关羽:表包含一个自动增加的键域,从而为每一行提供唯一的索引,保证了每一行数据的唯一性,同时增加了索引,一定程度上可以加快查找速度。
张飞:二哥我想更新我的表怎么办?
关羽:SQLite提供了ALTER TABLE命令,允许用户重命名或添加新的字段到已有表中,但是不能从表中删除字段噢。
张飞:二哥,我觉得用Sqlite数据库时,经常需要进行机械性的CRUD操作,可不可以进行了一下封装,去除这些繁琐的操作?
关羽:现在已经有不少第三方Android数据库的持久层开发包啦,例如Androrm、ormlite。
张飞:二哥,我的数据库应用现在总是崩溃,怎么办?
关羽:数据库应用的调试不同于一般的程序,要考虑到数据在数据库的状况,出现崩溃不要慌,检查SQL语句,检查数据和表的结构是否一致,检查数据库表是否更新上了。还有常见的导致崩溃的问题,例如创建多个数据库,反复创建同一张表,SQL语句特殊字符没有转义,不注意空格等。
张飞:二哥最后一个问题!
关羽:你说吧!
张飞:今天大哥为什么不在?
关羽:额……大哥可能休产假去了吧,今天我只能勉为其难了。
相关文章推荐
- hibernate正向工程生成数据库
- oracle 存储过程
- 数据库命令行操作
- Oracle中REGEXP_SUBSTR函数
- 编程, 细心永远都不嫌多(记录java连接数据库的一个错误)
- 10015---MySQL--innodb_flush_log_at_trx_commit参数
- oracle数据库中的联接查询
- Oracle INV - SO line backorder API
- 连接数据库方法
- 数据库事务隔离级别与锁
- PLsql美化规则
- 范式(Oracle)
- plsql oracle客户端配置
- 安卓内部存储(三)SQLite数据库
- PetaPoco利用ODP.NET Managed Driver连接Oracle
- Mysql连接出错问题
- 上Mysql com.mysql.jdbc.StatementImpl$CancelTask内存泄漏问题和解决方法
- sql 执行 delete 的时候,结合子查询 exists ,怎样支持别名呢?
- Redis中的String类型操作
- SQL Server 创建约束图解 唯一 主键