Android中数据库SQLite用法解析

SQLite使用可参考:https://www.runoob.com/sqlite/sqlite-tutorial.html

1、SQLite基本使用

1.1、SQLiteOpenHelper简介

​ SQLiteOpenHelper是一个抽象类,用于辅助SQLite数据库操作,必须实现构造方法、onCreate和onUpgrade。

(1)构造方法:

public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
        @Nullable CursorFactory factory, int version)

public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
        @Nullable CursorFactory factory, int version,
        @Nullable DatabaseErrorHandler errorHandler)

public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
        @NonNull SQLiteDatabase.OpenParams openParams)
        
public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,
        @Nullable CursorFactory factory, int version,
        int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler)

private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,
        int minimumSupportedVersion,
        @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder)

​ 其中context可以传入Application的context, name为对应的数据库文件名(SQLite数据持久化需要,如果传入null, 则会创建一个内存数据库,应用结束数据会丢失), version为数据库版本,minimumSupportedVersion为数据库支持的最低版本,已存在的数据库版本低于这个版本时数据库将被删除,重新创建数据库,openParamsBuilder为数据库打开参数类SQLiteDatabase.OpenParams的builder, 可配置数据库损坏监听errorHandler和用于查询时生成Cursor的cursorFactory。

(2)onCreate(SQLiteDatabase db)方法

​ onCreate方法中一般通过SQLiteDatabase类型的db,执行execSQL(String sql)来实现表的创建。

(3)onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)方法

​ oldVersion为已存在的数据库版本,newVersion为新创建的数据库版本(构造方法中传入的version),需要大于oldVersion。该方法用于数据库升级,可以添加列、修改表名等。

(4)onConfigure(SQLiteDatabase db)、onOpen(SQLiteDatabase db)等

​ onConfigure在onCreate之前调用,可以启用写前日志记录或外键支持,onOpen重写以执行数据库开启后的操作。

​ 当已存在的数据库版本oldVerson等于0时(表示数据库不存在),会调用onCreate方法,创建相应的表;如果oldVersion小于newVersion,会调用onUpgrade方法,执行数据库升级相关操作,如果oldVersion大于newVersion,会调用onDowngrade方法(需要重写onDowngrade方法),执行数据库降级操作。

1.2、数据库增、删、改、查

(1)创建SQLiteOpenHelper,用于操作指定数据库中的数据

​ 新建UserSQLiteHelper类,继承自SQLiteOpenHelper,实现onCreate方法和onUpgrade方法,在onCreate方法中新建表,在onUpgrade中升级表。

public class UserSQLiteHelper extends SQLiteOpenHelper {

    private static final int CURRENT_VERSION = 1;

    private static final String CREATE_USER_TABLE = "create table user(id integer primary key autoincrement, " +
            "name char(50)" +
            ", age int default 18" +
            ")";

    private static final String DROP_USER_TABLE = "drop table if exists user";

    private UserSQLiteHelper(int version) {
        super(BaseApplication.getInstance(), "user.db", null, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_USER_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //这里简单的删除原来的表,再新建表
        db.execSQL(DROP_USER_TABLE);
        onCreate(db);
    }
    
    private static volatile UserSQLiteHelper instance;

    public static UserSQLiteHelper getInstance() {
        if (instance == null) {
            synchronized (UserSQLiteHelper.class) {
                if (instance == null) {
                    instance = new UserSQLiteHelper(CURRENT_VERSION);
                }
            }
        }
        return instance;
    }
}

​ 获取操作数据库的SQLiteDatabase

String tableName = "user";
UserSQLiteHelper helper = UserSQLiteHelper.getInstance();
SQLiteDatabase writableDatabase = helper.getWritableDatabase();

​ 查询数据库中所有的记录,用于检查增、删、改、查结果

private void queryAll(String tag, SQLiteDatabase database) {
    String table = "user";
    Cursor cursor = database.query(table, new String[]{"name", "id"},
            null, null, null, null, null);
    if (cursor == null) {
        Log.d("Test" + tag, "查询全部:查询得到的cursor为空");
    } else if (cursor.getCount() == 0) {
        Log.d("Test" + tag, "查询全部:未查询到指定的数据");
        cursor.close();
    } else {
        while (cursor.moveToNext()) {
            String name = cursor.getString(cursor.getColumnIndex("name"));
            int id = cursor.getInt(cursor.getColumnIndex("id"));
            Log.d("Test" + tag, "查询全部:User[name = " + name + ", id = " + id + "]");
        }
        cursor.close();
    }
}

(2)新增数据

//增加数据
ContentValues insert = new ContentValues();
insert.put("name", "张三");
writableDatabase.insert(tableName, "name", insert);

queryAll("增加后", writableDatabase);

​ Logcat中输出为:

Test增加后: 查询全部:User[name = 张三, id = 3]

(3)查询数据

//查询数据
Cursor cursor = writableDatabase.query(tableName, new String[]{"name", "id"}, "name = ?", new String[]{"张三"},
        null, null, null);
if (cursor == null) {
    Log.d("Test", "查询得到的cursor为空");
} else if (cursor.getCount() == 0) {
    Log.d("Test", "未查询到指定的数据");
    cursor.close();
} else {
    while (cursor.moveToNext()) {
        String name = cursor.getString(cursor.getColumnIndex("name"));
        int id = cursor.getInt(cursor.getColumnIndex("id"));
        Log.d("Test", "User[name = " + name + ", id = " + id + "]");
    }
    cursor.close();
}
queryAll("查询后", writableDatabase);

​ Logcat中输出为:

Test: User[name = 张三, id = 3]
Test查询后: 查询全部:User[name = 张三, id = 3]

(4)更新数据

//更新数据
ContentValues update = new ContentValues();
update.put("name", "李四");
// 相当于update user set name = "李四" where name = "张三"
writableDatabase.update(tableName, update, "name = ?", new String[]{"张三"}); 
queryAll("更新后", writableDatabase);

​ Logcat中输出为:

Test更新后: 查询全部:User[name = 李四, id = 3]

(5)删除数据

//删除
writableDatabase.delete(tableName, "name = ?", new String[]{"李四"});
queryAll("删除后", writableDatabase);

​ Logcat中输出为:

Test删除后: 查询全部:未查询到指定的数据

2、SQLiteDatabase增、删、改、查参数分析

2.1、增

​ 用于插入数据的方法有三个,分别是

public long insert(String table, String nullColumnHack, ContentValues values)

public long insertOrThrow(String table, String nullColumnHack, ContentValues values)

public long insertWithOnConflict(String table, String nullColumnHack,
            ContentValues initialValues, int conflictAlgorithm)

​ insertWithOnConflict使用conflictAlgorithm参数,指定列存在约束并且冲突时的处理,包括回滚、终止、替换等。insert方法和insertWithOrThrow都调用了insertWithOnConflict,指定了CONFLICT_NONE算法,即不做任何操作。

​ insertWithOnconflict方法如下:

public long insertWithOnConflict(String table, String nullColumnHack,
        ContentValues initialValues, int conflictAlgorithm) {
    acquireReference();
    try {
        StringBuilder sql = new StringBuilder();
        sql.append("INSERT");
        sql.append(CONFLICT_VALUES[conflictAlgorithm]);
        sql.append(" INTO ");
        sql.append(table);
        sql.append('(');

        Object[] bindArgs = null;
        int size = (initialValues != null && !initialValues.isEmpty())
                ? initialValues.size() : 0;
        if (size > 0) {
            bindArgs = new Object[size];
            int i = 0;
            for (String colName : initialValues.keySet()) {
                sql.append((i > 0) ? "," : "");
                sql.append(colName);
                bindArgs[i++] = initialValues.get(colName);
            }
            sql.append(')');
            sql.append(" VALUES (");
            for (i = 0; i < size; i++) {
                sql.append((i > 0) ? ",?" : "?");
            }
        } else {
            sql.append(nullColumnHack + ") VALUES (NULL");
        }
        sql.append(')');

        SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
        try {
            return statement.executeInsert();
        } finally {
            statement.close();
        }
    } finally {
        releaseReference();
    }
}

​ 以上可以看出,该方法就是根据各参数,生成sql语句,然后执行插入操作,其中sql语句如下: insert 参数1 into 参数2 ( 参数3 ) , 其中

参数1为插入数据存在冲突时使用的算法,为String[] CONFLICT_VALUES = new String[] {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}中的一个,

参数2为要插入数据对应的表, 上面示例中为user,

参数3为具体的数据, 假设initialValues中有name和age两个字段(initialValues为空的情况,会插入一条数据,nullColumnHack指定的列值为null),分别为张三、30, 则参数3 为 name, age) values(?, ? , 对应的bindArgs为{"张三", 30}。

​ 于是要执行的sql语句为insert or replace into user ( name, age) values(?, ? ),对应的参数bindArgs为{"张三", 30},传入SQLiteStatement, 调用executeInsert()方法以执行对应的插入语句。。

2.2、删

​ 用于删除的方法有一个:

public int delete(String table, String whereClause, String[] whereArgs) {
        acquireReference();
        try {
            SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
                    (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
            try {
                return statement.executeUpdateDelete();
            } finally {
                statement.close();
            }
        } finally {
            releaseReference();
        }
    }

​ 方法有三个参数table、whereClause、whereArgs,其中table表示要操作的表名, whereClause格式类似于 id = ? and name = ?,数组whereArgs提供占位符?对应的参数,传入SQLiteStatement, 调用executeUpdateDelete以执行delete from table where id = ? and name = ?语句,对应的参数为whereArgs。如果whereClause为空字符串或null, 则会执行delete from table,会将表中所有的数据删除。

2.3、改

​ 用于更新的方法有两个:

public int update(String table, ContentValues values, String whereClause, String[] whereArgs)

public int updateWithOnConflict(String table, ContentValues values,
        String whereClause, String[] whereArgs, int conflictAlgorithm) 

​ 其中update(String, ContentValues, String, String[])方法调用了updateWithOnConflict(String, ContentValues, String, String[], int), 最后一个参数指定数据冲突时的算法,和insert相关的方法中相同。

​ updateWithOnConflict(String, ContentValues, String, String[], int)方法如下:

public int updateWithOnConflict(String table, ContentValues values,
            String whereClause, String[] whereArgs, int conflictAlgorithm) {
        if (values == null || values.isEmpty()) {
            throw new IllegalArgumentException("Empty values");
        }

        acquireReference();
        try {
            StringBuilder sql = new StringBuilder(120);
            sql.append("UPDATE ");
            sql.append(CONFLICT_VALUES[conflictAlgorithm]);
            sql.append(table);
            sql.append(" SET ");

            // move all bind args to one array
            int setValuesSize = values.size();
            int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
            Object[] bindArgs = new Object[bindArgsSize];
            int i = 0;
            for (String colName : values.keySet()) {
                sql.append((i > 0) ? "," : "");
                sql.append(colName);
                bindArgs[i++] = values.get(colName);
                sql.append("=?");
            }
            if (whereArgs != null) {
                for (i = setValuesSize; i < bindArgsSize; i++) {
                    bindArgs[i] = whereArgs[i - setValuesSize];
                }
            }
            if (!TextUtils.isEmpty(whereClause)) {
                sql.append(" WHERE ");
                sql.append(whereClause);
            }

            SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
            try {
                return statement.executeUpdateDelete();
            } finally {
                statement.close();
            }
        } finally {
            releaseReference();
        }
    }

​ 和insertWithOnConflict类似, 该方法也是将参数拼接成sql语句:update 参数1 参数2 set 参数3 where 参数4, 其中:

参数1为更新数据存在冲突时使用的算法,为String[] CONFLICT_VALUES = new String[] {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}中的一个,

参数2为要更新数据对应的表, 上面示例中为user,

参数3为具体需要更新字段拼接的字符串,假设ContentValues中有name和age两个字段,分别为张三、30, 则参数3 为 name=? , age = ? , 对应的bindArgs为{"张三", 30}。

参数4为对应的查询条件,对应String whereClause参数,类似于**id = 20 and name =李四 **等查询条件, 以指定具体需要更新的数据。

​ 于是,具体的sql语句为:update or replace user set name=?, age = ? where id = 20 and name = 李四, 对应的参数bindArgs为{"张三", 30}, 传入SQLiteStatement, 调用executeUpdateDelete()方法以执行对应的更新语句。

2.4、查

​ (1) 使用参数的查询方法有:

public Cursor query(boolean distinct, String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit)
            
public Cursor query(boolean distinct, String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit, CancellationSignal cancellationSignal)
            
public Cursor query(String table, String[] columns, String selection,
            String[] selectionArgs, String groupBy, String having,
            String orderBy)
            
public Cursor query(String table, String[] columns, String selection,
            String[] selectionArgs, String groupBy, String having,
            String orderBy, String limit)

public Cursor queryWithFactory(CursorFactory cursorFactory,
            boolean distinct, String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit)
            
public Cursor queryWithFactory(CursorFactory cursorFactory,
            boolean distinct, String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit, CancellationSignal cancellationSignal)                        

​ 最上面五个方法是对最后一个方法queryWithFactory的调用,也是通过拼接sql语句再执行来完成查询操作的,方法如下:

public Cursor queryWithFactory(CursorFactory cursorFactory,
        boolean distinct, String table, String[] columns,
        String selection, String[] selectionArgs, String groupBy,
        String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
    acquireReference();
    try {
        String sql = SQLiteQueryBuilder.buildQueryString(
                distinct, table, columns, selection, groupBy, having, orderBy, limit);

        return rawQueryWithFactory(cursorFactory, sql, selectionArgs,
                findEditTable(table), cancellationSignal);
    } finally {
        releaseReference();
    }
}

​ 其中,调用了SQLiteQueryBuilder的buildQueryString方法来拼接sql语句,如下:

public static String buildQueryString(
        boolean distinct, String tables, String[] columns, String where,
        String groupBy, String having, String orderBy, String limit) {
    if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) {
        throw new IllegalArgumentException(
                "HAVING clauses are only permitted when using a groupBy clause");
    }
    if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
        throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
    }

    StringBuilder query = new StringBuilder(120);

    query.append("SELECT ");
    if (distinct) {
        query.append("DISTINCT ");
    }
    if (columns != null && columns.length != 0) {
        appendColumns(query, columns);
    } else {
        query.append("* ");
    }
    query.append("FROM ");
    query.append(tables);
    appendClause(query, " WHERE ", where);
    appendClause(query, " GROUP BY ", groupBy);
    appendClause(query, " HAVING ", having);
    appendClause(query, " ORDER BY ", orderBy);
    appendClause(query, " LIMIT ", limit);

    return query.toString();
}

​ 最终将sql语句拼接成如下格式(参数1为所需查询列拼接的字符串):SELECT distinct 参数1 FROM table WHERE selection GROUP BY groupBy HAVING having ORDER BY orderBy LIMIT limit。从查询语句来看,之前列出的查询方法都可以进行多表查询,示例如下:

//增加数据
ContentValues values = new ContentValues();
values.put("name", "张三");
writableDatabase.insert("user", "name", values);
writableDatabase.insert("user1", "name", values);

//查询数据
Cursor cursor = writableDatabase.query(true, "user,user1",
        new String[]{"user.name", "user.id", "user1.name", "user1.id"},
        "user.name = ? and user1.name = ?", new String[]{"张三", "张三"},
        null, null, null, null);
if (cursor == null) {
    Log.d("Test", "查询得到的cursor为空");
} else if (cursor.getCount() == 0) {
    Log.d("Test", "未查询到指定的数据");
    cursor.close();
} else {
    while (cursor.moveToNext()) {
        String name = cursor.getString(cursor.getColumnIndex("user.name"));
        int id = cursor.getInt(cursor.getColumnIndex("user.id"));
        String name1 = cursor.getString(cursor.getColumnIndex("user1.name"));
        int id1 = cursor.getInt(cursor.getColumnIndex("user1.id"));
        Log.d("Test", "name = " + name + ", id = " + id + ", name1 = " + name1 + ", id1 = " + id1);
    }
    cursor.close();
}

​ (2)直接使用sql的查询方法有:

public Cursor rawQuery(String sql, String[] selectionArgs)

public Cursor rawQuery(String sql, String[] selectionArgs,
            CancellationSignal cancellationSignal)
            
public Cursor rawQueryWithFactory(
            CursorFactory cursorFactory, String sql, String[] selectionArgs,
            String editTable)
            
public Cursor rawQueryWithFactory(
            CursorFactory cursorFactory, String sql, String[] selectionArgs,
            String editTable, CancellationSignal cancellationSignal)            

​ 直接通过sql语句来进行查询,可以进行多表复合查询,sql对对应的查询语句, selectionArgs为占位符?对应的参数数组。

2.5、其他

​ SQLiteDatabase除了增删改查之外, 还可以执行其他语句,包括创建表、删除表、重命名表、添加列等等。

public void execSQL(String sql) throws SQLException {
    executeSql(sql, null);
}


public void execSQL(String sql, Object[] bindArgs) throws SQLException {
        if (bindArgs == null) {
            throw new IllegalArgumentException("Empty bindArgs");
        }
        executeSql(sql, bindArgs);
}



public int executeSql(String sql, Object[] bindArgs) {
   ...
}

3、SQLiteDatabase事务

​ 某种情况下,需要执行一系列操作时,确保这些操作同时成功,或者同时失败,数据库中能够保证这些操作同时成功或同时失败的执行单元称为事务,典型的代码如下:

   sqliteDatabase.beginTransaction();
   try {
      ...
      sqliteDatabase.setTransactionSuccessful();
   } finally {
      sqliteDatabase.endTransaction();
   }

​ SQLiteDatabase的beginTransaction()方法标识一个事务的开始, setTransactionSuccessful()表示增删改查等操作成功,endTransaction()方法表示结束一个事务,此时修改将被提交到数据库。如果过程中产生异常,则不会调用setTransactionSuccessful(),中间的各种操作将被还原。

​ 事务开始标识有如下方法:

public void beginTransaction() {
    beginTransaction(null /* transactionStatusCallback */, true);
}

public void beginTransactionNonExclusive() {
    beginTransaction(null /* transactionStatusCallback */, false);
}

public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
    beginTransaction(transactionListener, true);
}

public void beginTransactionWithListenerNonExclusive(
        SQLiteTransactionListener transactionListener) {
    beginTransaction(transactionListener, false);
}

private void beginTransaction(SQLiteTransactionListener transactionListener,
        boolean exclusive) {
    acquireReference();
    try {
        getThreadSession().beginTransaction(
                exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
                        SQLiteSession.TRANSACTION_MODE_IMMEDIATE,
                transactionListener,
                getThreadDefaultConnectionFlags(false /*readOnly*/), null);
    } finally {
        releaseReference();
    }
}

​ 其中SQLiteTransactionListener为事务执行过程监听接口,可以监测到事务具体的执行情况:onBegin、onCommit、onRoolback, 分别表示开始、提交、还原。

public interface SQLiteTransactionListener {
    /**
     * Called immediately after the transaction begins.
     */
    void onBegin();

    /**
     * Called immediately before commiting the transaction.
     */
    void onCommit();

    /**
     * Called if the transaction is about to be rolled back.
     */
    void onRollback();
}

总结:以上简要分析了SQLiteDatabase的用法,包括增删改查等, 还有就是SQLite事务在安卓中的应用。

posted @ 2021-04-19 16:52  笪笠  阅读(796)  评论(0编辑  收藏  举报