Android中使用SQLite数据库的基础代码结构

来源:互联网 发布:dns 协议端口号为53 编辑:程序博客网 时间:2024/05/22 10:40

Android中使用SQLite数据库的基础代码结构


Android中默认支持SQLite数据库操作,但是一般使用的时候,把所有的逻辑放在SQLiteOpenHelper实现类中,这样做第一会增加代码文件的代码量,而且会把数据库的结构的管理代码和数据库与外部的交互代码以及交互实例的管理混合在一起,不易于管理。同时,还经常出现使用已经关闭的数据库异常。可能对于多线程访问的处理还不好。下面介绍的结构是之前看别人的推荐方案总结的,其实主体结构还是别人的。

一、常量管理

使用数据库的时候会使用到很多的字符串常量,包括数据库名称、表名称、列名称以及某些列的一些用到的一些常量数据。如果使用手写的方式,不仅查看不方便,而且很可能因为书写手误出现错误。比如今天写新建表的语句的时候把primary key写错,运行的时候报了异常。因此推荐使用一个常量类来保存这些常量。比如我经常使用Contants类保存我的这些常量,如下所示:

// 跑步类型public static final int MODE_RUN_FIX = 1;// 定点路线跑public static final int MODE_RUN_CUSTOM = 2;// 自由路线跑// 数据库中使用的常量public static final String DB_NAME = "running.db";// 数据库名称public static final int DB_VERSION = 1;// 数据库版本// 数据库表名称public static final String DB_TABLE_NAME_USER = "user";// 用户信息存储表public static final String DB_TABLE_NAME_ROUTE = "route";// 用户路线路线存储表public static final String DB_TABLE_NAME_FIXED_ROUTE = "fixed_route";// 定向跑路线信息存储表public static final String DB_TABLE_NAME_POINT = "point";// 系统定点数据存储表// route表列名称public static final String COLUMN_ROUTE_TYPE = "type";// 跑步路线类型public static final String COLUMN_ROUTE_ID = "_id";// id编号public static final String COLUMN_ROUTE_DISTANCE = "distance";// 路线长度public static final String COLUMN_ROUTE_START_TIME = "start_time";// 路线开始时间public static final String COLUMN_ROUTE_END_TIME = "end_time";// 路线结束时间public static final String COLUMN_ROUTE_CALORIE = "calorie";// 路线过程中卡路里消耗public static final String COLUMN_ROUTE_AVERAGE_SPEED = "average_speed";// 跑步过程中的平均速度public static final String COLUMN_ROUTE_MAX_SPEED = "max_speed";// 跑步过程中的最大速度public static final String COLUMN_ROUTE_IS_SUBMIT = "is_submit";// 路线是否提交到服务器public static final int COLUMN_ROUTE_SUBMITTED = 100;// 整形值代表路线已经提交到服务器public static final int COLUMN_ROUTE_NOT_SUBMIT = 200;// 整形值代表路线未提交到服务器
二、SQLiteOpenHelper实现类

当然使用数据库的第一个要做的事情就是建立使用的数据库,建立所使用的表。而这个工作就是由SQLiteOpenHelper类来实现的,具体说是其实现子类,这个需要自己实现。Android中管理SQLite的策略是:先判断某个数据库是否已经存在,如果已经存在则不做任何处理,如果不存在,在执行

<code>public void onCreate(SQLiteDatabase db){}

中的代码,进行数据库以及相关表的建立工作。也就是说这个方法并不是每次使用应用都会调用。

然后另一个重要的方法是:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}

这个方法用于更新数据库的结构。当然这个方法也不是每次都调用。每次使用SQLiteOpenHelper的时候,都会判断当前的数据库版本(这个会在后面说道),如果传入的版本号大于之前保存的旧版本号则会执行这个方法。因此,这个方法主要用于当应用的结构发生变化的时候通过改进数据库版本号来进行相关操作。但是很有可能出现,某个手机装的应用的数据库版本号为1,但是更新的时候需要更新到10,当中若干个数据库版本,常见的做法是增量更新:

if(oldVersion==1){    //update to version 2    oldVersion++;}if(oldVersion==2){    //update to version 3    oldVersion++;}.....if(oldVersion==9){    //update to version 10    oldVersion++;}

对于SQLiteOpenHelper实现类不许要有一个构造方法,我常用的一个构造方法是:

public DBOpenHelper(Context context) {    super(context, Constants.DB_NAME, null, Constants.DB_VERSION);}

其中Context使用上下文环境,使用任何一个Activity实例即可。参数说明如下:

  • Constants.DB_NAME:数据名称,字符串记为要有.db后缀。
  • null位置要放入CursorFactory实例,这个没用过,一般都是使用null
  • Constants.DB_VERSION:数据库版本号。这个建议放到Constants常量类中保存。
三、数据库实例管理类

由于可能会在多个地方或者一个地方多次使用数据库。因此管理使用的数据库使用实例很重要。因为使用用数据库之后要关闭,但是当在一个地方多次使用数据库实例的时候经常出现因为使用不当,出现使用已经关闭的数据库的异常。下面介绍的管理代码,不仅仅可以解决这个问题也可以解决多线程访问的问题。

package cn.letsbook.running.database;import java.util.concurrent.atomic.AtomicInteger;import android.database.sqlite.SQLiteDatabase;import cn.letsbook.running.model.exception.NotInitException;/** * 数据库管理类,用于管理数据的初始化、数据库的打开以及数据库的关闭 *  * @author Jywang * @time 2014-9-19 18:20 * @email jywangkeep@163.com *  */public class DBManager {    // 原子级计数,用于并发操作,可以避免同时多个请求的时候普通int数据的计数可能出错    private AtomicInteger openCounter = new AtomicInteger();    private static DBManager instance;    private static DBOpenHelper helper;    private SQLiteDatabase db;    /**     * 初始化DBManager的instance实例     *      * @param _helper     *            数据库打开自定义帮助实例(DBOpenHelper)     */    public static synchronized void initInstance(DBOpenHelper _helper) {        if (instance == null) {            instance = new DBManager();            helper = _helper;        }    }    /**     * 获取instance实例     *      * @return DBManager的instance实例     * @throws NotInitException     *             当instance未初始化的时候抛出该异常     */    public static synchronized DBManager getInstance() throws NotInitException {        if (instance == null)            throw new NotInitException();        return instance;    }    /**     * 打开数据库,只有当数据库连接数为0的时候才会打开数据库,否则使用之前已经打开的实例     *      * @return 数据库连接实例     */    public synchronized SQLiteDatabase openDatabase() {        if (openCounter.incrementAndGet() == 1)            db = helper.getWritableDatabase();        return db;    }    /**     * 关闭数据库,只有连接数只有一个的时候才会真正的关闭数据库     */    public synchronized void closeDatabase() {        if (openCounter.decrementAndGet() == 0)            db.close();    }}

从上面的代码可以看出,使用的是单例模式,如果使用的时候已经有打开的数据库实例则返回这个实例,如果没有则新建数据库实例。但是在操作的时候会使用一个变量来统计有多少次数据库实例的请求访问。如果在某个地方的访问结束后会调用closeDatabase()方法关闭数据库实例,但是因为只有一个数据库实例,所以只有当所有的请求数据库实例的地方都调用了关闭数据库实例的方法的时候才关闭这个实例。这个会避免使用已经关闭的数据库实例的异常的出现。

同时计数的变量不是使用int类型,因为这种类型在多线程访问的时候会出现竞争问题,导致结果不正确,很容易导致使用已经关闭数据库实例的问题,因此采用了AtomicInteger来进行统计,这个是原子级操作,可以避免这个问题。这个也是Java并发处理中推荐使用的类型。

四、数据库逻辑操作类

对于数据库的增删改差的操作放在数据库操作类中,比如我实现的DBOperator类,里面包含了对路线的插入和查询:

package cn.letsbook.running.database;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.TreeMap;import org.json.JSONException;import android.content.ContentValues;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import cn.letsbook.running.model.FixedRoute;import cn.letsbook.running.model.Route;import cn.letsbook.running.model.exception.InitException;import cn.letsbook.running.util.Constants;/** * 数据库操作类,用于增、删、改、查的数据库操作 *  * @author Jywang * @time 2014-9-19 18:18 * @email jywangkeep@163.com *  */public class DBOperator {    /**     * 添加定向跑路线到数据库中     *      * @param db     *            数据库操作实例     * @param fixedRoute     *            路线实例     * @return 插入结果     *         <ul>     *         <li>true:插入成功</li>     *         <li>false:插入失败</li>     *         </ul>     * @throws JSONException     */    public static synchronized boolean addFixedRoute(SQLiteDatabase db,            FixedRoute fixedRoute) throws JSONException {        if (fixedRoute == null)            return false;        ContentValues values = new ContentValues();        values.put(Constants.COLUMN_FIXED_ROUTE_ID, fixedRoute.getId());        values.put(Constants.COLUMN_FIXED_ROUTE_TYPE, fixedRoute.getType());        values.put(Constants.COLUMN_FIXED_ROUTE_TURN_NUMBER,                fixedRoute.getType());        values.put(Constants.COLUMN_FIXED_ROUTE_SELECT_POINTS,                fixedRoute.getSelectPointsString());        values.put(Constants.COLUMN_FIXED_ROUTE_PASSED_POINTS,                fixedRoute.getPassPointsString());        long rowId = db.insert(Constants.DB_TABLE_NAME_FIXED_ROUTE, null,                values);        if (rowId < 0)            return false;        return true;    }    /**     * 添加路线到数据库中     *      * @param db     *            数据库操作实例     * @param route     *            路线实例     * @return 插入结果     *         <ul>     *         <li>true:插入成功</li>     *         <li>false:插入失败</li>     *         </ul>     */    public static synchronized boolean addRoute(SQLiteDatabase db, Route route) {        if (route == null)            return false;        ContentValues values = new ContentValues();        values.put(Constants.COLUMN_ROUTE_ID, route.getId());        values.put(Constants.COLUMN_ROUTE_TYPE, route.getType());        values.put(Constants.COLUMN_ROUTE_CALORIE, route.getCalorie());        values.put(Constants.COLUMN_ROUTE_DISTANCE, route.getDistance());        values.put(Constants.COLUMN_ROUTE_AVERAGE_SPEED,                route.getAverageSpeed());        values.put(Constants.COLUMN_ROUTE_MAX_SPEED, route.getMaxSpeed());        values.put(Constants.COLUMN_ROUTE_START_TIME, route.getStartTime());        values.put(Constants.COLUMN_ROUTE_END_TIME, route.getEndTime());        // 由于SQLite不支持boolean类型的数据因此使用两个不同的整形值来代替boolean的不同结果.        // 其中Constants.COLUMN_ROUTE_SUBMITTED常量代表路线信息已经提交到服务器,        // Constants.COLUMN_ROUTE_NOT_SUBMIT代表路线信息未提交到服务器.        values.put(Constants.COLUMN_ROUTE_IS_SUBMIT,                route.getIsSubmit() ? Constants.COLUMN_ROUTE_SUBMITTED                        : Constants.COLUMN_ROUTE_NOT_SUBMIT);        long rowId = db.insert(Constants.DB_TABLE_NAME_ROUTE, null, values);        if (rowId < 0)            return false;        else            return true;    }    /**     * 获取定向跑路线数据     *      * @param db     *            数据库操作实例     * @return 获取的定向跑路线数据集合     * @throws InitException     *             FixedRoute实例初始化错误的时候抛出该异常     * @throws JSONException     *             FixedRoute中对JSON字符串和Point进行转换出现错误的时候抛出该异常。     */    public static synchronized Map<Integer, FixedRoute> getFixedRoutes(            SQLiteDatabase db) throws InitException, JSONException {        Cursor cursor = db.rawQuery("SELECT " + Constants.COLUMN_FIXED_ROUTE_ID                + "," + Constants.COLUMN_FIXED_ROUTE_TYPE + ","                + Constants.COLUMN_FIXED_ROUTE_TURN_NUMBER + ","                + Constants.COLUMN_FIXED_ROUTE_PASSED_POINTS + ","                + Constants.COLUMN_FIXED_ROUTE_SELECT_POINTS + " FROM "                + Constants.DB_TABLE_NAME_FIXED_ROUTE, null);        if (cursor == null)            return null;        // 使用Map集合的原因是为了便于查找固定id值的FixedRoute实例。        Map<Integer, FixedRoute> fixedRoutesMap = new TreeMap<Integer, FixedRoute>();        while (cursor.moveToNext()) {            FixedRoute fixedRoute = new FixedRoute(                    cursor.getInt(cursor                            .getColumnIndex(Constants.COLUMN_FIXED_ROUTE_ID)),                    cursor.getInt(cursor                            .getColumnIndex(Constants.COLUMN_FIXED_ROUTE_TYPE)),                    cursor.getString(cursor                            .getColumnIndex(Constants.COLUMN_FIXED_ROUTE_SELECT_POINTS)),                    cursor.getString(cursor                            .getColumnIndex(Constants.COLUMN_FIXED_ROUTE_PASSED_POINTS)),                    cursor.getInt(cursor                            .getColumnIndex(Constants.COLUMN_FIXED_ROUTE_TURN_NUMBER)));            fixedRoutesMap.put(fixedRoute.getId(), fixedRoute);        }        cursor.close();        return fixedRoutesMap;    }    /**     * 获取所有的路线信息列表     *      * @param db     *            数据库操作实例     * @return 获取的路线信息列表     */    public static synchronized List<Route> getRoutes(SQLiteDatabase db) {        Cursor cursor = db.rawQuery("SELECT " + Constants.COLUMN_ROUTE_ID + ","                + Constants.COLUMN_ROUTE_TYPE + ","                + Constants.COLUMN_ROUTE_CALORIE + ","                + Constants.COLUMN_ROUTE_DISTANCE + ","                + Constants.COLUMN_ROUTE_AVERAGE_SPEED + ","                + Constants.COLUMN_ROUTE_MAX_SPEED + ","                + Constants.COLUMN_ROUTE_START_TIME + ","                + Constants.COLUMN_ROUTE_END_TIME + ","                + Constants.COLUMN_ROUTE_IS_SUBMIT + " FROM "                + Constants.DB_TABLE_NAME_ROUTE, null);        if (cursor == null)            return null;        List<Route> routes = new ArrayList<Route>();        while (cursor.moveToNext()) {            Route route = new Route(                    cursor.getInt(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_ID)),                    cursor.getInt(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_TYPE)),                    cursor.getLong(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_START_TIME)),                    cursor.getLong(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_END_TIME)),                    cursor.getFloat(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_CALORIE)),                    cursor.getFloat(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_AVERAGE_SPEED)),                    cursor.getFloat(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_MAX_SPEED)),                    cursor.getFloat(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_DISTANCE)),                    Constants.COLUMN_ROUTE_NOT_SUBMIT == cursor.getInt(cursor                            .getColumnIndex(Constants.COLUMN_ROUTE_IS_SUBMIT)) ? false                            : true);            routes.add(route);        }        cursor.close();        return routes;    }    /**     * 整合定向跑路线数据和公有路线数据。由于查询出来的公有路线数据不包含定向跑路线数据,因此此方法为了整合得到完整的路线数据。     *      * @param db     *            数据库操作实例     * @param routes     *            查询得到的公有路线数据     * @return 整合之后的路线数据     * @throws InitException     *             FixedRoute初始化出现错误的时候会抛出该异常     * @throws JSONException     *             FixedRoute中对JSON字符串和Point之间的转换出现错误的时候抛出该异常     */    public static synchronized List<Route> integrateFixedRouteToRoute(            SQLiteDatabase db, final List<Route> routes) throws InitException,            JSONException {        Map<Integer, FixedRoute> fixedRoutesMap = getFixedRoutes(db);        List<Route> integratedRoutes = new ArrayList<Route>();        for (int index = 0; index < routes.size(); index++) {            Route route = routes.get(index);            if (Constants.MODE_RUN_FIX == route.getType()) {                FixedRoute fixedRoute = fixedRoutesMap.get(route.getId());                route.setFixedRoute(fixedRoute);            }            integratedRoutes.add(route);        }        fixedRoutesMap.clear();        return integratedRoutes;    }}

其实这个类相当于一个工具类,因此方法我都设为static直接使用DBOperator.addRoute(db,route)这种方式就可以访问。

第二个要注意的是,我在每一个方法的前面都加入了synchronized关键字修饰,这个就相当于给每个方法都加了锁,这样在多线程访问的时候会一定程度地避免竞争问题。

五、使用方法

有了上面的各个类之后,可以如下使用:

    DBOpenHelper helper = new DBOpenHelper(this);    //一定要初始化    DBManager.initInstance(helper);    DBManager instance = DBManager.getInstance();    SQLiteDatabase db = instance.openDatabase();    //Do something you like    instance.closeDatabase();
0 0
原创粉丝点击