您的位置:首页 > 其它

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

2016-12-12 21:40 162 查看

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方法,根据自定义主键查重
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐