SQLite

在客户端编程中经常使用轻量级数据库SQLite,其优点是体积小,使用文件存储,十分轻便,缺点是功能较弱,性能较差,但客户端上并没有性能要求,因此SQLite十分合适。

Android编程中,是没有JDBC的,数据的持久化API是Android框架集成SQLite实现的,因此我们直接就能使用SQLite,但在不添加额外的Java库时,也只能使用它。Android框架提供的持久化API和JDBC实际上比较像,里面有很多相同的概念,比如:用占位符组织SQL语句,查询结果集的游标等概念。

Android中使用SQLite

  1. 继承SQLiteOpenHelper类,填充其中的回调函数
  2. 初始化自己的SQLiteOpenHelper,获取SQLiteDatabase对象,执行增删改查

初始化数据库

MyOpenHelper.java

public class MyOpenHelper extends SQLiteOpenHelper
{

    public MyOpenHelper(Context context)
    {
        //context 上下文对象 name 数据库名 factory 传null即可 version 版本号
        super(context, "test.db", null, 1);
    }

    /**
     * 数据库创建时执行
     * @param db 数据库对象
     */
    @Override
    public void onCreate(SQLiteDatabase db)
    {
        String createDatabase =
                "create table t_user(" +
                "_id integer primary key autoincrement," +
                "username varchar(20)," +
                "birthday integer" +
                ");";
        db.execSQL(createDatabase);
    }

    /**
     * 数据库版本号发生改变时回调
     * @param db 数据库对象
     * @param oldVersion 旧版本号
     * @param newVersion 新版本号
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
    {
        //执行更新使用的数据库版本号
        db.execSQL("alter table t_user add phone varchar(11)");
    }
}

数据库版本号的概念:应用版本升级,如果旧版本数据库已经存在,而新版本应用有更高的数据库版本号,就执行onUpdate(),如果是全新安装,以前的数据库还不存在,就执行onCreate()。

执行数据库增删改查操作

UserDao.java

public class UserDao
{
    private SQLiteOpenHelper sqLiteOpenHelper = null;

    public UserDao(Context context)
    {
        this.sqLiteOpenHelper = new MyOpenHelper(context);
    }

    /**
     * 按名字查询用户
     * @param username 用户名
     * @return 包含所有相同名字用户的列表
     */
    public List<User> queryFromName(String username)
    {
        List<User> userList = new ArrayList<>();

        SQLiteDatabase db = sqLiteOpenHelper.getReadableDatabase();
        String queryString = "select _id, birthday from t_user where username=?;";

        //rawQuery() 更灵活更适合查询 query() 不灵活
        //sql 包含占位符的SQL语句 selectionArgs 参数数组
        Cursor cursor = db.rawQuery(queryString, new String[]{username});

        if(cursor != null && cursor.getCount() > 0)
        {
            while(cursor.moveToNext())
            {
                int id = cursor.getInt(0);
                long birthdayInt = cursor.getLong(1);
                Date birthday = new Date(birthdayInt);
                userList.add(new User(id, username, birthday));
            }
            cursor.close();
        }
        db.close();
        return userList;
    }

    /**
     * 添加用户
     * @param user 用户对象
     * @return 添加成功返回True
     */
    public boolean add(User user)
    {
        SQLiteDatabase db = sqLiteOpenHelper.getWritableDatabase();

        //ContentValues对象包含插入的参数
        ContentValues contentValues = new ContentValues();
        contentValues.put("username", user.getUsername());
        contentValues.put("birthday", user.getBirthday().getTime());

        //插入操作 table 表名 nullColumnHack 返回null时的默认值,传null即可 values 包含参数的ContentValues对象
        long result = db.insert("t_user", null, contentValues);
        db.close();

        boolean success = true;
        if(result == -1)
        {
            success = false;
        }
        return success;
    }

    /**
     * 按Id删除用户
     * @param id 用户id
     * @return 成功删除多少行
     */
    public int delete(int id)
    {
        SQLiteDatabase db = sqLiteOpenHelper.getWritableDatabase();

        String idStr = Integer.toString(id);
        //table 表名 whereClause where条件 whereArgs 条件参数,必须是String类型
        int result = db.delete("t_user", "_id=?", new String[]{idStr});

        db.close();
        return result;
    }

    /**
     * 根据对象中主键更新用户信息
     * @param user 用户对象
     * @return 操作影响多少行
     */
    public int update(User user)
    {
        SQLiteDatabase db = sqLiteOpenHelper.getWritableDatabase();

        ContentValues contentValues = new ContentValues();
        contentValues.put("username", user.getUsername());
        contentValues.put("birthday", user.getBirthday().getTime());
        String idStr = Integer.toString(user.getId());
        //table 表名 values 修改的参数值 whereClause where条件 whereArgs 条件参数,必须是String类型
        int result = db.update("t_user", contentValues, "_id=?", new String[]{idStr});
        db.close();
        return result;
    }
}

上述代码展示了增删改查的最佳实践例子,查询建议使用rawQuery(),能够自己组织SQL语句,更加灵活,增删改建议使用定安卓制化的API,不容易出错。

上述代码还展示了日期的存取,实际上SQLite中使用integer类型保存日期,在DAO层进行 Java的Date类型转换。这里补充一点SQLite中Integer的定义:

INTEGER. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.

SQLite中的integer会自动根据存储数据的大小进行调节,最大能达到8字节,Java中long类型就是8字节的,所有完全可以用integer类型来存储Java的long类型数据。但是千万要注意,取出数据时(尤其是存储的时间),不要错写成cursor.getInt(),否则就会发生溢出,产生奇怪的结果。

数据库事务

//开启一个数据库事务
db.beginTransaction();
try
{
    db.execSQL("update account set money= money-200 where name=?",new String[]{"李四"});
    db.execSQL("update account set money= money+200 where name=?",new String[]{"张三"});
    //标记事务中的sql语句全部成功执行
    db.setTransactionSuccessful();
}
finally
{
    //判断事务的标记是否成功,如果不成功,回滚错误之前执行的sql语句
    db.endTransaction();
}

2行调用beginTransaction()开启事务,如果执行成功就调用setTransactionSuccessful()标记成功,最终无论成功与否都要调用endTransaction()结束事物,事物内部实现会判断是否回滚。

Android中SQLite的调试

  • 最直接的方式是进入adb shell,切换到/data/data/<应用包名>/databases/,执行sqlite3 xxx.db
  • 如果数据库较为复杂,shell观察起来比较麻烦,那就只能用ddms或者adb pull把数据库文件下载下来,用数据库管理工具查看。
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。