Android数据库
什么情况下我们才用数据库做数据存储?
大量数据结构相同的数据需要存储时。Android内置了sqlite,轻量级。
创建数据库的方法
- 创建一个类继承SqliteOpenHelper,需要添加一个构造方法,实现两个方法oncreate ,onupgrade。
package com.example.databasedemo;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDatabaseOpenHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table book ("
+ "_id integer primary key autoincrement, name varchar(20), telephone varchar(11))";
public static final String NEW_TABLE = "create table book ("
+ "_id integer primary key autoincrement, price real, pages integer)";
private Context mContext;
/**
*
* @param context 上下文
* @param name 数据库的名称
* @param factory 用来创建cursor对象,填入null使用默认的
* @param version version:数据库的版本号,从1开始,如果发生改变,onUpgrade方法将会调用,4.0之后只能升不能降
*/
public MyDatabaseOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
// 调用getReadableDatabase()或者getWritableDatabase()时会调用该方法
// 第一次创建数据库时才能执行该方法,特别适合做表结构的初始化
// 传入的参数db可以用来执行sql语句
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(NEW_TABLE); // 另外一个表
}
// version改变时,调用这个方法,version只能升不能降
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 先删除,不删除就onCreate发现表存在会报错。
// 若是表被删除了,或者oncreate里面又新建一个表。因为之前已经创建了aa.db,onCreate()方法不会得到执行。则不能创建成功。所以这里需要删除后再重建
db.execSQL("drop table if exists book");
db.execSQL("drop table if exists people");
// 强制执行onCreate
onCreate(db);
db.execSQL("alter table book add author varchar(20)");
db.execSQL("alter table people add age integer");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
// 1.创建一个帮助类的对象,调用getReadableDatabase方法,返回一个SqliteDatebase对象
MyDatabaseOpenHelper dbHelper = new MyDatabaseOpenHelper(mContext, "demo.db", null, 15);
// 创建数据库,有则打开,没有则create
dbHelper.getReadableDatabase();
}
帮助类对象中的getWritableDatabase 和 getReadableDatabase都可以帮助我们获取一个数据库操作对象SqliteDatabase。
区别:
- getReadableDatabase: 先尝试以读写方式打开数据库,如果磁盘空间满了,他会重新尝试以只读方式打开数据库。
- getWritableDatabase: 直接以读写方式打开数据库,如果磁盘空间满了,就直接报错。
数据库的CURD - 1
对上面建的book表进行CURD
- 使用上面的MyDatabaseOpenHelper创建数据库和表。
- 封装一个InfoBean来存储表的信息。
封装一个InfoDao来返回一个MyDatabaseOpenHelper,以及执行增删改查操作。
bean用来封装表的数据。
package com.example.databasedemo.dao;
// 这些变量名和建表时候字段对应
public class InfoBean {
public int _id;
public String name;
public int age;
public String telephone;
}
封装好的执行增删改查的类,注意db不要随便db.close
,容易引发错误。
package com.example.databasedemo.dao;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.example.databasedemo.MyDatabaseOpenHelper;
public class InfoDao {
private MyDatabaseOpenHelper myDatabaseOpenHelper;
private SQLiteDatabase db;
public InfoDao(Context context ,String dbName , int version){
//创建一个帮助类对象
myDatabaseOpenHelper = new MyDatabaseOpenHelper(context, dbName, null, version);
db = myDatabaseOpenHelper.getReadableDatabase();
}
public void add(InfoBean bean){
//sql:sql语句, bindArgs:sql语句中占位符的值
db.execSQL("insert into people(name,telephone) values(?,?);", new Object[]{bean.name,bean.telephone});
}
public void del(String name){
//sql:sql语句, bindArgs:sql语句中占位符的值
db.execSQL("delete from people where name=?;", new Object[]{name});
}
public void update(InfoBean bean){
//sql:sql语句, bindArgs:sql语句中占位符的值
db.execSQL("update people set telephone=? where name=?;", new Object[]{bean.telephone, bean.name});
}
public void query(String name){
//原始查询 --> sql:sql语句, selectionArgs:查询条件占位符的值,返回一个cursor对象
Cursor cursor = db.rawQuery("select _id, name, telephone from people where name = ?;", new String []{name});
//解析Cursor中的数据
if(cursor != null && cursor.getCount() >0){//判断cursor中是否存在数据
//循环遍历结果集,获取每一行的内容
while(cursor.moveToNext()){ //条件,游标能否定位到下一行
//获取数据
int id = cursor.getInt(cursor.getColumnIndex("_id"));
String name_str = cursor.getString(cursor.getColumnIndex("name"));
String phone = cursor.getString(cursor.getColumnIndex("telephone"));
Log.d("query result", "[ _id:"+id+" ,name:"+name_str+" ,phone:"+phone+" ]");
}
cursor.close();//关闭结果集
}
}
}
布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
tools:context="com.example.databasedemo.MainActivity">
<Button
android:id="@+id/bt_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add"/>
<Button
android:id="@+id/bt_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/update"/>
<Button
android:id="@+id/bt_del"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/del"/>
<Button
android:id="@+id/bt_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/query"/>
</LinearLayout>
MainActivity
package com.example.databasedemo;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.example.databasedemo.dao.InfoBean;
import com.example.databasedemo.dao.InfoDao;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Context mContext;
private InfoDao infoDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
// 创建数据库,有则打开,没有则create
infoDao = new InfoDao(mContext, "test.db", 3);
Button btAdd = (Button) findViewById(R.id.bt_add);
Button btDel = (Button) findViewById(R.id.bt_del);
Button btUpdate = (Button) findViewById(R.id.bt_update);
Button btQuery = (Button) findViewById(R.id.bt_query);
btAdd.setOnClickListener(this);
btDel.setOnClickListener(this);
btUpdate.setOnClickListener(this);
btQuery.setOnClickListener(this);
}
@Override
public void onClick(View v) {
InfoBean bean = null;
InfoBean bean1 = null;
switch (v.getId()) {
case R.id.bt_add:
bean = new InfoBean();
bean.name = "张三";
bean.telephone = "119";
infoDao.add(bean);
bean1 = new InfoBean();
bean1.name = "李四";
bean1.telephone = "120";
infoDao.add(bean1);
break;
case R.id.bt_del:
infoDao.del("张三");
break;
case R.id.bt_update:
bean = new InfoBean();
bean.name = "张三";
bean.telephone = "110";
infoDao.update(bean);
break;
case R.id.bt_query:
infoDao.query("张三");
infoDao.query("李四");
break;
default:
break;
}
}
}
上面的方法基本是手写sql语句,容易写错。还有一种更便捷的方法 。
数据库的CURD -2
使用db.insert()、db.delete()、db.update()、db.query()
package dao;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.example.databasedemo.MyDatabaseOpenHelper;
public class InfoDao {
private MyDatabaseOpenHelper myDatabaseOpenHelper;
private SQLiteDatabase db;
public InfoDao(Context context ,String dbName , int version){
//创建一个帮助类对象
myDatabaseOpenHelper = new MyDatabaseOpenHelper(context, dbName, null, version);
db = myDatabaseOpenHelper.getReadableDatabase();
}
public boolean add(InfoBean bean){
//执行sql语句需要sqliteDatabase对象
ContentValues values = new ContentValues(); // 是用Map封装的对象
values.put("name", bean.name);
values.put("telephone", bean.telephone);
// 第二个参数可以为空,返回值表示新增的行号,-1表示添加失败
long result = db.insert("people", null, values);
return result != -1;
}
public int del(String name){
//执行sql语句需要sqliteDatabase对象
int count = db.delete("people", "name = ?", new String[]{name});
return count;
}
public int update(InfoBean bean){
ContentValues values = new ContentValues();
values.put("telephone", bean.telephone);
int count = db.update("people", values, "name = ?", new String[]{bean.name});
return count;
}
public void query(String name){
// 查询people表中的name为参数指定的"_id", "name", "telephone"字段,按照id递减
Cursor cursor = db.query("people", new String[]{"_id", "name", "telephone"}, "name = ?", new String[]{name}, null, null, "_id desc");
//解析Cursor中的数据
if(cursor != null && cursor.getCount() >0){//判断cursor中是否存在数据
//循环遍历结果集,获取每一行的内容
while(cursor.moveToNext()){ //条件,游标能否定位到下一行
//获取数据
int id = cursor.getInt(cursor.getColumnIndex("_id"));
String name_str = cursor.getString(cursor.getColumnIndex("name"));
String phone = cursor.getString(cursor.getColumnIndex("telephone"));
Log.d("query result", "[ _id:"+id+" ,name:"+name_str+" ,phone:"+phone+" ]");
}
cursor.close();//关闭结果集
}
}
}
@Override
public void onClick(View v) {
InfoBean bean = null;
int count = 0;
switch (v.getId()) {
case R.id.bt_add:
bean = new InfoBean();
bean.name = "张三";
bean.telephone = 119;
boolean result = infoDao.add(bean);
if (result) {
Toast.makeText(mContext, "添加成功", Toast.LENGTH_SHORT).show();
}
break;
case R.id.bt_del:
count = infoDao.del("张三");
Toast.makeText(mContext, "删除"+count+"行", Toast.LENGTH_SHORT).show();
break;
case R.id.bt_update:
bean = new InfoBean();
bean.name = "张三";
bean.telephone = 110;
count = infoDao.update(bean);
Toast.makeText(mContext, "更新"+count+"行", Toast.LENGTH_SHORT).show();
break;
case R.id.bt_query:
infoDao.query("张三");
break;
default:
break;
}
}
使用以上方法不容易写错sql语句,而且其返回值能方便地知道数据变化了几条。
使用第二种方法更简单一些,但是不能多表查询(传参时候只能传入一个table)。而第一种手写rawQuery()的方法可以实现。可谓各有利弊。
数据库中的事务
执行多条sql语句,要么同时执行成功,要么同时执行失败。不能有的成功,有的失败。失败了则会回滚。
举个银行转账的例子。因为各种原因比如在转账过程中突然断电断网,不能使得资金流失。李四个张三转200,张三要么收到两百。要么退还给李四(回滚到未转钱的时候)。
还是需要一个继承自SQLiteOpenHelper的类,不过这次的比较简单了。
package com.example.trancaction;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDatabaseOpenHelper extends SQLiteOpenHelper {
/**
* @param context 上下文
* @param name 数据库的名称
* @param factory 用来创建cursor对象,填入null使用默认的
* @param version version:数据库的版本号,从1开始,如果发生改变,onUpgrade方法将会调用,4.0之后只能升不能降
*/
public MyDatabaseOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
// 直接在创建表的时候就添加数据
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table account (_id integer primary key autoincrement,name varchar(20),money varchar(20))");
db.execSQL("insert into account ('name','money') values ('张三','2000')");
db.execSQL("insert into account ('name','money') values ('李四','5000')");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO: 2017/4/11
}
}
package com.example.trancaction;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
Button btTransfer = (Button) findViewById(R.id.bt_transfer);
btTransfer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MyDatabaseOpenHelper dbHelper = new MyDatabaseOpenHelper(mContext, "account.db", null, 1);
SQLiteDatabase db = dbHelper.getReadableDatabase();
//3.转账,将李四的钱减200,张三加200
db.beginTransaction();//开启一个数据库事务
try {
// 如果没有事务,这里只会执行李四的钱转出,张三收不到。
db.execSQL("update account set money= money-200 where name=?", new String[]{"李四"});
int i = 100 / 0;//模拟一个异常
db.execSQL("update account set money= money+200 where name=?", new String[]{"张三"});
db.setTransactionSuccessful();//能运行到最后这儿,就标记事务中的sql语句全部成功执行
} finally {
db.endTransaction();//判断事务的标记是否成功,如果不成功,回滚错误之前执行的sql语句
}
}
});
}
}
by @sunhaiyu
2017.4.13