Android DBFlow分库与自动升级
前言
最近研究了下Android常用的数据库,之前有使用greendao和ormlite等数据,各有千秋。之所以写这篇文章是有的老板反馈DBFlow性能挺好,就是切换数据库的时候有点力不从心,于是我就研究了下看如何解决分库的问题。
一、自动更新
官方提供的更新字段方法
@Database(version = 2)
public class AppDatabase {
@Migration(version = 2, database = AppDatabase.class)
public class Migration2 extends AlterTableMigration<User> {
public Migration2(Class<User> table) {
super(table);
}
@Override
public void onPreMigrate() {
addColumn(SQLiteType.TEXT, "myColumn");
addColumn(SQLiteType.REAL, "anotherColumn");
}
}
}
如上,每当我们修改字段的时候都必须手动编写升级的方法,做过字段升级的开发者应该都清楚,这种操作成本太高了,而且容易出错。
下面实现自动更新字段
修改源代码DBFlow,重新打包
唯一的修改就是把@Column改成@Retention(RetentionPolicy.SOURCE),如果是RUNTIME类型,编译成class文件后,注解就消失了,因为我这个方案是通过@Column来实现字段的升级。
1.在项目目录的build.gradle中加入
allprojects {
repositories {
//...
maven { url "https://jitpack.io" }
}
}
2.在工程目录的build.gradle中配置
apply plugin: 'com.android.application'
def dbflow_version = "4.2.5"
android {
...
}
dependencies {
//...
annotationProcessor "com.github.joyrun.DBFlow:dbflow-processor:${dbflow_version}"
implementation "com.github.joyrun.DBFlow:dbflow-core:${dbflow_version}"
implementation "com.github.joyrun.DBFlow:dbflow:${dbflow_version}"
// sql-cipher database encyrption (optional)
implementation "com.github.joyrun.DBFlow:dbflow-sqlcipher:${dbflow_version}"
}
二、创建AppDatabase
import android.database.Cursor;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.Database;
import com.raizlabs.android.dbflow.annotation.Migration;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.sql.QueryBuilder;
import com.raizlabs.android.dbflow.sql.migration.BaseMigration;
import com.raizlabs.android.dbflow.structure.InvalidDBConfiguration;
import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper;
import java.lang.reflect.Field;
import java.util.List;
import cn.yun.base.utils.klog.KLog;
//@Database(name = AppDatabase.NAME, version = AppDatabase.VERSION)//不分库时使用
@Database(version = AppDatabase.VERSION)//分库时使用,只写version,name用来分库
public class AppDatabase {
//数据库名称
// public static final String NAME = "jwDatabase";
//数据库版本号(表结构修改时记得修改version,可以将version与app版本号做绑定判断)
public static final int VERSION = 4;
@Migration(version = AppDatabase.VERSION, database = AppDatabase.class)
public static class DatabaseAutoUpdate extends BaseDatabaseAutoUpdate {
@Override
protected String getDatabaseName() {
// return AppDatabase.NAME;
return MyDBFlowUtil.getDbName();
}
}
public static abstract class BaseDatabaseAutoUpdate extends BaseMigration {
protected abstract String getDatabaseName();
@Override
public void migrate(DatabaseWrapper database) {
// KLog.e("DBFlow升级:");
try {
String databaseName = getDatabaseName();
// KLog.e("DBFlow升级时数据库名字:"+databaseName);
List<Class<?>> classes = FlowManager.getDatabase(databaseName).getModelClasses();
for (Class c : classes) {
try {
Cursor cursor = database.rawQuery("SELECT * FROM " + c.getSimpleName(), null);
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Column.class)) {
// KLog.e("DBFlow field.getName() :"+field.getName());
if (cursor.getColumnIndex(field.getName()) < 0) {
//缺少的字段
QueryBuilder queryBuilder = new QueryBuilder().append("ALTER")
.appendSpaceSeparated("TABLE")
.appendSpaceSeparated(c.getSimpleName())
.appendSpaceSeparated("ADD COLUMN")
.appendSpaceSeparated(QueryBuilder.quoteIfNeeded(field.getName()));
String sql = queryBuilder.getQuery();
database.execSQL(sql);
// KLog.e("DBFlow ALTER field.getName() :"+field.getName());
}
}
}
cursor.close();
} catch (Exception e) {
// KLog.e("DBFlow异常:"+e);
}
}
} catch (InvalidDBConfiguration e) {
e.printStackTrace();
}
}
}
}
三、动态分库
import android.content.Context;
import android.text.TextUtils;
import com.jwd.shop.app.StringConfig;
import com.jwd.shop.bean.ShopDataBean;
import com.orhanobut.hawk.Hawk;
import com.raizlabs.android.dbflow.config.DatabaseConfig;
import com.raizlabs.android.dbflow.config.DatabaseHolder;
import com.raizlabs.android.dbflow.config.FlowConfig;
import com.raizlabs.android.dbflow.config.FlowManager;
import com.raizlabs.android.dbflow.config.GeneratedDatabaseHolder;
public class MyDBFlowUtil {
public static void initDBFlow(Context context) {
FlowManager.close();
FlowConfig.Builder flowConfig = new FlowConfig.Builder(context).openDatabasesOnInit(true);
addDatabase(flowConfig, GeneratedDatabaseHolder.class, AppDatabase.class,getDbName());
FlowManager.init(flowConfig.build());
}
private static void addDatabase(FlowConfig.Builder flowConfig, Class<? extends DatabaseHolder> databaseHolderClass, Class<?> databaseClass, String dbName) {
flowConfig.addDatabaseHolder(databaseHolderClass);
flowConfig.addDatabaseConfig(new DatabaseConfig.Builder(databaseClass).databaseName(dbName).build());
}
/**
* 实现自己的分库逻辑,根据不同的门店id创建不同的数据库,切换门店则切换数据库
* @return
*/
public static String getDbName() {
//获取门店id作为数据库名(这里用到Hawk类似sharedpreferences的一个强大的库)
ShopDataBean shopDataBean = Hawk.get(StringConfig.SHOP_INFO);
String shopId = shopDataBean.getShopInfo().getShopId();
String dbName = !TextUtils.isEmpty(shopId) ? shopId : "jwdb";
return dbName;
}
}
四、初始化(在业务需要切换数据库的地方使用,比如切换门店后、切换用户等)
注意:切换账号的时候,必须再次调用MyDBFlowUtil.initDBFlow方法,用于销毁DBFlow,重新初始化。
MyDBFlowUtil.initDBFlow(mActivity);
// FlowManager.init(mActivity);
//设置日志显示
FlowLog.setMinimumLoggingLevel(FlowLog.Level.V);
五、创建表
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.OneToMany;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.sql.language.SQLite;
import com.raizlabs.android.dbflow.structure.BaseModel;
import java.util.List;
import com.jwd.shop.dbflows.AppDatabase;
@Table(database = AppDatabase.class)
public class CreateOrderOfflineModel extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public int flowNo;
@Column
public String shopName;
@Column
public String shopPayCode;
@Column
public String companyId;
@Column
public String companyName;
@Column
public String orderNo;
@Column
public String shopId;
@Column
public String deviceId;
@Column
public String deviceUid;
@Column
public String customerId;
@Column
public String customerPhone;
@Column
public int goodsAmount;
@Column
public int activityAmount;
@Column
public int voucherAmount;
@Column
public int discountAmount;
@Column
public int finalAmount;
@Column
public String activityId;
@Column
public String activityName;
@Column
public String payType;
@Column
public int payTotalAmount;
@Column
public int payAmount;
public List<OrderGoodsOfflineModel> goods;
public List<OrderAttrBeanModel> orderAttr;
@OneToMany(methods = {OneToMany.Method.ALL}, variableName = "goods")
public List<OrderGoodsOfflineModel> getGoods() {
if (goods == null || goods.isEmpty()) {
goods = SQLite.select()
.from(OrderGoodsOfflineModel.class)
.where(OrderGoodsOfflineModel_Table.createOrderOfflineModel_id.eq(id))
.queryList();
}
return goods;
}
@OneToMany(methods = {OneToMany.Method.ALL}, variableName = "orderAttr")
public List<OrderAttrBeanModel> getOrderAttr() {
if (orderAttr == null || orderAttr.isEmpty()) {
orderAttr = SQLite.select()
.from(OrderAttrBeanModel.class)
.where(OrderAttrBeanModel_Table.createOrderOfflineModel_id.eq(id))
.queryList();
}
return orderAttr;
}
// @Override
// public String toString() {
// return "CreateOrderOfflineModel{" +
// "id=" + id +
// ", flowNo=" + flowNo +
// ", shopName='" + shopName + '\'' +
// ", shopPayCode='" + shopPayCode + '\'' +
// ", companyId='" + companyId + '\'' +
// ", companyName='" + companyName + '\'' +
// ", orderNo='" + orderNo + '\'' +
// ", shopId='" + shopId + '\'' +
// ", deviceId='" + deviceId + '\'' +
// ", deviceUid='" + deviceUid + '\'' +
// ", customerId='" + customerId + '\'' +
// ", customerPhone='" + customerPhone + '\'' +
// ", goodsAmount=" + goodsAmount +
// ", activityAmount=" + activityAmount +
// ", voucherAmount=" + voucherAmount +
// ", discountAmount=" + discountAmount +
// ", finalAmount=" + finalAmount +
// ", activityId='" + activityId + '\'' +
// ", activityName='" + activityName + '\'' +
// ", payType='" + payType + '\'' +
// ", payTotalAmount=" + payTotalAmount +
// ", payAmount=" + payAmount +
// ", goods=" + goods +
// ", orderAttr=" + orderAttr +
// '}';
// }
}
六、一对多
import com.jwd.shop.dbflows.AppDatabase;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.ForeignKey;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.structure.BaseModel;
@Table(database = AppDatabase.class)
public class OrderGoodsOfflineModel extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String goodsId;
@Column
public String goodsName;
@Column
public String goodsSap;
@Column
public long goodsPrice;
@Column
public String goodsCategoryId;
@Column
public String goodsCategoryName;
@Column
public String goodsTasteId;
@Column
public String goodsTasteName;
@Column
public String goodsUnitId;
@Column
public String goodsUnitCode;
@Column
public String goodsUnitName;
@Column
public int goodsNum;
@Column
public int goodsAmount;
@Column
public int goodsDiscount;
@Column
public int finalGoodsAmount;
@ForeignKey(stubbedRelationship = true)
public CreateOrderOfflineModel createOrderOfflineModel;
}
import com.jwd.shop.dbflows.AppDatabase;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.ForeignKey;
import com.raizlabs.android.dbflow.annotation.OneToMany;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.sql.language.SQLite;
import com.raizlabs.android.dbflow.structure.BaseModel;
import java.util.List;
@Table(database = AppDatabase.class)
public class OrderAttrBeanModel extends BaseModel {
@PrimaryKey(autoincrement = true)
public long _id;
@Column
public String id;
@Column
public String name;
@Column
public String desc;
@Column
public String type;
@Column
public int amount;
@ForeignKey(stubbedRelationship = true)
public CreateOrderOfflineModel createOrderOfflineModel;
public List<DpzkUseItemModel> useItem;
@OneToMany(methods = {OneToMany.Method.ALL}, variableName = "useItem")
public List<DpzkUseItemModel> getUseItem() {
if (useItem == null || useItem.isEmpty()) {
useItem = SQLite.select()
.from(DpzkUseItemModel.class)
.where(DpzkUseItemModel_Table.orderAttrBeanModel__id.eq(_id))
.queryList();
}
return useItem;
}
}
import com.jwd.shop.dbflows.AppDatabase;
import com.raizlabs.android.dbflow.annotation.Column;
import com.raizlabs.android.dbflow.annotation.ForeignKey;
import com.raizlabs.android.dbflow.annotation.PrimaryKey;
import com.raizlabs.android.dbflow.annotation.Table;
import com.raizlabs.android.dbflow.structure.BaseModel;
@Table(database = AppDatabase.class)
public class DpzkUseItemModel extends BaseModel {
@PrimaryKey(autoincrement = true)
public long id;
@Column
public String name;
@Column
public String sapCode;
@Column
public int rate;
@ForeignKey(stubbedRelationship = true)
public OrderAttrBeanModel orderAttrBeanModel;
}
七、增删改查的常规操作
//-------------------------添加操作-----------------------
//DBFlow 对数据的增删改查已经做了封装,使用起来比较简单
private void insertBaseModle() {
BigSeaInfo0 product = new BigSeaInfo0();
product.name = "P000" + index;
product.age = 18 + index;
product.remarks = "备注-" + index;
product.money = 300 * index;
// product.insert();
boolean success = product.save();
index++;
// 执行到这里之后 id 已经被赋值
App.showToast(this, "添加结果:" + success);
}
//-------------------------修改操作-----------------------
/*
* 更新和删除可以为先查询后操作,只要查到对应的数据,在 bean 上做修改,
* 然后调用 update() 方法,数据库就能修改完成。还有另一中更接近 sql 语法的方式。
*/
private void updateBaseModle0() {
// 第一种 先查后改
BigSeaInfo0 product = SQLite.select()
.from(BigSeaInfo0.class)//查询第一个
.where(BigSeaInfo0_Table.name.is("P0000"))
.querySingle();// 区别与 queryList()
if (product != null) {
Log.d("zhh_Bd", "Update: " + product.name + " update to P0000");
product.name = "P000X";
boolean success = product.update();
App.showToast(this, "修改结果:" + success);
} else {
App.showToast(this, "name=P0000的条件数据不存在:");
}
}
// update:第二种 高级用法,删改查都是同理
private void updateBaseModle1() {
SQLite.update(BigSeaInfo0.class)
.set(BigSeaInfo0_Table.name.eq("PXXXX"))
.where(BigSeaInfo0_Table.name.is("P0001"))
.execute();
}
//-------------------------删除操作-----------------------
//删除1
private void deleteBaseModle() {
// 第一种 先查后删
BigSeaInfo0 product = SQLite.select()
.from(BigSeaInfo0.class)
.where(BigSeaInfo0_Table.name.is("P00010"))
.querySingle();//查询单个
if (product != null) {
product.delete();
Log.d("zhh_db", "Delete: " + product.name);
}
//删除一张表
// Delete.table(BigSeaInfo0.class);
// 删除多张表
// Delete.tables(MyTable1.class, MyTable2.class);
}
//删除2
private void deleteBaseModle1() {
// 第二种
SQLite.delete(BigSeaInfo0.class)
.where(BigSeaInfo0_Table.name.is("PXXXX"))
.execute();
}
//-------------------------查询操作-----------------------
//查询
private void selectBaseModle() {
// List<BigSeaInfo0> list = SQLite.select().from(BigSeaInfo0.class).queryList();
List<BigSeaInfo0> list = SQLite.select().from(BigSeaInfo0.class).
where(BigSeaInfo0_Table.name.like("P%")).queryList();
printData(list);
}
//异步执行查询:尽管是异步的,但是线程安全的
private void selectBaseModleSync() {
SQLite.select().from(BigSeaInfo0.class)//.where(BigSeaInfo0_Table.name.is(""))
.async().queryListResultCallback(new QueryTransaction.
QueryResultListCallback<BigSeaInfo0>() {
@Override
public void onListQueryResult(QueryTransaction transaction,
@NonNull List<BigSeaInfo0> tResult) {
printData(tResult);//更新UI
}
});
}
//指定字段升降序查询
private void selectBaseModleOrderBy() {
//true为'ASC'正序, false为'DESC'反序
List<BigSeaInfo0> list = SQLite.select()
.from(BigSeaInfo0.class)
.where()
.orderBy(BigSeaInfo0_Table.userId, true)
.queryList();
printData(list);
}
//分组查询--以年龄+名字分组查询:先排序后分组
private void selectBaseModleGroupBy() {
List<BigSeaInfo0> list = SQLite.select()
.from(BigSeaInfo0.class)
.groupBy(BigSeaInfo0_Table.age, BigSeaInfo0_Table.name)
.queryList();
printData(list);
}
//分页查询--每页查询3条--》limit后面跟的是3条数据,offset:是从第(page*3)条开始读取
private void selectPageBaseModle(int page) {
List<BigSeaInfo0> list = SQLite.select()
.from(BigSeaInfo0.class)
.limit(3)//条数-》3
.offset(page * 3)//当前页数
.queryList();
printData(list);
}
八、事务操作
//-------------------------添加操作-----------------------
private void insertBaseModle0() {
BigSeaInfo0 product = new BigSeaInfo0();
product.name = "P000" + index;
product.age = 18 + index;
product.remarks = "备注-" + index;
product.money = 300 * index;
//boolean success = product.save();
boolean success = product.async().save();//异步
//添加成功,但是返回为false,因为是异步,还未执行完成异步就返回值了,所以需要配置事物操作(个人理解)
index++;
// 执行到这里之后 id 已经被赋值
App.showToast(this, "添加结果:" + success);
}
//异步执行查询:尽管是异步的,但是线程安全的
private void insertBaseModle1() {
//自己去实现事务批量保存
Transaction transaction = FlowManager.getDatabase(AppDatabase.class).
beginTransactionAsync(new ITransaction() {
@Override
public void execute(DatabaseWrapper databaseWrapper) {
// todo 处理list保存:批量添加
int i = 0;
Iterator<BigSeaInfo0> iterator = resultList().iterator();
while (iterator.hasNext()) {
BigSeaInfo0 info = iterator.next();
boolean success = info.save();
i = success ? ++i : i;
}
Log.i("zhh_db_sync", "成功添加了数据条数:" + i);
}
}).build();
transaction.execute();
//transaction.executeSync();//异步执行
}
private void insertBaseModle2() {
FlowManager.getDatabase(AppDatabase.class)
.beginTransactionAsync(new ProcessModelTransaction.Builder<>(
new ProcessModelTransaction.ProcessModel<BigSeaInfo0>() {
@Override
public void processModel(BigSeaInfo0 bigSeaInfo0,
DatabaseWrapper wrapper) {
// do work here -- i.e. user.delete() or user.update()
//增删改操作等,这里添加集合对象
boolean success = bigSeaInfo0.save();
Log.i("zhh_db_sync", "添加结果" + success);
}
}).addAll(resultList()).build())
.error(new Transaction.Error() {
@Override
public void onError(Transaction transaction, Throwable error) {
App.showToast(DbFlowTransactionActivity.this,
"error结果:" + error.getMessage());
Log.i("zhh_db_sync", "error结果" + error.getMessage());
}
})
.success(new Transaction.Success() {
@Override
public void onSuccess(Transaction transaction) {
App.showToast(DbFlowTransactionActivity.this,
"success结果:" + transaction.name());
Log.i("zhh_db_sync", "添加success");
}
}).build()
// .execute();//同步
.executeSync();//异步
}
//-------------------------修改||删除操作-----------------------
private void updateBaseModle0() {
//不存在的条件数据做更改,也走成功的方法
SQLite.update(BigSeaInfo0.class)
.set(BigSeaInfo0_Table.name.eq("PXXXX"))
.where(BigSeaInfo0_Table.name.is("P0001"))
.async()
.error(new Transaction.Error() {
@Override
public void onError(@NonNull Transaction transaction, @NonNull Throwable error) {
Log.i("zhh_db_sync", "异步修改error---" + error.getMessage());
}
}).success(new Transaction.Success() {
@Override
public void onSuccess(@NonNull Transaction transaction) {
Log.i("zhh_db_sync", "异步修改success");
}
}).execute();
}
//删除同上,SQLite.update--》改为SQLite.delete
//-------------------------查询操作-----------------------
//提供了三种方式
//方式1:QueryResultSingleCallback:单个查询
SQLite.select().from(BigSeaInfo0.class).where(BigSeaInfo0_Table.name.is("P0000"))
.async()//异步查询
.querySingleResultCallback(new QueryTransaction.
QueryResultSingleCallback<BigSeaInfo0>() {
@Override
public void onSingleQueryResult(QueryTransaction transaction,
@Nullable BigSeaInfo0 bigSeaInfo0) {
if(null!=bigSeaInfo0){
Log.i("zhh_db_sync", "对象查询-----异步2--id---"
+ bigSeaInfo0.userId + ",name->" + bigSeaInfo0.name +
",age---" + bigSeaInfo0.age + ",note--" + bigSeaInfo0.remarks +
",money-->" + bigSeaInfo0.money);
}else{
App.showToast(DbFlowTransactionActivity.this,"数据为空");
}
}
})
.error(new Transaction.Error() {
@Override
public void onError(@NonNull Transaction transaction,
@NonNull Throwable error) {
Log.i("zhh_db_sync", "Sync-----error--" + error.getMessage());
}
}).success(new Transaction.Success() {
@Override
public void onSuccess(@NonNull Transaction transaction) {
Log.i("zhh_db_sync", "Sync-----success--" + transaction.name());
//更新Ui操作
}
}).execute();
//方式2:QueryResultListCallback:集合查询
SQLite.select().from(BigSeaInfo0.class)//.where(BigSeaInfo0_Table.name.is(""))
.async()//异步查询
.queryListResultCallback(new QueryTransaction.
QueryResultListCallback<BigSeaInfo0>() {
@Override
public void onListQueryResult(QueryTransaction transaction,
@NonNull List<BigSeaInfo0> tResult) {
printData(tResult);//更新UI
}
}).error(new Transaction.Error() {
@Override
public void onError(@NonNull Transaction transaction,
@NonNull Throwable error) {
Log.i("zhh_db_sync", "SyncList--error---" + error.getMessage());
}
}).success(new Transaction.Success() {
@Override
public void onSuccess(@NonNull Transaction transaction) {
Log.i("zhh_db_sync", "SyncList---success--" );
}
}).execute();
//方式3:QueryResultCallback:方式1+2的统一
SQLite.select().from(BigSeaInfo0.class).async().queryResultCallback(
new QueryTransaction.QueryResultCallback<BigSeaInfo0>() {
@Override
public void onQueryResult(@NonNull QueryTransaction<BigSeaInfo0> transaction,
@NonNull CursorResult<BigSeaInfo0> tResult) {
BigSeaInfo0 bigSeaInfo0 = tResult.toModel();
//这里可以是返回集合:tResult.toList()
Log.i("zhh_db_sync", "对象查询-----异步1-->id---" +
bigSeaInfo0.userId + ",name->" + bigSeaInfo0.name +
",age---" + bigSeaInfo0.age + ",note--" + bigSeaInfo0.remarks
+ ",money-->" + bigSeaInfo0.money);
tResult.close();//关闭资源
}
}).execute();
九、多module开发
只要在Module的build.gradle增加配置,不同的Module需要配置不同的名称。
apt {
arguments {
targetModuleName 'SomeUniqueModuleName'
}
}
clean + rebuild 后会生成对应的SomeUniqueModuleNameGeneratedDatabaseHolder类,初始化的时候添加进来即可
public void initialize(Context context) {
FlowManager.init(FlowConfig.builder(context)
.addDatabaseHolder(SomeUniqueModuleNameGeneratedDatabaseHolder.class)
.build());
}
参考文章
https://yumenokanata.gitbooks.io/dbflow-tutorials/content/chapter1.html
https://www.cnblogs.com/xxdh/p/9282504.html
https://www.jianshu.com/p/dba58d85f62f?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation