LitePal无法使用自定义主键的临时解决方案

LitePal无法使用自定义主键的临时解决方案

1 解决

LitePal的默认主键名为id,类型为long。而且不可改变,无法设置其他名称,也无法设置其他类型。
对于一些主键使用了String类型的后台数据库,数据储存就很容易出问题。
最常见的问题就是,大量重复数据。因为LitePal认为这些数据的id不同,是不同的数据。
为此,本人提出了一种临时解决方案。并不是很完美,但能解决大部分情况。

思路:
key:主要问题出在DataSupport的save方法上。
- 0 比如我有一个Person类,主键名为personId,类型为String。
- 1 先使用DataSupport.where("personId = ?", personId)来获取所有主键值为personId的对象,并去除重复。

public static Person findPersonByPersonId(String personId) {
   List<Person> persons = DataSupport.where("personId = ?", personId).find(Person.class);
    if (persons == null || persons.size() == 0) {
        return null;
    } else {
        for (int i = 1; i < persons.size(); i++) {
            persons.get(i).delete();
        }
    }
    return persons.get(0);
}

2 重写save方法。保存时比对该personId是否已存在,存在就将id设置成从数据库中取出来的对象的id,再save。(关于save方法,注释写的很清楚,当这个id的字段存在时,它的作用相当于update)

@Override
public synchronized boolean save() {
    Person p = findPersonByPersonId(personId);
    if (p == null || p.id == 0) {//sqlite中自增id从1开始,0表示不存在
        return super.save();
    } else {
        this.id = p.id;
        return super.save();
    }
}

整个Person.java文件:

public class Person extends DataSupport {

    private long id;
    private String personId;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getPersonId() {
        return personId;
    }

    public void setPersonId(String personId) {
        this.personId = personId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static Person findPersonByPersonId(String personId) {
        List<Person> persons = DataSupport.where("personId = ?", personId).find(Person.class);
        if (persons == null || persons.size() == 0) {
            return null;
        } else {
            for (int i = 1; i < persons.size(); i++) {
                persons.get(i).delete();
            }
        }
        return persons.get(0);
    }

    @Override
    public synchronized boolean save() {
        Person p = findPersonByPersonId(personId);
        if (p == null || p.id == 0) {//sqlite中自增id从1开始,0表示不存在
            return super.save();
        } else {
            this.id = p.id;
            return super.save();
        }
    }
}

2 进阶

但是如果每个实体类都这么写,岂不是有很多重复代码?所以我们要学会封装。
- 1 long类型id及其get、set方法,写入基类
- 2 根据自定义主键获取对象的方法可以通过泛型+反射实现(注意:自定义主键必须是该实体类的第一个字段,这也刚好符合大多数人的编码习惯)
- 3 重写save方法

/**
 * 数据库表基础类
 *
 * @author Relish 2016/12/12
 */
public abstract class DBSupport<T extends DBSupport> extends DataSupport {

    protected long id;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }


    /**
     * 根据你自己定义的主键到数据库中查找这个实体<p></p>
     * 需要将主键作为实体类的第一个字段,就可以通过反射获得主键名和主键值
     *
     * @return 泛型实体类对象
     */
    @SuppressWarnings("unchecked")
    protected T find() {
        try {
            Class clazz = getGenericType();
            Field pk = clazz.getDeclaredFields()[0];
            pk.setAccessible(true);
            String pkValue = pk.get(this) + "";
            if (pkValue.isEmpty()) {
                return null;
            }
            List<T> list = where(pk.getName() + " = ?", pkValue).find(clazz);
            if (list == null || list.size() == 0) {
                return null;
            } else if (list.size() > 1) {
                for (int i = 1; i < list.size(); i++) {
                    list.get(i).delete();
                }
            }
            return list.get(0);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 在该数据库框架中,
     * Id只允许long或int类型,
     * 所以自定义的String类型的personId不唯一,
     * 会导致存储多个相同的数据。
     * 需要手动查重。
     *
     * @return 是否插入成功
     */
    @Override
    public synchronized boolean save() {
        T t = find();//根据主键名和主键值获取数据库中实例
        if (t == null || t.id == 0) {//不存在
            return super.save();
        } else {
            this.id = t.id;//更新字段
            return super.save();//update
        }
    }

    /**
     * 获得泛型类型
     * <p></p>
     * 注:其中DBSupport<T>是泛型类
     * 在父类中声明getGenericType
     * 子类继承具体的DBSupport<Person>
     * 那么在子类中就可以通过getGenericType()获取到Person的class.
     *
     * @return T.class
     */
    private Class getGenericType() {
        Type genType = getClass().getGenericSuperclass();
        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (!(params[0] instanceof Class)) {
            return Object.class;
        }
        return (Class) params[0];
    }
}

基类写好以后,Person类就可以简化好多代码。只需将extends DataSupport改成extends DBSupport<Person>即可,同时确保自定义主键是该实体类的第一个字段。其余代码完全不用改,是不是很棒!?

public class Person extends DBSupport<Person> {

    /**
     * 主键,必须写在第一个(反射需要)
     */
    private String personId;

    private String name;

    public String getPersonId() {
        return personId;
    }

    public void setPersonId(String personId) {
        this.personId = personId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

需要注意的有3点:
- 1 自定义主键必须是实体类的第一个字段
- 2 写完extends DBSupport后,不要忘记加上<实体类名>
- 3 在保存数据时都使用save方法,不要使用DataSupport.saveAll(Collection collection)方法,不然还是会有重复数据的。

3 最后说两句

介于以上三点注意事项。因此解决得并不完美,只能说是临时解决。如有遗漏,欢迎指出。

4 再深入

4.1 save和saveAll

看了源码之后,发现save和saveAll方法最后都指向一个名为doSaveAction的方法。查看内部实现,可以看出,save时还是会查看id值。

    private void doSaveAction(DataSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields)
            throws SecurityException, IllegalArgumentException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        values.clear();
        beforeSave(baseObj, supportedFields, values);
        long id = saving(baseObj, values);
        afterSave(baseObj, supportedFields, supportedGenericFields, id);
    }

理论上还是可以通过反射的办法获得自定义主键字段名和字段值,通过查重选择save或update。(未测试)
由此可以得出两种解决办法:
- 1 在save时根据查重,重置id值
- 2 重写onSaveAction方法,根据自定义主键查重

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值