SQLite本地数据库的应用

前端之家收集整理的这篇文章主要介绍了SQLite本地数据库的应用前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

说明
我们知道savedInstanceState、文件与SharedPreference都能够保存数据,但他们都无法满足应用持久化保存数据的需求,Android为此提供了长期存储地:即sqlite数据库

概述

sqlite是一个轻量级的关系型数据库,运算速度快,占用资源少,很适合在移动设备上使用, 不仅支持标准sql语法,还遵循ACID(数据库事务)原则,无需账号,使用起来非常方便!

sqlite是类似于MysqL和Postgresql的开源关系型数据库。不同于其他数据库的是, sqlite使用单个文件存储数据,使用sqlite库读取数据。

小结下特点:

sqlite通过文件保存数据库,一个文件就是一个数据库数据库中又包含多个表格,表格里又有 多条记录,每个记录由多个字段构成,每个字段有对应的值,每个值我们可以指定类型,也可以不指定 类型(主键除外)

关于sqlite数据库支持存储的数据类型及相关的基本操作语句可以移步到android中的数据库操作或者SQLite在线文档

Android标准库包含sqlite库以及配套的一些Java辅助类。

使用sqlite本地数据库

Step 1 : 定义Schema

我们以上一篇RecyclerView的基本用法为例,将每一个View对象中的内容存入数据库

创建数据库前,首先要清楚存储什么样的数据。 我们要保存的是一条条Info信息
记录,这需要定义如图所示的infos数据表。

sql中一个重要的概念是schema:一种DB结构的正式声明,用于表示database的组成结构。schema是从创建DB的sql语句中生成的。我们会发现创建一个伴随类(companion class)是很有益的,这个类称为合约类(contract class),它用一种系统化并且自动生成文档的方式,显示指定了schema样式。

Contract Clsss是一些常量的容器。它定义了例如URIs表名列名等。这个contract类允许在同一个包下与其他类使用同样的常量。 它让我们只需要在一个地方修改列名,然后这个列名就可以自动传递给整个code

组织contract类的一个好方法是在类的根层级定义一些全局变量,然后为每一个table来创建内部类

首先,我们来创建定义schema的Java类。创建时,新建一个databas,在包下新建类命名为InfoDbSchema,这样,就可以将InfoDbSchema.java文件放入专门的database包中,实现数据库操作相关代码的组织和归类。

在InfoDbSchema类中,再定义一个描述数据表的InfoTable内部类:

public class InfoDbScheme {
    public static final class InfoTable{
        public static final String NAME = "infos";
    }
}

InfoTable内部类唯一的用途就是定义描述数据表元素的String常量。首先要定义的是数据库表名(InfoTable.NAME)

接下来定义数据表字段:

public class InfoDbScheme {
    public static final class InfoTable{
        public static final String NAME = "infos";

        public static final class Col{
            public static final String UUID = "uuid";
            public static final String TITLE = "title";
            public static final String DATE = "date";
        }
    }
}

有了这些数据表元素,就可以在Java代码中安全地引用了。例如, InfoTable.Cols.TITLE就是指Info记录的title字段。此外,这种定义方式还给修改字段名称或新增表元素带来了方便。

step 2 : 使用sql Helper创建初始数据库

定 义 完 数 据 库 schema , 就 可 以 创 建 数 据 库 了 。 openOrCreateDatabase(…) 和databaseList()方法是Android提供的Context底层方法,可以用来打开数据库文件并将其转换为sqliteDatabase实例。

不过,实际开发时,建议总是遵循以下步骤。

  1. 确认目标数据库是否存在。

  2. 如果不存在,首先创建数据库,然后创建数据库表以及必需的初始化数据。

  3. 如果存在,打开并确认InfoDbSchema是否是最新版本。

  4. 如果是旧版本,就运行相关代码升级到最新版本。

    令人高兴的是, Android提供的sqliteOpenHelper类可以帮我们处理这些。在数据库包中创建InfoBaseHelper类(InfoBaseHelper.java):

public class InfoBaseHelper extends sqliteOpenHelper {
private static final int VERSION = 1;
private static final String DATABASE_NAME = "infoBase.db";
public InfoBaseHelper(Context context) {
super(context,DATABASE_NAME,null,VERSION);
}
@Override
public void onCreate(sqliteDatabase db) {
}
@Override
public void onUpgrade(sqliteDatabase db,int oldVersion,int newVersion) {
}
}

有了sqliteOpenHelper类,打开sqliteDatabase的繁杂工作都可以交给它处理。在InfoLab中用它创建infos数据库InfoLab.java):

public class InfoLab {
    private static InfoLab sInfoLab;
    private Context mAppContext;
    private ArrayList<Info> mInfos;
    private sqliteDatabase mDateBase;

    private InfoLab(Context appContext){
        mAppContext = appContext.getApplicationContext();
        mDateBase = new InfoBaseHelper(mAppContext).getWritableDatabase();
        mInfos = new ArrayList<Info>();

         /* for(int i = 0;i<100;i++){ Info info = new Info(); info.setmTtitle("Info #"+i); mInfos.add(info); }*/
    }
    ...
}

这里调用getWritableDatabase()方法时, CrimeBaseHelper要做如下工作。

  1. 打开/data/data/com.example.sqlitetest2/databases/crimeBase.db数据库;如果不存在,就先创建crimeBase.db数据库文件

  2. 如果是首次创建数据库,就调用onCreate(sqliteDatabase)方法,然后保存最新的版本号。

  3. 如果已创建过数据库,首先检查它的版本号。如果InfoOpenHelper中的版本号更高,就调用onUpgrade(sqliteDatabase,int,int)方法升级

最后,再做个总结: onCreate(sqliteDatabase)方法负责创建初始数据库; onUpgrade(sqliteDatabase,int)方法负责与升级相关的工作。

我 们 在onCreate(…)方法创建数据库,这需要导入InfoDbSchema类的InfoTable内部类。(InfoBaseHelper.java

public void onCreate(sqliteDatabase db) {

        db.execsql("create table " + InfoTable.NAME + "(" +
                " _id integer primary key autoincrement," +
                InfoTable.Col.UUID + "," +
                InfoTable.Col.TITLE + "," +
                InfoTable.Col.DATE  +
                ")"
        );
    }

现在我们就在手机本地文件中创建了一个本地数据库数据库名字叫做infoBase.db,在数据库中还创建了一个数据库表,表的名字叫做infos,在表中我们还创建了几个字段,uuid、title还有date。

我们可以在手机目录/data/data/[your package name]下查看(前提是手机要root),你就可以看到下图这样的文件

当然现在info表里我们还没有添加数据。

step 3 : 写入数据库

要使用sqliteDatabase,数据库中首先要有数据。数据库写入操作有:向infos表中插入新记录以及在Info变更时更新原始记录。

我们修改InfoLab类,不用List来存储数据,改用mDateBase来存储数据,首先要删除掉InfoLab类中的ArrayList<Info>代码增加一个添加数据的方法及更新数据的方法,改动完成如下:(InfoLab.java

public class InfoLab {
    private static InfoLab sInfoLab;
    private Context mAppContext;
   // private ArrayList<Info> mInfos;
    private sqliteDatabase mDateBase;

    private InfoLab(Context appContext){
        mAppContext = appContext.getApplicationContext();
        mDateBase = new InfoBaseHelper(mAppContext).getWritableDatabase();
       // mInfos = new ArrayList<Info>();

         /* for(int i = 0;i<100;i++){ Info info = new Info(); info.setmTtitle("Info #"+i); mInfos.add(info); }*/
    }

    public static InfoLab get(Context c){
        if(sInfoLab==null){
            sInfoLab = new InfoLab(c.getApplicationContext());
        }
        return sInfoLab;
    }

    public ArrayList<Info> getInfos(){
      // return mInfos;
        return new ArrayList<>();
    }

    public Info getInfo(UUID uuid){
// for(Info i:mInfos){
// if(i.getmId().equals(uuid)){
// return i;
// }
// }
        return null;
    }

    public void addInfo(Info info){
      // mInfos.add(info);

    }

 public void updateInfo(Info info){

    }
}

在InfoListFragment.java类中增加添加数据的按钮,修改info_list_activity.xml代码如下:

<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=".InfoListActivity">

    <android.support.v7.widget.RecyclerView  xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/info_recycler_view" android:layout_width="match_parent" android:layout_height="wrap_content"/>

    <LinearLayout  android:id="@+id/empty_crime_list" android:layout_width="wrap_content" android:layout_height="123dp" android:orientation="vertical" android:layout_gravity="center">

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:padding="16dp" android:text="没有Info记录可以显示"/>

        <Button  android:id="@+id/add_crime_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:padding="16dp" android:text="@string/new_crime"/>
    </LinearLayout>
</LinearLayout>

修改InfoListActivity.java代码如下:

public class InfoListActivity extends AppCompatActivity {
    ...
    private LinearLayout mLinearLayout;
    private Button addButton;
    ...
     protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.info_list_activity);

        mLinearLayout = (LinearLayout)this.findViewById(R.id.empty_crime_list);
        addButton = (Button)this.findViewById(R.id.add_crime_button);

        mInfoRecyclerView = (RecyclerView)this.findViewById(R.id.info_recycler_view);
        mInfoRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        updateUI();
    } 
    ...
     private void updateUI() {
        InfoLab infoLab = InfoLab.get(this);
        List<Info> infos = infoLab.getInfos();

        if(mAdapter==null){
            mAdapter = new InfoAdapter(infos);
            mInfoRecyclerView.setAdapter(mAdapter);
        }
        else{
            mAdapter.notifyDataSetChanged();
        }
        if(infos.size()>0){
            mLinearLayout.setVisibility(View.GONE);
        }
        else{
            mLinearLayout.setVisibility(View.VISIBLE);
            addButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("hehe","hehe");
                   Info info = new Info();
                    InfoLab.get(InfoListActivity.this).addInfo(info);
                    Intent intent = new Intent(InfoListActivity.this,InfoDetailActivity.class);
                    intent.putExtra(EXTRA_INFO_ID,info.getmId());
                    startActivity(intent);
                }
            });
        }
           mInfoRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
    }   

}

现在我们点击按钮,就会加载InfoDetailActivity.java页面

接下来开始往数据库中写入数据:

使用 ContentValues

负责处理数据库写入和更新操作的辅助类是ContentValues。它是个键值存储类,类似于Java的HashMap和前面用过的Bundle。不同的是, ContentValues只能用于处理sqlite数据。

step 4 : 创建ContentValues( InfoLab.java )

public class InfoLab {
    ...
     public static ContentValues getContentValues(Info info){
        ContentValues values = new ContentValues();
        values.put(InfoTable.Col.UUID,info.getmId().toString());
        values.put(InfoTable.Col.TITLE,info.getmTtitle());
        values.put(InfoTable.Col.DATE,info.getmDate().toString());
        return values;
    }
}

step 5 : 插入和更新记录( InfoLab.java )

public void addInfo(Info info){
      // mInfos.add(info);
        ContentValues values = getContentValues(info);
        mDateBase.insert(InfoTable.NAME,values);
    }

insert(String,String,ContentValues)方法有两个重要的参数,还有一个很少用到。
传入的第一个参数是数据库表名,最后一个是要写入的数据。
第二个参数称为nullColumnHack。它有什么用途呢?
别急,举个例子你就明白了。假设你想调用insert(…)方法,但传入了ContentValues
空值。这时, sqlite不干了, insert(…)方法调用只能以失败告终。
然而,如果能以uuid值作为nullColumnHack传入的话, sqlite就可以忽略ContentValues空值,而且还会自动传入一个带uuid且值为null的ContentValues。结果, insert(…)方法得以成功调用并插入了一条新记录

public void updateInfo(Info info){
        String uuidString = info.getmId().toString();
        ContentValues values = getContentValues(info);
        mDateBase.update(InfoTable.NAME,values,InfoTable.Col.UUID + " = ?",new String[] { uuidString });

    }

update(String,ContentValues,String[])更新方法类似于insert(…)方法,向其传入要更新的数据表名和为表记录准备的ContentValues。然而,与insert(…)方法不同的是,你要确定该更新哪些记录。具体的做法是:创建where子句(第三个参数) ,然后指定where子句中的参数值(String[]数组参数)。
问题来了,为什么不直接在where子句中放入uuidString呢?这可比使用?然后传入String[]简单多了!
事实上,很多时候, String本身会包含sql代码。如果将它直接放入query语句中,这些代码
可能会改变query语句的含义,甚至会修改数据库资料。这实际就是sql脚本注入, 危害相当严重。
使用?的话,就不用关心String包含什么,代码执行的效果肯定就是我们想要的。

step 6 : Info数据刷新( InfoDetailActivity.java )

public class InfoDetailActivity extends AppCompatActivity {
    ...
    public void onCreate(Bundle savedInstanceState) {
    ...
    }

    @Override
    protected void onResume() {
        super.onResume();
        InfoLab.get(this).updateInfo(mInfo);
    }
}

这样,点击按钮,你就可以往里面插入数据了,因为还没有完成会导致闪退,但是数据库中已经成功的添加了一条数据,打开数据库目录可以看到:

step 7 : 读取数据库

读取sqlite数据库中数据需要用到query(…)方法。这个方法有好几个重载版本。我们要用的版本如下:

public Cursor query(
String table,String[] columns,String where,String[] whereArgs,String groupBy,String having,String orderBy,String limit)

参数table是要查询数据表。参数columns指定要依次获取哪些字段的值。参数where和whereArgs的作用与update(…)方法中的一样。
新增一个便利方法调用query(…)方法查询InfoeTable中的记录( InfoLab.java )

private Cursor queryCrimes(String whereClause,String[] whereArgs) {
Cursor cursor = mDatabase.query(
InfoTable.NAME,// Columns - null selects all columns
whereClause,whereArgs,// groupBy
null,// having
null // orderBy
);
return cursor;
}

step 8 : 使用 CursorWrapper

Cursor是个神奇的表数据处理工具,其任务就是封装数据表中的原始字段值。

创建InfoCursorWrapper类(InfoCursorWrapper.java

public class InfoCursorWrapper extends CursorWrapper {
    /** * Creates a cursor wrapper. * * @param cursor The underlying cursor to wrap. */
    public InfoCursorWrapper(Cursor cursor) {
        super(cursor);
    }

    ...
}

新增getCrime()方法InfoCursorWrapper.java

public class InfoCursorWrapper extends CursorWrapper {
    /** * Creates a cursor wrapper. * * @param cursor The underlying cursor to wrap. */
    public InfoCursorWrapper(Cursor cursor) {
        super(cursor);
    }

    public Info getInfo() {
        String uuidString = getString(getColumnIndex(InfoTable.Col.UUID));
        String title = getString(getColumnIndex(InfoTable.Col.TITLE));
        long date = getLong(getColumnIndex(InfoTable.Col.DATE));

        Info info = new Info(UUID.fromString(uuidString));
        info.setmTtitle(title);
        info.setmDate(new Date(date));
        return info;
    }
}

step 9 : 使用cursor封装方法(InfoLab.java)

private InfoCursorWrapper queryInfo(String whereClaues,String[] whereArgs){
        Cursor cursor = mDateBase.query(
                InfoTable.NAME,// Columns - null selects all columns
                whereClaues,// groupBy
                null,// having
                null // orderBy
                 );

        return new InfoCursorWrapper(cursor);
    }

step 10 :返回info列表(InfoLab.java)

public ArrayList<Info> getInfos(){
      // return mInfos;
        //return new ArrayList<>();

        ArrayList<Info> infos = new ArrayList<>();
       InfoCursorWrapper cursor = queryInfo(null,null);
        try {
            cursor.moveToFirst();
            while (!cursor.isAfterLast()) {
                infos.add(cursor.getInfo());
                cursor.moveToNext();
            }
        } finally {
            cursor.close();
        }
        return infos;
    }

要从cursor中取出数据,首先要调用moveToFirst()方法移动cursor指向第一个元素。读取行记录后,再调用moveToNext()方法,读取下一行记录,直到isAfterLast()告诉我们没有数据可取为止。

最后,别忘了调用Cursor的close()方法关闭它。

step11 :重写getInfo(UUID)方法(InfoLab.java)

public Info getInfo(UUID uuid){
// for(Info i:mInfos){
// if(i.getmId().equals(uuid)){
// return i;
// }
// }
       // return null;
        InfoCursorWrapper cursor = queryInfo(
                InfoTable.Col.UUID + " = ?",new String[] { uuid.toString() }
        );
        try {
            if (cursor.getCount() == 0) {
                return null;
            }
            cursor.moveToFirst();
            return cursor.getInfo();
        } finally {
            cursor.close();
        }
    }

上述代码的作用如下。

现在可以插入info记录了。也就是说,点击New Crime菜单项,实现将info添加InfoLab代码可以正常工作了。

数据库查询没有问题了。 InfoDetailActivity现在能够看见InfoLab中的所有Info了。

InfoLab.getInfo(UUID) 方 法 也 能 正 常 工 作 了 。 InfoDetailActivity 终于可以显示真正的Info对象了。

step 12 : 刷新模型层数据

添加setInfos(List<Info>)方法InfoListActivity.java):

private class InfoAdapter extends RecyclerView.Adapter<InfoHolder> {
...

@Override
public int getItemCount() {
    return mInfos.size();
}

 public void setInfos(List<Info> infos){
            mInfos = infos;
        }

}

然后在updateUI()方法调用setInfos(List<Info> infos)方法(InfoListActivity.java)

private void updateUI() {
        InfoLab infoLab = InfoLab.get(this);
        List<Info> infos = infoLab.getInfos();

        if(mAdapter==null){
            mAdapter = new InfoAdapter(infos);
            mInfoRecyclerView.setAdapter(mAdapter);
        }
        else{
            mAdapter.setInfos(infos);
            mAdapter.notifyDataSetChanged();
        }
       ...
    }

现在,可以验证我们的成果了。运行应用,新增一项info记录,然后按回退键,
确认InfoListActivity中会出现刚才新增的记录。数据库中也添加了info记录。

源码在这里

猜你在找的Sqlite相关文章