cupboard2,rxcupboard2的使用
前言:
最近在开发一个应用的时候,需要下载大量的基础数据。这时候便需要我们的sqlite出马了。于是就找到了一款轻量级的sqlite管理三方库cupboard2,这也是我的师兄安利给我的,用起来感觉还不错。在使用过程中也遇到过很多坑,一方面记录一下自己的解决问题的过程,另一方面希望同样遇到过这些问题的同学能快速脱坑。
使用:
首先在Gradle中引入库,这里我还使用了另一个库rxcupboard2,因为现在一般在项目中都会使用RxJava嘛,所以配合rxcupboard2使用效果更佳!
implementation 'nl.2312:rxcupboard2:2.0'
如果想单独使用cupboard2的可以看原作者,链接在这里:https://bitbucket.org/littlerobots/cupboard
然后创建你的SQLiteOpenHelper类,Cupboard将java类与数据库的表映射在一起,同时java类中的属性对应数据库表的列字段。如果数据库增加了表,修改了字段,总之任何数据库的变动,只需增加数据库版本cupboard会自动处理升级的问题。
`
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import nl.qbusict.cupboard.Cupboard;
import static nl.qbusict.cupboard.CupboardFactory.cupboard;
public class CupboardDbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "RxCupboard.db";
private static final int DATABASE_VERSION = 1;
private static SQLiteDatabase database;
static {
Cupboard cupboard = cupboard();
cupboard.register(User.class);
}
public synchronized static SQLiteDatabase getConnection(Context context) {
if (database == null) {
// Construct the single helper and open the unique(!) db connection for the app
database = new CupboardDbHelper(context.getApplicationContext()).getWritableDatabase();
}
return database;
}
public CupboardDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
cupboard().withDatabase(db).createTables();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
cupboard().withDatabase(db).upgradeTables();
}
}
public class User {
private long _id;
private String userName;
private String passWord;
public long get_id() {
return _id;
}
public void set_id(long _id) {
this._id = _id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RxDatabase mDataBase = RxCupboard.withDefault(CupboardDbHelper.getConnection(this));
}
`
这里定义好你的实体类,并且在CupboardDbHelper类中的静态代码块儿中注册你的实体。运行起来就能看到你的数据库和你的表了。这里值得注意的是,_id 这个字段不管你写不写,Cupboard都会帮你创建一个 _id的字段并且他是主键自增的,翻看源码你可以发现。所以,我建议你还是在每张表里加上 _id这个字段。
`
boolean createNewTable(CupboardDatabase db, String table, List<Column> cols) {
StringBuilder sql = new StringBuilder("create table '").append(table).append("' (_id integer primary key autoincrement");
...
}
`
代码确实非常简洁,简单的就创建好了你的sqlite和表。到这里差不多就可以愉快的使用的你数据库了。mDataBase.query(User.class);mDataBase.delete();mDataBase.put();等方法足够你的增删改查的使用,它还可以自定义查询删除等条件,这里就不赘述了。
然鹅。
当遇到较为复杂的查询的时候,总会拼写sql语句吧,你难免写下一些列名的硬编码。某一天,如果你的列名变了,类型也变了。这时候需要去查找用到这个列名的地方,并且一一修改,万一某个地方没改到。。。唉,不说了,毕竟被坑过。
cupboard提供了@Column,@Ignore注解,@Column代表给该列指定别称,@Ignore 表示忽略此字段不在数据库中创建该字段。或许是因为网上某些信息的误导,我确实使用了这俩注解,但发现并没有效果。别称和忽略似乎都没有生效。非常苦恼的去翻看了一下源码
/** * Annotation interface that allows one to decouple a field name from a column * name, by specifying the column name in an Annotation. This is particularly * useful when working with existing data like the {@link ContactsContract} ContentProvider as it utilises * generic column names (e.g. data1, data2,...,data15) which map to various * aliases depending on the mime type in use for a given row. * <p/> * Note that annotations are not processed by default. To enable processing of annotations construct an instance of Cupboard using {@link nl.qbusict.cupboard.CupboardBuilder} and call {@link nl.qbusict.cupboard.CupboardBuilder#useAnnotations()} <br/> */
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface Column {
String value();
}
虽然看不太懂这里注释的意思,但是大概意思是不能使用工厂产生的默认单例,需要开启某个设置。网上那个cupboard的文章,我估计他根本没试过这俩注解,唉,害苦我了。带着半猜测的态度,试着将CupboardDbHelper改写了一下。
`
public static final Cupboard cupboard;
static {
cupboard = new CupboardBuilder().useAnnotations().build();
cupboard.register(User.class);
}
public class User {
public static final String USER_USERNAME = "name";
public static final String USER_PASSWORD = "pwd";
public long _id;
@Column(USER_USERNAME)
public String userName;
@Column(USER_PASSWORD)
public String passWord;
@Ignore
public String age;
}
`
运行之后发现,果然达到了我想要的效果。再也不用担心列名硬编码问题了。我很庆幸自己亲手去翻看了一下源码,没有继续执着看网上的教程。
然鹅。。。
当我愉快的往数据库里插入数据的时候,突然弹出了异常。。
java.lang.IllegalArgumentException: Entity is not registered: class User
f**k,居然提示我表没有注册? 我反复删除APP 测试,发现表确实已经创建了,但是就是不能插入数据。。思索良久,我便猜想会不会跟我改了cupboard创建方式有关,后来发现确实是。当我使用自己的创建的cupboard建表时,增删改操作确还是使用的工厂创建的默认实例。我忽略了这个问题。
`
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RxDatabase mDataBase = RxCupboard.with(CupboardDbHelper.cupboard, CupboardDbHelper.getConnection(this));
User user = new User();
user.userName = "admin";
user.passWord = "111111";
mDataBase.put(user).subscribe();
}
`
运行后,数据成功插入数据库。
最后,当我觉得坑都排得差不多的时候。发现服务器返回的数据量比较多(好几千条),如果按照目前这种方式逐条插入的话,肯定会带来性能问题。因为sqlite是默认开启事务的,每次插入数据都会开启 提交 关闭事务。查看了一下Rxcupboard的源码, 发现他并没有批量插入的操作。cupboard倒是提供了批量插入的操作。
`
cupboard源码:
/**
* Put multiple entities in a single transaction.
*
* @param entities the entities
*/
public void put(Collection<?> entities) {
boolean mNestedTransaction = mDatabase.inTransaction();
mDatabase.beginTransaction();
try {
for (Object entity : entities) {
put(entity);
if (!mNestedTransaction) {
mDatabase.yieldIfContendedSafely();
}
}
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
}
`
好吧, 没有办法了,都到这个地步了,总不能弃坑吧。本着开源的精神,我便把Rxcupboard的源码下了下来,并稍微修改了一下。既然你不提供批量插入的操作,我就改成我想要的。于是便在RxDatabase中增加了一个方法
`
public <T> Single<Collection<T>> put(final Collection<T> entities) {
return Single.fromCallable(new Callable<Collection<T>>() {
@Override
public Collection<T> call() throws Exception {
dc.put(entities);
return entities;
}
});
}
`
这就很舒服。。总算达到我理想的状态了,差不多小纠结了一天时间。