1:在之前的关于activeandroid的基本使用中讲,为了保持数据的唯一性,可以增加唯一的约束
@Column(name = "person_id",unique = true)
private int personId;
当时没仔细说,其实只加了这个唯一约束,只能保证具有相同的personId的只有一条数据,但是却不能及时更新。假设数据库里存了一条personId为1的数据,下一次,我们再从云端获取到personId为1的数据,这个时候就保存不进去了,如果这个时候,personId为1的这条数据更新了,我们数据库就还是旧的数据。造成这个问题的原因是,SQLite 在ON CONFLICT子句定义了解决约束冲突的算法。有五个选择:ROLLBACK, ABORT, FAIL, IGNORE,具体的不同请移步:SQLite on conflict子句, 而activeAndroid在创建表时候,默认的解决约束冲突的算法是:UNIQUE ON CONFLICT FAIL,"FAIL:当发生约束冲突,命令中止返回SQLITE_CONSTRAINT。但遇到冲突之前的所有改变将被保留"。也就是这里会插入失败。
那我们可不可以指定这个解决约束冲突的算法呢,当然是可以的。activeAndroid提供了一个属性onUniqueConflict,我们可以这么写:
@Column(name = "person_id", unique = true, onUniqueConflict = Column.ConflictAction.REPLACE) private int personId;
指定解决冲突的算法为:REPLACE,这样每次有相同的数据来的时候,会先删除掉之前的,然后再把最新的添加到数据库。
activeandroid还提供了了一个字段:onUniqueConflicts,看名字是存放一组解决冲突的算法,这个要配合uniqueGroups = {"group"}使用。官网
/*
* If set uniqueGroups = {"group_name"}, we will create a table constraint with group.
*
* Example:
*
* @Table(name = "table_name")
* public class Table extends Model {
* @Column(name = "member1", uniqueGroups = {"group1"}, onUniqueConflicts = {ConflictAction.FAIL})
* public String member1;
*
* @Column(name = "member2", uniqueGroups = {"group1", "group2"}, onUniqueConflicts = {ConflictAction.FAIL, ConflictAction.IGNORE})
* public String member2;
*
* @Column(name = "member3", uniqueGroups = {"group2"}, onUniqueConflicts = {ConflictAction.IGNORE})
* public String member3;
* }
*
* CREATE TABLE table_name (..., UNIQUE (member1, member2) ON CONFLICT FAIL, UNIQUE (member2, member3) ON CONFLICT IGNORE)
*/
详情:(见https://github.com/pardom/ActiveAndroid/blob/master/src/com/activeandroid/annotation/Column.java)
@Column(name = "person_id", unique = true, onUniqueConflicts = {Column.ConflictAction.REPLACE}) private int personId;
然后怎么冲突出现的时候 ,一直save失败,一直返回的id是-1;
Error inserting UNIQUE constraint failed: WatchFaceCloud.WatchFaceId (Sqlite code 2067), (OS error - 2:No such file or directory)
看日志的报错信息就是唯一冲突。最后把数据库导出来看了下,发现创建的表的时候,Column.ConflictAction.REPLACE这个没有起效,创建表的时候用的还是:UNIQUE ON CONFLICT FAIL。后来看下官网才知道。这都是本人血的教训,找了好几个小时的错误原因。
2:因为之前的失误,导致我们的数据表结构变了,也就是我们不只是数据库版本+1这么简单,需要我们做数据表的迁移,因为本人对数据库操作也不熟悉,怕有风险,所以没有做数据表迁移的操作,如果是新装app的,自然没有问题,遇到冲突的时候会按照replace算法走,但是之前装app的用户,更新app以后,再插入一条数据之前,我们先去查找,有的话,先删掉,然后再save,保证可以插入进去。在这个过程中又遇到一个问题。
就是如果一个Model对象save失败以后,会一直失败。即使你把数据从数据库删掉了,再次save(),还是会返回-1;
public void save(){
WatchFaceCloud watchFaceCloud = gson.fromJson(json, WatchFaceCloud.class);
/**
* 先save,如果是新装的app,遇到唯一约束,会使用算法Replace,会直接save成功
* 如果是版本更新的app的用户,遇到唯一约束,因为默认使用的是Fail,会导致save失败,返回-1
*/
Long save = watchFaceCloud.save();
if (save == -1) {
delete(watchFaceCloud.getWatchFaceId());
watchFaceCloud.save();//依旧保存失败,返回的还是-1;
}
}
public void delete(int watchFaceId) {
new Delete().from(WatchFaceCloud.class).where("WatchFaceId = ?", watchFaceId).execute();
}
我们来看下源码:
public final Long save() {
final SQLiteDatabase db = Cache.openDatabase();
final ContentValues values = new ContentValues();
....//省略,这里是把我们的实体类转成ContentValues。
if (mId == null) {
mId = db.insert(mTableInfo.getTableName(), null, values);
}
else {
db.update(mTableInfo.getTableName(), values, idName+"=" + mId, null);
}
Cache.getContext().getContentResolver()
.notifyChange(ContentProvider.createUri(mTableInfo.getType(), mId), null);
return mId;
}
当我们想更新数据的时候,因为唯一约束,会导致该条数据保存失败,也就是mId=-1;然后我们根据这条数据的一个字段去删掉该条数据,删除成功了,我们再调用该model的save,这个时候该model的mId=-1.所以是走的else
db.update(mTableInfo.getTableName(), values, idName+"=" + mId, null);
这是去更新数据库id为-1的数据,其实数据库根本米有这个数据,会没有任何反应,然后直接return mId了,其实我们根本没有存进去,返回的也还是-1.但是不会报: Error inserting UNIQUE constraint failed。设计者应该没有考虑到会save两次的情况。估计也没人这么用(手动滑稽)。
但是像我,就是比较倔,觉得我的逻辑是米有问题的,就像这么走下去,那么就有两个解决办法了。
A:反射。
删除以后,反射该model,拿到mId字段,强制设置值为null.
public void reflexFileId(WatchFaceCloud watchFaceCloud) {
if (watchFaceCloud == null) {
return;
}
Field f;
Class temp = watchFaceCloud.getClass();
try {
//因为mId是在我们的实体类的父类Model里面所以需要先拿到父类的Class
//得到父类,然后赋给自己
temp = temp.getSuperclass();
f = temp.getDeclaredField("mId");
f.setAccessible(true);
Long id = (Long) f.get(watchFaceCloud);
if (id == -1) {
f.set(watchFaceCloud, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
设置成功以后,再次调用watchfaceCloud.save();就会发现save成功了。
B:clone一个类,即数据一模一样,但是是两个对象。一个对象用来试探性的save,如果save失败了,就删除,然后用clone的对象再去save.
public void save(){
//要保存的watchFaceCloud
WatchFaceCloud watchFaceCloud = gson.fromJson(json, WatchFaceCloud.class);
//先去查下,看下是否会引发唯一约束的问题
WatchFaceCloud oldWatchFace = getWatchFace(watchFaceCloud.getWatchFaceId());
boolean add;
if (oldWatchFace != null) {
//生成一个临时的变量,试探是否是旧版本的数据库表结构,
// watchFaceCloud.save(),失败以后item的id,删掉就变成-1了,再次save还是会失败。
WatchFaceCloud tempWatchFace = new WatchFaceCloud();
tempWatchFace.setWatchFaceId(watchFaceCloud.getWatchFaceId());
add = tempWatchFace.save()>0;
//重复保存失败,说明是旧版本的数据库表结构,则需要删除掉,再保存
if (!add) {
oldWatchFace.delete();
}
}
add =watchFaceCloud.save()>0;
}
至此问题得到解决。撒花~~~
每日语录:
生命并没有什么意义,但是活着的话就可能会遇见有意思的事,所以陌生人,不管多难,都请坚持下去,海子说:你来人间一趟,你要看看太阳,和你的心上人,一起走在街上,了解她,也要了解太阳(一组健康的工人,正午抽着纸烟),夏天的太阳,太阳,当年基督入世,也在这太阳下长大。加油!!!
单曲循环《活着》