转载请标明出处: http://blog.csdn.net/airsaid/article/details/52419418
本文出自:周游的博客
Android中有许多的数据存储方式,如果我们有少量的数据需要存储,那么使用:SharedPreferences、文件存储就可以了。但是如果有大量数据需要进行读写,那么就需要使用到数据库了。Android中内置了SQLite数据库,而SQLite数据库是一个真正轻量级的数据库,它并没有后台进程,整个数据库就对应于一个文件。Android也给我们提供了大量的API,使用起来很方便。
Android提供了SQLiteDatabase对象来管理数据库,SQLiteDatabase有提供方法来创建,删除,执行SQL命令,并执行常见的数据库管理任务。
SQLiteDatabase提供了如下静态方法来打开一个文件对应的数据库:
当我们获取到SQLiteDatabase对象之后,就可以调用SQLiteDatabase如下的方法来对数据库进行操作了:
JAVA方法 | 释义 |
---|---|
execSQL(String sql) | 执行SQL语句 |
execSQL(String sql, Object[] bindArgs) | 执行带占位符的SQL语句 |
insert(String table, String nullColumnHack, ContentValues values) | 向指定表中插入特定数据 |
delete(String table, String whereClause, String[] whereArgs) | 删除指定表中的特定数据 |
update(String table, ContentValues values, String whereClause, String[] whereArgs) | 更新指定表中的数据 |
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy,String having, String orderBy) | 查询执行表中的数据 |
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy,String having, String orderBy, String limit) | 查询执行表中的数据,limit参数控制最多查询几条数据 |
query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs,String groupBy, String having, String orderBy, String limit) | 查询执行表中的数据,distinct 参数表示是否去除重复的值 |
rawQuery(String sql, String[] selectionArgs) | 执行带占位符的SQL语句查询 |
beginTransaction() | 开始事务 |
endTransaction() | 结束事务 |
Android提供了上面的方法,来帮助开发者更“简便”的对数据库进行增删改查。但是其实这些方法完全可以通过SQL语句来完成。而用记上面复杂的参数的时间,就可以来掌握SQL语句了。
上面的方法,都是返回了一个Cursor接口,该接口提供对由数据库查询返回的结果集的随机读写访问,而Cursor同样提供了如下方法来移动查询结果的记录指针:
JAVA方法 | 释义 |
---|---|
move(int offset) | 从当前位置向前或向后移动记录指针,如果移动成功则返回true |
moveToFirst() | 移动记录指针到第一行 |
moveToLast() | 移动记录指针到最后一行 |
moveToNext() | 移动记录指针到下一行 |
moveToPrevious() | 移动记录指针到上一行 |
moveToPosition(int position) | 移动记录指针到指定行 |
isFirst() | 是否指向第一条 |
isLast() | 是否指向最后一条 |
isBeforeFirst() | 是否指向第一条之前 |
isAfterLast() | 是否指向最后一条之后 |
isNull(int columnIndex) | 指定列是否为空(列基数为0) |
isClosed() | 游标是否已关闭 |
getCount() | 总数据项数 |
getPosition() | 返回当前游标所指向的行数 |
getColumnIndex(String columnName) | 返回某列名对应的列索引值 |
getString(int columnIndex) | 返回当前行指定列的值 |
一旦通过以上方法移动到指定行后,就可以调用Cursor的getXXX()方法来获取指定列的数据了。
// 判断数据库文件存放的文件夹是否存在,不存在则创建
String mPath = Environment.getExternalStorageDirectory() + "/db/";
File file = new File(mPath);
if(!file.exists() && !file.isDirectory()){
// 创建目录
file.mkdirs();
}
// 使用静态方法打开数据库(不存在则创建),第二个参数用于返回Course工厂,null则表示使用默认工厂。
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(mPath + "blog.db", null);
// 执行创建表SQL语句,创建了一个名为blog_info的表
String createSql = "create table blog_info(blog_id integer primary key, blog_name varchar(255), blog_link varchar(255))";
database.execSQL(createSql);
注意:由于数据库创建在SD卡中,所以需要加上写入SD卡权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
在创建好数据库和表之后就可以对表中的数据进行管理和操作了。我们可以直接通过execSQL()方法执行SQL语句来操作,也可以使用Android给我们直接提供的增删改查方法。
往刚刚上面创建的数据库blog_info表中插入一条数据:
ContentValues contentValues = new ContentValues();
contentValues.put("blog_name", "Airsaid");
contentValues.put("blog_link", "http://blog.csdn.net/airsaid");
// line为新添加的行号,该行号是一个内部值,与主键id无关,当发生错误时,返回-1。
long line = mDatabase.insert("blog_info", null, contentValues);
删除和”Airsaid”相关的数据:
int num = mDatabase.delete("blog_info", "blog_name like ?", new String[]{"Airsaid"});
Toast.makeText(this, "一共删除了:" + num + "条数据", Toast.LENGTH_SHORT).show();
将所有主键大于5的blog_name改为周游:
ContentValues contentValues = new ContentValues();
contentValues.put("blog_name", "周游");
int num = mDatabase.update("blog_info", contentValues, "_id > ?", new String[]{"5"});
由于查询条件的不确定性,所以query()方法的参数真的是比较长。。感觉建议在写查询语句的时候,可以直接使用rawQuery()方法进行查询。
下面写一个实例,用户分别输入博客名和博客链接,点插入后,将数据存储到数据库,并用列表展示出来:
* 布局:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.airsaid.sqlitedemo.MainActivity">
<EditText
android:id="@+id/edt_blog_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"/>
<EditText
android:id="@+id/edt_blog_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"/>
<Button
android:onClick="insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="插入数据"/>
<ListView
android:id="@+id/listView"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
LinearLayout>
public class MainActivity extends AppCompatActivity {
private String mPath = Environment.getExternalStorageDirectory() + "/db/";
private static final String DB_NAME = "blog.db";
private EditText mEdtName;
private EditText mEdtLink;
private ListView mListView;
private SQLiteDatabase mDatabase;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mEdtName = (EditText) findViewById(R.id.edt_blog_name);
mEdtLink = (EditText) findViewById(R.id.edt_blog_link);
mListView = (ListView) findViewById(R.id.listView);
// 判断数据库文件存放的文件夹是否存在,不存在则创建
File file = new File(mPath);
if(!file.exists() && !file.isDirectory()){
// 创建目录
file.mkdirs();
}
// 使用静态方法打开数据库(不存在则创建),第二个参数用于返回Course工厂,null则表示使用默认工厂。
mDatabase = SQLiteDatabase.openOrCreateDatabase(mPath + DB_NAME, null);
// 执行创建表SQL语句,创建了一个名为blog_info的表
String createSql = "create table blog_info(_id integer primary key, blog_name varchar(255), blog_link varchar(255))";
mDatabase.execSQL(createSql);
}
/**
* 插入数据
*/
public void insert(View v){
// 获取用户输入信息
String name = mEdtName.getText().toString();
String link = mEdtLink.getText().toString();
// 执行SQL语句插入数据
mDatabase.execSQL("insert into blog_info values(null, ?, ?)", new String[]{name, link});
// 填充数据到ListView
Cursor cursor = mDatabase.rawQuery("select * from blog_info", null);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.item_listview_blog, cursor
, new String[]{"blog_name", "blog_link"}, new int[]{R.id.txt_name, R.id.txt_link}
, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
mListView.setAdapter(adapter);
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/txt_name"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
<TextView
android:id="@+id/txt_link"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"/>
LinearLayout>
运行结果:
数据库操作,事务是少不了的,而SQLiteDatabase也提供了如下几个方法来操作事务:
* beginTransaction():开始事务。
* endTransaction():结束事务。
* inTransaction():判断当前上下文是否处于事务中。
当调用endTransaction()方法结束事务的时候,那么到底是提交事务还是进行回滚事务呢?这取决于SQLiteDatabase是否调用了:setTransactionSuccessful()方法来设置事务标志,如果在执行事务的时设置了,当事务成功则提交事务,否则程序将回滚事务。
其实在真实项目开发中,很少使用SQLiteDatabase的方法来打开数据库,而是通过继承Android给我们提供的SQLiteOpenHelper抽象类来创建。
SQLiteOpenHelper是一个辅助类,用于来帮助我们建立和管理数据库。我们只需要继承它,并实现两个抽象方法:
* onCreate(SQLiteDatabase db):在初次生成数据库的时候会被调用,用于生成表结构。
* onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):在数据库版本发生更新时会被调用,oldVersion为旧数据库版本号,newVersion为新版本数据库版本号。
SQLiteOpenHelper有如下方法:
JAVA方法 | 释义 |
---|---|
getWritableDatabase() | 以写的方式打开数据库对应的SQLiteDatabase对象 |
getReadableDatabase() | 以读写的方式打开数据库对应的SQLiteDatabase对象 |
getDatabaseName() | 返回被打开SQLite数据库的名称 |
onConfigure(SQLiteDatabase db) | 正在配置数据库连接时调用 |
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) | 当数据库降级时调用 |
onOpen(SQLiteDatabase db) | 当数据库已经打开时调用 |
setWriteAheadLoggingEnabled(boolean enabled) | 启用或者禁用数据库中使用预写日记记录 |
public class MyDatabaseHelper extends SQLiteOpenHelper {
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 创建表结构
db.execSQL("create table dict(_id integer primary key autoincrement, word, detail)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public void add(View v) {
// 添加生词
String word = mEdtWord.getText().toString();
String detail = mEdtDetail.getText().toString();
if(TextUtils.isEmpty(word) || TextUtils.isEmpty(detail)){
Toast.makeText(this, "输入的数据不能为空", Toast.LENGTH_SHORT).show();
return;
}
SQLiteDatabase database = mDb.getReadableDatabase();
database.execSQL("insert into dict values(null, ? , ?)", new String[]{word, detail});
Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
}
主要是通过SQLiteOpenHelper的 getReadableDatabase()方法获取了SQLiteDatabase对象。这里需要注意的是:
getReadableDatabase()方法会先以读写方式打开数据库,如果数据库的磁盘空间已经满了,那么就会打开失败,然后再尝试以只读的方式打开数据库。
而通过getWritableDatabase()方法,如果磁盘空间已满,打开时就会出错。
public void seek(View v) {
// 查看生词
String dict = mEdtInput.getText().toString();
SQLiteDatabase database = mDb.getReadableDatabase();
Cursor cursor = database.rawQuery("select * from dict where word like ? or detail like ?"
, new String[]{"%" + dict + "%", "%" + dict + "%"});
List<String> dicts = new ArrayList<>();
while (cursor.moveToNext()){
String word = cursor.getString(1);
String detail = cursor.getString(2);
dicts.add("生词:" + word + " 解释:" + detail);
}
showData(dicts);
}
private void showData(List dicts) {
if(dicts.size() < 1) {
Toast.makeText(this, "没有找到到该生词数据", Toast.LENGTH_SHORT).show();
return;
}
String[] strings = dicts.toArray(new String[dicts.size()]);
for (String string : strings) {
Log.e("test", string);
}
new AlertDialog.Builder(this)
.setTitle("查询出来的单词结果")
.setItems(dicts.toArray(new String[dicts.size()]), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
}
参考文章:《疯狂Android讲义》