Android数据库

Android数据库

什么情况下我们才用数据库做数据存储?

大量数据结构相同的数据需要存储时。Android内置了sqlite,轻量级。

创建数据库的方法

  1. 创建一个类继承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

上一篇:Android自定义控件 开源组件SlidingMenu的项目集成


下一篇:C语言宏定义取得两数的最大值和最小值