概观
现有SQL对象关系映射(ORM)库的一个问题是它们依靠Java反射来定义数据库模型,表格模式和列关系。 DBFlow是少数几个严格依赖注释处理来生成基于SQLiteOpenHelper框架的Java代码的人之一,它避免了这个问题。这种方法可以提高运行时性能,同时还可以避免您需要编写大量的样板代码,这些代码通常用于声明表,管理模式更改以及执行查询。
建立
以下部分介绍如何使用DBFlow v3进行设置。如果您是从旧版本的DBFlow进行升级,请阅读本迁移指南。其中一个主要变化是用于从注释生成Java代码的库现在依赖于JavaPoet。生成的数据库和表类现在使用’_’而不是’$’作为分隔符,在升级时可能需要对代码进行小的调整。
Gradle配置
由于DBFlow4尚未正式发布,因此您还需要将https://jitpack.io添加到您的allprojects- > repositories依赖项列表中:
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}
接下来,在您的内部app/build.gradle,将DBFlow添加到您的依赖列表中。我们创建一个单独的变量来存储版本号,以便以后更容易更改:
def dbflow_version = "4.0.3"
dependencies {
// annotationProcessor now supported in Android Gradle plugin 2.2
// See https://bitbucket.org/hvisser/android-apt/wiki/Migration
annotationProcessor "com.github.Raizlabs.DBFlow:dbflow-processor:${dbflow_version}"
implementation "com.github.Raizlabs.DBFlow:dbflow-core:${dbflow_version}"
implementation "com.github.Raizlabs.DBFlow:dbflow:${dbflow_version}"
// sql-cipher database encryption (optional)
// implementation "com.github.Raizlabs.DBFlow:dbflow-sqlcipher:${dbflow_version}"
}
注意:如果您正在升级,请确保删除任何以前的DBFlow引用。旧版本的注释处理器发生了重大变化。如果你java.lang.NoSuchMethodError: com.raizlabs.android.dbflow.annotation.Table.tableName()Ljava/lang/String;,那么你很可能仍然在你的Gradle配置中包含旧的注释处理器。
编译问题
如果您看到java.lang.IllegalArgumentException: expected type but was null错误,则可能是您使用的包名不全是小写。确保将您的软件包名称重命名为全部小写:
此外,所有的表名必须是上骆驼的情况下(即User,UserTable,MyUserTable如描述等)这个问题。如果你不使用惯例,你可能会触发这些问题。
DBFlow 4.0.0-beta1及以上版本已修复此问题。
禁用即时运行
由于DBFlow依赖于为表格文件生成代码,Android的即时运行功能通常会干扰创建正确的表格集。强烈建议您通过转到您的Android Studio首选项并在其中禁用它们来禁用此功能:
实例化DBFlow
接下来,我们需要DBFlow在自定义应用程序类中实例化。如果您没有Application对象,请MyApplication.java按如下所示创建一个对象:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// This instantiates DBFlow
FlowManager.init(new FlowConfig.Builder(this).build());
// add for verbose logging
// FlowLog.setMinimumLoggingLevel(FlowLog.Level.V);
}
}
修改您的AndroidManifest.xml文件以引用此应用程序对象的android:name属性:
<application
android:name=".MyApplication"
...>
<!-- ... -->
</application>
如果你跳过这一步,你可能会看到com.raizlabs.android.dbflow.structure.InvalidDBConfiguration: Model object: XXXX is not registered with a Database. Did you forget an annotation?。
创建数据库
创建一个MyDatabase.java文件并用@Database装饰器注释你的类来声明你的数据库。它应该包含用于创建表的名称以及版本号。 注意:如果您决定更改稍后创建的任何表的模式,则需要修改版本号。版本号应始终增加(并且从不降级)以避免与旧数据库版本冲突。
创建数据库
创建一个MyDatabase.java文件并用@Database装饰器注释你的类来声明你的数据库。它应该包含用于创建表的名称以及版本号。 注意:如果您决定更改稍后创建的任何表的模式,则需要修改版本号。版本号应始终增加(并且从不降级)以避免与旧数据库版本冲突。
定义你的表格
对于模型,Java模型对象需要从BaseModel类中进行扩展,如下所示Organization:
定义你的表格
对于模型,Java模型对象需要从BaseModel类中进行扩展,如下所示Organization:
定义你的表格
对于模型,Java模型对象需要从BaseModel类中进行扩展,如下所示Organization:
import com.raizlabs.android.dbflow.structure.BaseModel;
// **Note:** Your class must extend from BaseModel
@Table(database = MyDatabase.class)
public class Organization extends BaseModel {
@Column
@PrimaryKey
int id;
@Column
String name;
}
注:您必须至少定义一列作为主键。如果任何字段被标记为私有,您还需要定义getter和setter方法(即getId()和setId())。否则,DBFlow可能无法在编译时生成表。
另外,请确保您的表名称使用上层骆驼案例(即MyTableName)。否则,您可能会触发java.lang.IllegalArgumentException: expected type but was null问题。请参阅此票以了解更多详情。
定义外键
我们可以ForeignKey轻松定义关系。该saveForeignKeyModel表示是否更新外键如果条目也被更新。在这种情况下,我们禁用了这个功能:
@Table(database = MyDatabase.class)
public class User extends BaseModel {
@Column
@PrimaryKey
int id;
@Column
String name;
@Column
@ForeignKey(saveForeignKeyModel = false)
Organization organization;
public void setOrganization(Organization organization) {
this.organization = organization;
}
public void setName(String name) {
this.name = name;
}
}
请记住,所有模型都需要从 BaseModel包含外键的地方进行扩展,否则表格将无法正确生成,从而导致您无法成功编译代码。
您可以在本帮助指南和“ DBFlow关系指南”中阅读有关设置对象之间关系的更多信息,以查看一对一,一对多和多对多关系。
与Parceler库一起使用
如果您在Parceler库中使用DBFlow ,请确保使用@Parcel(analyze={}装饰器注释该类。否则,Parceler库将尝试序列化与BaseModel该类关联的字段并触发Error:Parceler: Unable to find read/write generator for type错误。为了避免这个问题,请向Parceler指定应该检查继承链中的哪个类(请参阅此讨论以获取更多详细信息):
插入行
我们可以简单地调用.save()带注释的类来将行保存到表中:
// Create organization
Organization organization = new Organization();
organization.setId(1);
organization.setName("CodePath");
organization.save();
// Create user
User user = new User();
user.setName("John Doe");
user.setOrganization(organization);
user.save();
查询行
我们可以通过以下方式查询表中的所有记录:
// Query all organizations
List<Organization> organizationList = SQLite.select().
from(Organization.class).queryList();
我们也可以做更复杂的查询:
// Set query conditions based on column
List<User> users = SQLite.select().
from(User.class).
where(User_Table.age.greaterThen(25)).
queryList();
// Query users based on organization
List<User> users = SQLite.select().
from(User.class).
where(Organization_Table.name.is("CodePath")).
queryList();
有关查询记录的更多示例,请参阅DBFlow检索指南和SQLLiteWrapperLanguage Guide。
更新行
这可以通过调用.save()一个对象来完成:
// Create user
User user = new User();
user.setName("John Doe");
user.setOrganization(organization);
user.save();
这将自动更新记录,如果它已经被保存并且有一个匹配的主键。
删除行
我们可以简单地调用.delete()相应的对象:
user.delete();
数据库事务
请参阅本指南了解如何执行交易。您可以User使用ProcessModelTransaction该类批量保存对象列表:
ArrayList<User> users = new ArrayList<>();
// fetch users from the network
// save rows
FlowManager.getDatabase(AppDatabase.class)
.beginTransactionAsync(new ProcessModelTransaction.Builder<>(
new ProcessModelTransaction.ProcessModel<User>() {
@Override
public void processModel(User user) {
// do work here -- i.e. user.delete() or user.update()
user.save();
}
}).addAll(users).build()) // add elements (can also handle multiple)
.error(new Transaction.Error() {
@Override
public void onError(Transaction transaction, Throwable error) {
}
})
.success(new Transaction.Success() {
@Override
public void onSuccess(Transaction transaction) {
}
}).build().execute();
公开内容提供者
使用DBFlow的一个优点是您可以像Android 内容提供商一样轻松地公开表格,这样Android 内容提供商就可以让其他应用查询这些数据。
第一步是将这些内容提供者声明在声明数据库的相同位置,以帮助集中所有声明。我们还需要公开一个URL以供其他应用程序查询,这将content://com.codepath.myappname.provider在此示例中声明。
@ContentProvider(authority = MyDatabase.AUTHORITY,
database = MyDatabase.class,
baseContentUri = MyDatabase.BASE_CONTENT_URI)
@Database(name = MyDatabase.NAME, version = MyDatabase.VERSION)
public class MyDatabase {
public static final String NAME = "MyDatabase";
public static final int VERSION = 1;
public static final String AUTHORITY = "com.codepath.myappname.provider";
public static final String BASE_CONTENT_URI = "content://";
private static Uri buildUri(String... paths) {
Uri.Builder builder = Uri.parse(AppDatabase.BASE_CONTENT_URI + AppDatabase.AUTHORITY).buildUpon();
for (String path : paths) {
builder.appendPath(path);
}
return builder.build();
}
}
接下来,在这个相同的类中,我们将声明一个可声明的User端点(即content://com.codepath.myappname.provider/User):
public class MyDatabase {
// ...
// Declare endpoints here
@TableEndpoint(name = UserProviderModel.ENDPOINT, contentProvider = MyDatabase.class)
public static class UserProviderModel {
public static final String ENDPOINT = "User";
@ContentUri(path = UserProviderModel.ENDPOINT,
type = ContentUri.ContentType.VND_MULTIPLE + ENDPOINT)
public static final Uri CONTENT_URI = buildUri(ENDPOINT);
}
}
添加到清单文件
内容提供者的最后一步是暴露。如果您希望其他应用程序能够查看此数据,请设置android:exported为true。否则,如果您只希望现有应用程序查询此内容提供者,请将该值设置为false。请注意,DBFlow3使用underscore(_)而不是美元符号($)作为分隔符:
<provider
android:authorities="com.codepath.myapp.provider"
android:exported="true|false"
android:name=".provider.MyDatabase_Provider"/>
常见问题
问题:如何检查存储在设备上的SQLite数据?
为了检查持久数据,我们需要使用adb来查询或下载数据。您还可以看看使用Stetho库,该库提供了一种使用Chrome来检查本地数据的方法。
问题:DBFlow如何处理重复的ID?例如,我想确保没有插入重复的Twitter ID。有没有一种方法可以指定列是模型中的主键?
简单注释帖子ID列作为@PrimaryKey注释:
@Table(database = MyDatabase.class)
public class SampleModel extends BaseModel {
@PrimaryKey
@Column
private int remoteId;
// ... set the remote id based on the json response
}
确保在模拟器上卸载应用程序以确保架构更改生效。请注意,您可能需要手动确保您不会尝试通过验证它们不在数据库中来重新创建现有对象,如下所示。
问题:你如何指定数据类型(int,text)?DBFlow会自动知道列类型应该是什么?
该类型是根据字段的类型自动推断的。
问题:如何将日期存储到DBFlow中?
DBFlow支持自动序列化日期字段。它以毫秒为单位在内部存储为时间戳(INTEGER)。
@Column
private Date timestamp;
并且日期将被序列化为SQLite。您可以使用SimpleDateFormat将字符串解析为Date对象:
public void setDateFromString(String date) {
SimpleDateFormat sf = new SimpleDateFormat("EEE MMM dd HH:mm:ss ZZZZZ yyyy");
sf.setLenient(true);
this.timestamp = sf.parse(date);
}
接着:
public static List<Model> findRecent(Date newerThan) {
return new Select().from(Model.class).where("timestamp > ?", newerThan.getTimeInMillis()).execute();
}
问题:你如何表现1-1关系?
如果你还没有,请查看关系部分。您需要使用@ForeignKey和@Column注释来注释该字段:
public class User extends BaseModel {
public String email;
@Column
@ForeignKey
public Address address;
}
您可以通过简单地构建用户对象并将其分配给父对象的字段并调用父对象的保存来管理此过程。
User u = new User("[email protected]");
u.address = new Address("135 Hesby St, Los Angeles, CA");
u.address.save();
u.save();
在大多数情况下,您应该确保在关联的对象上单独调用保存。一旦这些都被保存,那么您可以通过以下方式访问所有用户:
List<User> usersList = SQLite.select().from(User.class).queryList();
然后轻松访问任何用户的地址:
User user = usersList.get(0);
Address address = user.address(); // <-- Gets us the address
您可以在本帮助指南和“ DBFlow关系指南”中阅读有关设置对象之间关系的更多信息,以查看一对一,一对多和多对多关系。
问题:如何从表中删除所有记录?
请参阅DBFlow的这一部分。您可以使用该.delete方法删除单个记录,并且可以删除符合特定条件的所有记录:
// Delete a whole table
Delete.table(MyTable.class);
// Delete multiple instantly
Delete.tables(MyTable1.class, MyTable2.class);
// Delete using query
SQLite.delete(MyTable.class)
.where(DeviceObject_Table.carrier.is("T-MOBILE"))
.and(DeviceObject_Table.device.is("Samsung-Galaxy-S5"))
.async()
.execute();
这允许批量删除。
问题:可以使用DBFlow进行连接吗?
连接使用DBFlow在[Select class] https://agrosner.gitbooks.io/dbflow/content/SQLiteWrapperLanguage.html#joins中提供的查询语言完成。你可以在这里阅读更多SQLiteWrapperLanguage文档
问题:在Android中与sqlite交互的最佳实践是ORM / DAO吗?
开发人员同时使用SQLiteOpenHelper和几个不同的ORM。在ORM崩溃或不必要的情况下,通常使用SQLiteOpenHelper。由于模型通常都会形成,尽管在很多情况下Android上的持久化可以非常接近地映射对象,但像ActiveAndroid这样的ORM对于简单的数据库映射尤其有用。
故障排除
由于DBFlow需要进行批注处理,因此有时您可能需要单击Build- > New Project重新生成生成的源代码。
ProGuard问题
如果您在ProGuard中使用DBFlow ,并Table is not registered with a Database. Did you forget the @Table annotation?确保将这一行包含在您的ProGuard配置中:
-keep class * extends com.raizlabs.android.dbflow.config.DatabaseHolder { *; }
你可以进入你的网站app/build/intermediate/classes/com/raizlabs/android/dbflow/config并寻找GeneratedDatabaseHolder.class理解代码生成的内容。
数据库架构
您还可以使用ADB下载并检查本地数据库:
adb pull /data/data/com.codepath.yourappname/databases/MyDatabase*.db
sqlite3 MyDatabase.db
键入.schema以查看表定义的创建方式:
sqlite> .schema
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE `Organization`(`id` INTEGER,`name` TEXT, PRIMARY KEY(`id`));
CREATE TABLE `User`(`id` INTEGER,`name` TEXT,`organization_id` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`organization_id`) REFERENCES `Organization`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION);
Gson库一起使用
如果您打算在Gson库中使用DBFlow ,那么StackOverflowError在尝试使用扩展名为Java的对象时可能会遇到异常BaseModel。为了避免这些问题,您需要排除ModelAdapter该类,它是BaseModel中包含的字段:
public class DBFlowExclusionStrategy implements ExclusionStrategy {
// Otherwise, Gson will go through base classes of DBFlow models
// and hang forever.
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaredClass().equals(ModelAdapter.class);
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
然后你需要创建一个自定义的Gson构建器来排除这个类:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setExclusionStrategies(new ExclusionStrategy[]{new DBFlowExclusionStrategy()});
请参阅此问题以获取更多信息。
添加日志
您可以通过在应用程序中设置此行来监视正在执行的SQL查询:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
FlowLog.setMinimumLoggingLevel(FlowLog.Level.V); // set to verbose logging
}
看到==========错误
Gradle java.lang.Thread.getStackTrace和ProcessorManager.logError错误跨越多行,但Android设备监视器可能没有适当地显示它们。出于这个原因,点击右下角的Gradle控制台可以更容易地查看错误消息的更多细节:
有几件事需要验证:
检查你是否使用私人领域。如果是这样,你需要手动定义这些字段的getter和setter。否则你可能会注意到class com.raizlabs.android.dbflow.processor.validator.ColumnValidator: Could not find setter for private element: “fieldName” from table class: MyTableName“
检查是否至少有一个@PrimaryKey与列关联。
检查编译错误部分并尝试升级到”4.0.0-beta1”。
参考
https://github.com/Raizlabs/DBFlow#usage-docs
https://agrosner.gitbooks.io/dbflow/content/SQLiteWrapperLanguage.html