Android activeandroid 唯一约束

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

Ps:onUniqueConflicts一定要配合uniqueGroups使用,不然不起作用。我当初是这么写的:

@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;
    }

至此问题得到解决。撒花~~~

每日语录:

生命并没有什么意义,但是活着的话就可能会遇见有意思的事,所以陌生人,不管多难,都请坚持下去,海子说:你来人间一趟,你要看看太阳,和你的心上人,一起走在街上,了解她,也要了解太阳(一组健康的工人,正午抽着纸烟),夏天的太阳,太阳,当年基督入世,也在这太阳下长大。加油!!!

单曲循环《活着》

猜你喜欢

转载自blog.csdn.net/androidzmm/article/details/102586474