一、SQLite数据库简介
SQLite一个关系型数据库,SQLite3支持NULL、INTEGER、REAAL(浮点数字)、TEXT(字符串文本)、和BLOB(二进制对象),虽然它只支持这5种类型,但是实际上它也接受varchar(n)、char(n)、decimal(p, s)等数据类型,只是在运算或保存时会转换成对应的5种数据类型。
SQLite是Android所带的一个标准的数据库,它是一个轻量级的嵌入式数据库,SQLite数据库的特点:更加适用于嵌入式系统,嵌入使用它的应用程序中;占用内存非常少,运行高效可靠,可移植性好;提供了零配置(zero-configuration)运行模式。SQLite数据库不仅提高了运行效率,而且屏蔽了数据库使用和管理的复杂性,程序仅需要进行最基本的数据操作,其他操作可以交给程序内部的数据库引擎完成。SQLite核心大约有3万行标准C代码,并且其源代码开放。
SQLite数据库的模块化设计,由8个独立的模构成,这些独立模块又构成了三个主要的子系统:编译器、核心模块及后端,模块将复杂的查询过程分解为细小的工作进行处理。其模块结构图如下:
接口由SQLite C API组成,该API简单易用,无论是应用程序、脚本,还是库文件,最终都是通过接口与SQLite交互的。图中的编译器由分词器、分析器和代码生成器组成。分词器和分析器对SQL语句进行语法检查分析后,将SQL语句转化为对于底层来说能够更方便处理的分层的数据结构,这种分层的数据结构称为“语法树”,生成的语法树将传递给代码生成器,由代码生成器进行处理,生成一种针对SQLite的汇编代码,该部分代码最后交与虚拟机执行。
SQLite数据库体系结构中最核心的部分是虚拟机,也称为虚拟数据库引擎(Virtual Database engine, VDBE)。与Java虚拟类似,虚拟机是用来解释执行字节码的。虚拟机的字节码由128个操作码组成,这些操作码主要用于操作数据库,每一条指令都可以完成特定的数据库操作,或以特定的方式处理栈的内容。
后端由B-树、页缓存和操作系统接口组成。B-树的主要功能就是索引,它维护着各个页面之间的复杂关系,便于快速找到所需数据;页缓存主要就是通过操作系统接口在B-树和磁盘之间传递页面;B-树和页缓存共同对数据进行管理。
二、SQLite的常用类及程序案例
在Android系统中,如果要进行SQLite数据库的操作,则主要使用以下几个类和接口:
(1)android.database.sqlite.SQLiteDatabase:完成数据的CRUD操作及事务处理
(2)android.database.sqlite.SQLiteOpenHelper:定义数据库的创建及更新操作
(3)android.database.Cursor:保存所有的查询结果
(4)android.content.ContentValues:对传递的数值进行封装
在Android系统中,每一个android.database.sqlite.SQLiteDatabase类的实例都代表了一个SQLite数据库的操作,通过SQLiteDatabase类可以执行SQL语句,以完成对数据表的增删改查操作,或者进行数据库的事务处理,此类中定义了基本的数据库执行SQL语句的操作方法以及一些操作模式常量。在进行开发时,一般不用创建SQLiteDatabase类对象,往往会由一个辅助的工具类SQLiteOpenHelper行操作的管理。SQLiteOpenHelper为中定义了3个回调方法:
(1)onCreate():在第一次使用数据库时会调用此方法生成相应的数据库表,但是此方法并不是在实例化SQLiteOpenHelper类的对象时调用,而是通过对象调用了getReadableDatabase()或getWritableDatabase()方法时才会调用。
(2)onUpgrade():当数据库需要进行升级时会调用此方法,一般可以在此方法中将数据表删除,并在删除表后调用onCreate()方法重新创建新的数据表。
(3)onopen():当数据库打开时会调用此方法,但是一般情况下用户不需要覆写此方法。
在SQLiteDatabase类中,也为用户提供了insert()、update()、delete()、query()等方法,只是在使用这些方法进行数据库操作时,所有的数据必须使用ContentValues类进行封装。android.content.ContentValues的功能与HashMap类的功能类似,都是采用“key=value”的形式保存数据,唯一不同的是,在ContentValues类中所设置的key都是String型的数据,而所设置的value都是基本数据类型的包装类。
当Android程序需要进行数据检索操作时,需要保存全部的查询结果,而保存查询结果可以使用android.database.Cursor接口完成,并且可以完成对结果集随机读写访问的操作。利用Cursor接口提供的一些常用方法,从前到后依次取得全部数据可按如下过程来进行:
(1)moveToFirst():将结果集的指针放在第一行数据。
(2)isAfterLast():判断是否还有数据,如果有,则取出。
(3)moveToNext():将指针向下移动,并继续使用isAfterLast()方法判断。
即:for(result.moveToFirst(); !result.isAfterLast(); result.moveToNext()){循环体;}
在进行数据查询时,分页显示的功能能是很常用的,Android系统通过ListView来实现滑动分页数据的读取,当用户将ListView滑动到底部时,系统可以自动从数据表中向下加载剩余的部分数据。要实现这种滚动刷新的操作,就需要一个滚动监听接口(OnScrollListener)的事件支持,此接口定义如下:
public interface OnScrollListener { //The user had previously been scrolling using touch and had performed a fling. public static int SCROLL_STATE_FLING; //The view is not scrolling. public static int SCROLL_STATE_IDLE; //The user is scrolling using touch, and their finger is still on the screen public static int SCROLL_STATE_TOUCH_SCROLL; /** * Callback method to be invoked when the list or grid has been scrolled. * This will be called after the scroll has completed * * @param view : The view whose scroll state is being reported * @param firstVisibleItem : the index of the first visible cell (ignore if visibleItemCount == 0) * @param visibleItemCount : the number of visible cells * @param totalItemCount : the number of items in the list adaptor */ public abstract void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount); /** * Callback method to be invoked while the list view or grid view is being scrolled. * If the view is being scrolled, this method will be called before the next frame of * the scroll is rendered. In particular, it will be called before any calls to * getView(int, View, ViewGroup). * * @param view : The view whose scroll state is being reported * @param scrollState : The current scroll state. One of SCROLL_STATE_IDLE, * SCROLL_STATE_TOUCH_SCROLL or SCROLL_STATE_IDLE. */ public abstract void onScrollStateChanged(AbsListView view, int scrollState); }
例 1 下面就通过一个实例来演示一下ListView分页操作的实现。程序运行效果截图
该程序代码清单如下:
(1)DatabaseHelper.java:负责取得数据库的连接对象,并创建数据表
(2)Cursor.java:完成分页数据查询的操作类,除返回查询数据之外,也返回全部记录数
(3)MainActivity.java:程序操作的Activity主类
(4)main.xml:程序的主体布局管理器,为MainActivity.java类提供布局支持
(5)tab_info.xml:ListView列表显示所需要的布局管理器
以下是详细源码:
(1)DatabaseHelper.java
package org.lion.sqlitetest; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASENAME = "runninglion.db"; private static final int DATABASEVERSION = 1; private static final String TABLENAME = "tablion"; public DatabaseHelper(Context context) { super(context, DATABASENAME, null, DATABASEVERSION); } @Override public void onCreate(SQLiteDatabase db) { //创建数据表 String sql = "create table " + TABLENAME + "(" //sql语句 + "id integer primary key," + "name varchar(50) not null," + "birthday date not null)"; db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { String sql = "drop table if exists " + TABLENAME; db.execSQL(sql); this.onCreate(db); } }
(2)Cursor.java
package org.lion.sqlitetest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; public class TabCursor { private static final String TABLENAME = "tablion"; private SQLiteDatabase db = null; public TabCursor(SQLiteDatabase db){ this.db = db; } /** * 查询出所有满足条件的记录数,使用count()函数,可以用于总页数的计算 * @return */ public int getCount(){ int count = 0; //String sql = "select count(id) from" + TABLENAME; String sql = "select * from " + TABLENAME; Cursor result = db.rawQuery(sql, null); for(result.moveToFirst(); !result.isAfterLast(); result.moveToNext()){ count = result.getInt(0); } return count; } /** * 根据用户给出的两个分页参数,查询出所有的相关数据,并将数据设置到List<Map<String, Object>> * 集合中,以在ListView中显示 * @param currentPage * @param lineSize * @return */ public List<Map<String, Object>> query(int currentPage, int lineSize){//查询 List<Map<String, Object>> all = new ArrayList<Map<String, Object>>(); String sql = "select id,name,birthday from " + TABLENAME + " limit ?,?"; String selectionArgs[] = new String[]{ //设置查询参数 String.valueOf((currentPage-1) * lineSize), String.valueOf(lineSize)}; Cursor result = db.rawQuery(sql, selectionArgs); for(result.moveToFirst(); !result.isAfterLast(); result.moveToNext()){ Map<String, Object> map = new HashMap<String, Object>(); map.put("id", result.getInt(0)); map.put("name", result.getString(1)); map.put("birthday", result.getString(2)); all.add(map); //向集合中保存 } this.db.close(); //关闭数据连接 return all; } }
(3)MainActivity.java
package org.lion.sqlitetest; import java.util.List; import java.util.Map; import org.lion.jsontest.R; import android.app.Activity; import android.database.sqlite.SQLiteOpenHelper; import android.os.Bundle; import android.view.Gravity; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; public class MainActivity extends Activity { private SQLiteOpenHelper helper = null; private LinearLayout layout = null; private ListView listView = null; private int currentPage = 1; //当前程序所在页 private int lineSize = 15; //每页显示的记录长度 private int allRecorders = 0; //保存全部记录数 private int pageSize = 1; //计算当前显示的总页数 private int lastItem = 0; //保存最后一个记录点 private SimpleAdapter simpleAdapter = null; private LinearLayout loadLayout = null; private TextView loadInfo = null; //定义提示文本 private List<Map<String, Object>> all = null; private LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.main); layout = (LinearLayout)findViewById(R.id.mainlayout); loadLayout = new LinearLayout(this); helper = new DatabaseHelper(this); //定义数据库辅助类 loadInfo = new TextView(this); loadInfo.setText("Loading data..."); loadInfo.setGravity(Gravity.CENTER); loadInfo.setTextSize(25.0f); loadLayout.addView(this.loadInfo, this.layoutParams); loadLayout.setGravity(Gravity.CENTER); this.showAllData(); //显示数据 pageSize = (allRecorders + currentPage - 1)/lineSize; //求出总页数 } private void showAllData(){ helper = new DatabaseHelper(MainActivity.this); listView = new ListView(MainActivity.this); TabCursor cursor = new TabCursor(helper.getReadableDatabase()); allRecorders = cursor.getCount(); all = cursor.query(currentPage, lineSize); simpleAdapter = new SimpleAdapter(this, all, //将数据包装 R.layout.tab_info, //每行显示一条数据 new String[]{"id","name","birthday"}, //设置组件的字段 new int[]{R.id.id,R.id.name,R.id.birthday}); //实例化适配器对象 listView.addFooterView(loadLayout); //增加一个标注 listView.setAdapter(simpleAdapter); //设置显示数据 listView.setOnScrollListener(new OnScrollListenerImpl()); layout.addView(listView); //追加组件 } private class OnScrollListenerImpl implements OnScrollListener{ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount){ lastItem = firstVisibleItem + visibleItemCount - 1; } @Override public void onScrollStateChanged(AbsListView view, int scrollState){ if(lastItem == simpleAdapter.getCount() && currentPage<pageSize && scrollState == OnScrollListener.SCROLL_STATE_IDLE){ currentPage++; //修改当前所在页 listView.setSelection(lastItem); //设置显示位置 appendData(); //刷新显示数据 } } } private void appendData(){ TabCursor cursor = new TabCursor(helper.getReadableDatabase()); List<Map<String, Object>> newdata = cursor.query(currentPage, lineSize); all.addAll(newdata); //追加数据 simpleAdapter.notifyDataSetChanged(); //更新记录通知 } }
(4)main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/mainlayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#00ff00" android:orientation="vertical"> </LinearLayout>
(5)tab_info.xml
<?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TableRow> <TextView android:id="@+id/id" android:textSize="20px" android:layout_height="wrap_content" android:layout_width="30px"/> <TextView android:id="@+id/name" android:textSize="20px" android:layout_height="wrap_content" android:layout_width="130px"/> <TextView android:id="@+id/birthday" android:textSize="20px" android:layout_height="wrap_content" android:layout_width="160px"/> </TableRow> </TableLayout>
三、SQLite的事务处理
在SQLite操作中也支持事务处理的,事务处理主要使用android.database.sqlite.SQLiteDatabase为中的以下3个方法:
(1)开始事务:public void begin Transaction()
(2)提交更新或回滚事务:public void setTransactionSuccessful()
(3)结束事务:public void endTransaction()
例 2 事务处理程序案例:
import android.database.sqlite.SQLiteDatabase; public class TransactionTest { private static final String TABLENAME = "tablion"; private SQLiteDatabase db = null; public TransactionTest(SQLiteDatabase db){ this.db = db; } public void insertBatch(){ db.beginTransaction(); //开始事务 try{ db.execSQL("insert into " + TABLENAME + "(id,name,birthday)values(?, ?)", new Object[]{"lion", "1991-02-03"}); //其它操作...... db.setTransactionSuccessful(); //如果不能正确执行,则回滚操作 }catch(Exception e){ }finally{ db.endTransaction(); //结束事务 } db.close(); } }