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方法,根据自定义主键查重
相关文章推荐
- 关于HP-UX中出现无法使用历史命令的问题的解决方案
- Thread中带参方法无法使用之解决方案
- "无法使用前导 .. 在顶级目录上退出"的解决方案(asp.net2.0+urlrewrite)
- Fedora10无法使用root用户登录的解决方案
- Fedora10无法使用root用户登录的解决方案
- 系统搜索功能无法使用解决方案
- 分页解决方案 之 QuickPager的使用方法(PostBack分页、自定义获取数据)
- 关于:“无法序列化会话状态。在“StateServer”或“SQLServer”模式下,ASP.NET 将序列化会话状态对象,因此不允许使用无法序列化的对象或 MarshalByRef 对象。如果自定义会话状态存储在“Custom”模式下执行了类似的序列化,则适用同样的限制。”的问题
- 关于:“无法序列化会话状态。在“StateServer”或“SQLServer”模式下,ASP.NET 将序列化会话状态对象,因此不允许使用无法序列化的对象或 MarshalByRef 对象。如果自定义会话状态存储在“Custom”模式下执行了类似的序列化
- T61笔记本启动后键盘无法使用解决方案
- (WebSite----Asp.Net Configuration----->无法连接到SQL Server数据库------>选择数据存储区---->应用程序当前被配置为使用提供程序:AspNetSqlProvider)解决方案
- Asp.net Mvc中MVCContrib中无法使用Castle的发解决方案
- Minilogon后Ctrl+Alt+Del无法使用的解决方案
- Asp.net Mvc中MVCContrib中无法使用Castle的发解决方案
- [原创]Tsys 2.0 beta 官方版无法使用自定义SQ
- 针对IBM HP ACER无法使用一键恢复的一般解决方案:
- Asp.net Mvc中MVCContrib中无法使用Castle的解决方案
- FLEX 应用---无法使用MXMLC命令解决方案
- 分页解决方案 之 QuickPager的使用方法(PostBack分页、自定义获取数据)