一、新增
SQLite中的新增默认是单个事务控制的,一次新增就是一次数据库操作,一次事务。如果几千次for循环操作,必然存在效率问题。下面代码是通过事务控制的方式提升效率:
public
void
addList(List<GroupMember> listMember) {
StringBuffer sbSQL =
new
StringBuffer();
SQLiteDatabase db =
super
.getDatabase();
db.beginTransaction();
for
(
int
i =
0
; i < listMember.size(); i++) {
GroupMember groupMember = listMember.get(i);
if
(i ==
0
) {
// 根据当前用户id和圈子id删除圈子成员
del(groupMember.getUserId(), groupMember.getGroupId());
// 第一次新增的时候删除历史数据
}
if
(i !=
0
) {
sbSQL.delete(
0
, sbSQL.length());
}
sbSQL.append(
" INSERT INTO "
).append(TABLE).append(
" (user_id, group_id, member_id, role_id) VALUES"
);
sbSQL.append(
" ("
).append(groupMember.getUserId())
.append(
","
).append(groupMember.getGroupId())
.append(
","
).append(groupMember.getMemberId())
.append(
","
).append(groupMember.getRole())
.append(
");"
);
db.execSQL(sbSQL.toString());
}
db.setTransactionSuccessful();
db.endTransaction();
}
尽量别用下面的SQL语法,在部分机型上面会报错。(小米、三星S3)。上面的写法已经可以满足需要了……
INSERT
INTO
table
(column1, column2)
VALUES
(val1, val2), (val1, val2)
二、批量修改
需求的出现:比如存在N个聊天圈子,圈子中有N个成员。每次进入圈子的时候后台线程下载圈子成员最新数据,并更新数据库。这时候存在三个表:圈子,圈子-成员关系表,用户表。
用户表是所有圈子的用户,保存的时候需要判断是否存在,存在就新增,否则修改部分数据。(因为用户带有详细资料,而圈子成员返回的只有名字、账号、头像三个字段,不能用上面的方法先删除所有数据,然后批量新增)
SQL关键写法如下(重点是SQLiteDatabase.insertWithOnConflict的用法):
public
void
insertOrReplace(List<GroupMember> listMember) {
SQLiteDatabase db =
super
.getDatabase();
db.beginTransaction();
for
(
int
i =
0
; i < listMember.size(); i++) {
BaseUserInfo baseUserInfo = listMember.get(i).getBaseUserInfo();
ContentValues cv =
new
ContentValues();
cv.put(
"user_id"
, baseUserInfo.getUserId());
cv.put(
"name"
, baseUserInfo.getName());
cv.put(
"logo"
, baseUserInfo.getLogo());
// 生成的sql是 INSERT INTRO OR REPLACE INTO 这样的 (如果存在就替换存在的字段值. 存在的判断标准是主键冲突, 这里的主键是userId). 下面会介绍这个地方的方法
db.insertWithOnConflict(TABLE,
null
, cv, SQLiteDatabase.CONFLICT_REPLACE);
}
db.setTransactionSuccessful();
db.endTransaction();
}
关键的一个地方:
insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, intconflictAlgorithm)
部分参数介绍(忽略参数table和initialValues了,这个大家都知道吧 - -):
1) nullColumnHack:当values参数为空或者里面没有内容的时候,我们insert是会失败的(底层数据库不允许插入一个空行),为了防止这种情况,我们要在这里指定一个列名,到时候如果发现将要插入的行为空行时,就会将你指定的这个列名的值设为null,然后再向数据库中插入。(实际开发中一般设置为null就好。)
比如:如果values为空,最后生成的SQL大概是"INSERT INTO table"这样的,那么这是一个错误的SQL,肯定插入失败。但是如果指定了nullColumnHack,最终会生成SQL"INSERT INTO TABLE("+nullColumnHack+")" VALUES (null)"; 这样会插入一行没数据的行,但是SQL不会报错了。
实际android源码如下:
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.size() >
0
) ? 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();
}
}
2) conflictAlgorithm:
该参数是一个int值,上面源码中也用到了(CONFLICT_VALUES[conflictAlgorithm]),那么CONFLICT_VALUES的值是什么?Android源码中如下:
public
static
final
int
CONFLICT_ROLLBACK =
1
;
public
static
final
int
CONFLICT_ABORT =
2
;
public
static
final
int
CONFLICT_FAIL =
3
;
public
static
final
int
CONFLICT_IGNORE =
4
;
public
static
final
int
CONFLICT_REPLACE =
5
;
public
static
final
int
CONFLICT_NONE =
0
;
private
static
final
String[] CONFLICT_VALUES =
new
String[]{
""
,
" OR ROLLBACK "
,
" OR ABORT "
,
" OR FAIL "
,
" OR IGNORE "
,
" OR REPLACE "
};