Android数据库ORMlite框架

前言

由于第二章是整个文档的核心,内容也很多,所以分次翻译。下一章的内容会继续本章接着翻译。

-------------------------------------------------------------------------------------

 

2 如何使用

这一章进入到更多详细地使用ORMLite的各种功能。

 

2.1 配置你的Class

为了配置你的class使其持久化,你需要做下面几步:

①添加@DatabaseTable 注解到你每个需要持久化的类的顶部。你也可以用@Entity

②添加@DatabaseField 注解到你需要持久化的字段的上面。你也可以使用@Column和其他的。

③为每个class添加一个无参的构造器,并且构造器在包内是可见的。

 

2.1.1 添加ORMLite 注解

自从java5开始,注解是有效的特殊代码标识,它提供meta信息包括类、方法或成员变量。把指定的类和成员变量存入数据库,ORMLite既支持它自己的注解(@DatabaseTable和 @DatabaseField)也支持很多来自javax.persistence包中标准的注解。注解是配置你的class最简单的方式,当然你也可以使用java代码或者Spring xmlSpring是个框架,更多信息google一下)对class进行配置。

ORMLite注解,对每个你想要持久化到SQL数据库的java类你都需要添加@DatabaseTable注解到public class 这一行的正上方。每个被这些注解标记过的class都将持久化到它自己的数据库表中。例如:

@DatabaseTable(tableName = "accounts")

public class Account {

...

 

@DatabaseTable注解可以有个可选的tableName 参数,也就是这个类对于的表名。如果没有特别指出,那么这个类名将默认作为表名。使用示例中的Account的对象都将会作为一条记录持久化到数据库名为account的表中。这将会在DaoManager实例化内部Dao的时候使用到。

除此之外,对于每个class你需要添加@DatabaseField注解到class的成员变量上,这个类是需要持久化到数据库的。每个成员变量都将被作为数据库中记录的一个字段进行持久化。示例:

 

复制代码
@DatabaseTable(tableName = "accounts")

public class Account {

@DatabaseField(id = true)

private String name;

@DatabaseField(canBeNull = false)

private String password;

...
复制代码

 

name字段,它是一个字符串并且是数据库中记录的唯一标识(主键)。在上面的示例中,account表的每条记录都有两个字段:

password字段,它也是一个字符串,它不能为null

@DatabaseField注解可以用下面的一些成员:(对常用的字段进行翻译,其他的参考原文)

常用的注解

成员名

数据类型

描述

columnName

String

数据库的列名。如果你没有设置这个成员名,会用标准的形式代替它。

dataType

 

字段的数据类型。通常情况下,数据类型是从java类的成员变量获取的,并不需要进行特殊指出。它相当于是SQL的数据类型。

defaultValue

String

当我们在表中创建新的记录时的一个字段的默认值。默认情况下是没有这个值的。

width

Integer

字段的宽度,主要用于字符串字段。默认是0,意味着采用默认的数据类型和具体的数据库的默认情况。对于字符串以为在255个字符即使有些数据库并不支持。

canBeNull

Boolean

字段是否能被分配null值。默认是true。如果你设置成false,那么你每次在数据库中插入数据是都必须为这个字段提供值。

id

Boolean

这个字段是否是id,默认是false。在一个class中只有一个成变量可以有这个值。id字段是一条记录的唯一标识而且是必需的,只有generatedId和 generatedIdSequence其中之一。

generatedId

Boolean

字段是否自动增加。默认为false。类中的一个成员变量设置了这个值,它告诉数据库每添加一条新记录都自动增加id。当一个有generatedid的对象被创建时使用Dao.create()方法,数据库将为记录生成一个id,它会被返回并且被create方法设置进对象。

generatedIdSequence

String

序列编号的名字,这个值在生成的时候会被使用。和generatedId相似,但是你能够指定使用的序列名称。默认是没有的。一个class中只有一个成员变量可以设置这个值。这仅仅在数据库需要序列生成id时才需要它。如果你选择使用generatedId代替它,那么代码将自动增加序列名。

 

其他注解

foreign

throwIfNull

useGetSet

persisted

unknownEnumName

format

uniqueIndexName

allowGeneratedIdInsert

foreignAutoRefresh

columnDefinition

unique

uniqueIndex

uniqueCombo

indexName

index

uniqueIndexName

version

maxForeignAutoRefreshLevel

foreignColumnName

foreignAutoCreate

 

2.1.2 使用javax.persistence 注解

取代使用ORMLite注解,你可以使用来自javax.persistence包的更多的标准JPA注解。取代@DatabaseTable注解,你可以使用javax.persistence @Entity注解。示例:

@Entity(name = "accounts")

public class Account {

...

 

@Entity注解有个可选的name参数,它用于指定表名。如果没有指定,类名将是默认的表名。

在每个成员变量中取代使用@DatabaseField注解,你可以用javax.persistence注解: @Column, @Id, @GeneratedValue, @OneToOne,@ManyToOne, @JoinColumn, and @Version. 示例:


下面这些javax.persistence注解和字段都支持:

注解

注解属性

描述

@Entity

name

用于关联的数据库表的名字。如果没有设置那么类名将被作为表名。

@Column

name

用作表字段的名字。如果没有设置那么变量名将作为字段名。

length

数据库表字段的长度。可能只有应用在字符串并且只被某些数据库类型支持。默认是255

nullable

设置成true,那么这个字段允许插入null值。

unique

 添加一个约束,它在表中必须是唯一的。

@GeneratedValue

 

用于定义一个自动增长的id值,它只能用于添加到@Id注解。

 

@OneToOne 

 

成员变量使用这些注解后会被认为是外键字段。 ORMLite没有实现多个或一个关系,它也不能使用任何注解成员。它只能使用这些注解的两者之一来表明它是一个外键。

@ManyToOne

 

@JoinColumn

name

设置成员变量的列名(字段名)。

nullable

设置成true,那么这个字段允许插入null值。

@Version

 

使用它将会把short, integer, long,  Date这些类型的成员转化成版本成员。

如果@Column注解在成员变量上用了一个未知的类型,那么它将被认为是序列化类型字段并且这个对象需要实现java.io.Serializable

 

2.1.3 添加无参构造器

在你给class添加了注解字段后,你也需要添加一个无参的包内可见的构造器。当一个对象在查询中被返回时,ORMLite使用java反射机制构造一个对象并且构造器需要被调用。所以你最终的示例拥有注解和构造器的Account类应该像这样:

 

复制代码
@DatabaseTable(tableName = "accounts")

public class Account {

@DatabaseField(id = true)

private String name;

@DatabaseField(canBeNull = false)

private String password;

...

Account() {

// all persisted classes must define a no-arg constructor

// with at least package visibility

}

...

}
复制代码

 

2.2 持久化数据类型 

下面这些java类型能够被ORMLite持久化到数据库。数据库具体编码帮助SQL类型和数据库具体持有类型的相互转化。

(具体的类型相互转化在此就不作介绍了,参见原文)。

 

2.3 连接源

注意:关于连接源,android用户应该参见手册中Android详细文档。

为了使用数据库和DAO对象,你需要配置JDBC调用数据源和ORMLite调用连接源。连接源是连接物理SQL数据库的一个工厂。这里是创建简单、单连接的代码示例:

// single connection source example for a database URI

ConnectionSource connectionSource =

new JdbcConnectionSource("jdbc:h2:mem:account");

 

这包中也包括了类JdbcPooledConnectionSource ,它是一个相对简单的连接池的实现。一个数据库连接已经释放而成为关闭,之后向他们添加内部列表他们都会拒绝。只有在没有休眠的可用的连接时,新的连接才需要创建。JdbcPooledConnectionSource也是同步的并且可以用于多线程中。在连接关闭前它可以设置空闲连接的最大数和存活的最长时间。

复制代码
// pooled connection source

JdbcPooledConnectionSource connectionSource =

new JdbcPooledConnectionSource("jdbc:h2:mem:account");

// only keep the connections open for 5 minutes

connectionSource.setMaxConnectionAgeMillis(5 * 60 * 1000);
复制代码

 


JdbcPooledConnectionSource也有一个一直存活的线程,它偶尔ping一下池中空闲的每个连接,目的是为了确认他们是有效的,关闭的那个就不再有效。在你从池中取得连接之前你也可以测试连接是否有效。

复制代码
// change the check-every milliseconds from 30 seconds to 60

connectionSource.setCheckConnectionsEveryMillis(60 * 1000);

// for extra protection, enable the testing of connections

// right before they are handed to the user

connectionSource.setTestBeforeGet(true);
复制代码

 


有很多其他额外的数据,他们能够被使用,包括很多更强大甚至高性能的池连接管理器。你可以用你自己直接封装的DataSourceConnectionSource类来例举说明。

复制代码
// basic Apache data source

BasicDataSource dataSource = new BasicDataSource();

String databaseUrl = "jdbc:h2:mem:account";

dataSource.setUrl(databaseUrl);

// we wrap it in the DataSourceConnectionSource

ConnectionSource connectionSource =

new DataSourceConnectionSource(dataSource, databaseUrl);
复制代码

 


当你用ConnectionSource时,你想调用close()方法来关闭一些底层的连接。推荐像下面这样的模式。

复制代码
JdbcConnectionSource connectionSource =

new JdbcPooledConnectionSource("jdbc:h2:mem:account");

try {

// work with the data-source and DAOs

...

} finally {

connectionSource.close();

}
复制代码

 


很不幸,DataSource接口没有关闭方法,所以你使用DataSourceConnectionSource你必须关闭底层数据源,通过操作DataSourceConnectionSource上的close()方法。

 

2.4 配置DAOs

一旦在你的类中有注解并且定义了你的ConnectionSource,你就需要创建一个DAOData Access Object),它是一个拥有数据库操作句柄的单一持久化类。每个DAO都有两个泛型参数:①我们用DAO持久化的类,②id字段,它用于确定数据库具体的记录。如果你的类没有ID字段,你可以放入Object或者Void作为第二个参数。例如,在上面的Account类,成员变量nameID字段,所以ID类是String

创建DAO最简单的方式是使用DaoManager类的静态方法createDao。示例:

复制代码
Dao<Account, String> accountDao =

DaoManager.createDao(connectionSource, Account.class);

Dao<Order, Integer> orderDao =

DaoManager.createDao(connectionSource, Order.class);
复制代码

 


注意:你需要使用DaoManager.createDao()方法创建你自己的DAO类,所以如果内置ORMLite功能是需要他们,他们可以被再次利用并且不能再次生成。创建DAO会有昂贵的操作代价并且很多设备有资源限制(比如移动设备应用),尽可能重复使用DAO 

如果你想更好的类层次的机构或者你需要添加附加的方法套你的DAOs中,你应该考虑定义一个接口,它继承自Dao接口。这个接口不是必需的,但是他说一种好的模式,这样你的代码在关联JDBC持久化的时候会更少。接下来是一个相当于本手册前面章节Account类的DAO接口的示例:

复制代码
/** Account DAO which has a String id (Account.name) */

public interface AccountDao extends Dao<Account, String> {

// empty wrapper, you can add additional DAO methods here

}
复制代码

 

然后在实现中,你需要扩展BaseDaoImpl基类。这里是个实现你的DAO接口的示例。

复制代码
/** JDBC implementation of the AccountDao interface. */

public class AccountDaoImpl extends BaseDaoImpl<Account, String>

implements AccountDao {

public AccountDaoImpl(ConnectionSource connectionSource)

throws SQLException {

super(connectionSource, Account.class);

}

}
复制代码

 


那就是你需要定义你的DAO类。如果有特殊的操作需要并且Dao基类没有提供的方法,你可以自由添加更多方法到你的DAO接口和添加到你的实现中。

注意:如果你正在使用一个定制的DAO,然后确保添加daoClass参数到你自己定制的DAO类的@DatabaseTable注解。这会被DaoManager用于内部实例化DAO

 

2.5 支持的数据库

ORMLite支持下面的数据库。这些数据库中的某些数据库有具体需要遵守的文档。(下面给出支持的数据库,具体文档参见官方文档)

支持的数据库

MySQL

H2

Android SQLite

HSQLDB

Netezza

DB2

Postgres

SQLite

Microsoft SQL Server

Derby

ODBC

Oracle

 

2.6 整合

这样你有一个注解对象被持久化,添加一个无参构造器,创建你的ConnectionSource并且定义你的DAO类。你已经开始持久化和查询你的数据库对象了。你需要下载并且添加H2 jar文件到你的classPath中,如果你想让这个示例跑起来的话。下面是整合的代码:

复制代码
// h2 by default but change to match your database

String databaseUrl = "jdbc:h2:mem:account";

JdbcConnectionSource connectionSource =

new JdbcConnectionSource(databaseUrl);

// instantiate the dao with the connection source

AccountDaoImpl accountDao = new AccountDaoImpl(connectionSource);

// if you need to create the 'accounts' table make this call

TableUtils.createTable(connectionSource, Account.class);

// create an instance of Account

Account account = new Account("Jim Coakley");

// persist the account object to the database

accountDao.create(account);

...

// destroy the data source which should close underlying connections

connectionSource.destroy();
复制代码

2.7 表和Schema创建

有几个ORMLite提供的工具,可以帮助你为存入数据库的类创建表和schema

 

2.7.1 TableUtils

TableUtils类提供了一些静态方法用以辅助创建和删除表,同样也提供了schema申明。(下面例举出静态方法名,详细说明参见官网)

静态方法原型

createTable(ConnectionSource, Class)

createTableIfNotExists(ConnectionSource, Class)

createTable(ConnectionSource, DatabaseTableConfig)

createTableIfNotExists(ConnectionSource, DatabaseTableConfig)

dropTable(ConnectionSource, Class, boolean ignoreErrors)

dropTable(ConnectionSource, DatabaseTableConfig, boolean ignoreErrors)

getCreateTableStatements(ConnectionSource, Class)

getCreateTableStatements(ConnectionSource, DatabaseTableConfig)

clearTable(ConnectionSource, Class)

clearTable(ConnectionSource, DatabaseTableConfig)

 

2.7.2 TableCreator

TableCreator这个类虽然是为使用Spring框架设计的,但是在其他的配置方面是很有用的。它配置ConnectionSource和被程序使用的DAO列表。

如果系统属性ormlite.auto.create.tables设置成true值,他会自动创建和这些DAO相关的表。如果系统属性ormlite.auto.drop.tables设置成true值,它也会自动删除创建的表。这在测试的时候特别有用:你开始使用取得最新schema的测试数据库,但是实际生产过程中你需要手动改变一个具体的schame。你可以在你运行测试脚本的时候设置系统属性,但是在运行实际脚本时要关闭它。

复制代码
List<Dao<?, ?>> daoList = new ArrayList<Dao<?, ?>>();

daoList.add(accountDao);

...

TableCreator creator =

new TableCreator(connectionSource, daoList);

// create the tables if the right system property is set

creator.maybeCreateTables();

...

// later, we may want to drop the tables that were created

creator.maybeDropTables();
复制代码

 

 2.8 唯一标识符字段

数据库中的记录通过定义为唯一的特殊字段成为唯一标识。记录不是必须有唯一标识字段当时很多DAO操作(更新、删除、刷新)都需要一个唯一标识字段。这个标识要么用户提供要么数据库自动生成。标识字段有表中唯一的值并且如果你用DAO根据id查询、删除、刷新或者更新指定行的时候他们必须存在。为了配置一个成员变量作为标识成员,你应该使用下面三个设置之一(而且必须使用一个):@DatabaseField: id, generatedId, generatedIdSequence 

 

2.8.1 成员变量使用id

用我们的Account类的示例,字符串name变量被标记有id = true 。这意味着name变量是这个对象的标识字段。每个account存入数据库都必须有一个唯一的name变量值,比如你不能有两行的name都是“John Smith”。

复制代码
public class Account {

@DatabaseField(id = true)

private String name;

...

}
复制代码

 

当你使用DAO利用具体的name值查询时,你将使用标识成员定位数据库中

Account对象。

复制代码
Account account = accountDao.queryForId("John Smith");

if (account == null) {

// the name "John Smith" does not match any rows

}
复制代码

 注意:如果你需要改变对象id字段的值,那么你必须使用Dao.updateId()方法,它获得当前对象的id旧值和新值。

 

2.8.2 成员变量使用generatedId

你可以配置一个longinteger的变量作为生成的标识字段。每条记录的id号都是数据库自动生成的。

复制代码
public class Order {

@DatabaseField(generatedId = true)

private int id;

...

}
复制代码

 

传递一个Order对象去创建和存储到数据库时,数据库返回一个生成的id值并且ORMLite设置给对象。在大部分数据库类型中向表中插入一条新记录时生成的值从1开始,每次增长1

复制代码
// build our order object without an id

Order order = new Order("Jim Sanders", 12.34);

...

orderDao.create(order);

System.out.println("Order id " + order.getId() +

" was persisted to the database");

// query for the order with an id of 1372

order = orderDao.queryForId(1372);

if (order == null) {

// none of the order rows have an id of 1372

}
复制代码

 

在上面的代码示例中,一个order构造用nameamount两个属性。当把它传给DAO'create方法时,id变量没有设置。它保存到数据库之后,ORMLite会把生成的id设置给id变量并且getId()方法在create方法返回后被order调用是有有效的。

注意:其他特殊变量类型也是可以生成的,比如UUID。你可以使用allowGeneratedIdInsert变量进行设置,允许向表中插入拥有已经设置过或没有设置过id的对象。根据数据库类型,你可能不能改变自动生成id字段的值。

 

2.8.3 成员变量使用generatedIdSequence

一些数据库使用一种被称为序列号生成器的东西来提供生成id的值。如果你把generatedId = true用在这些数据库上,序列名将会被ORMLite自动生成。如果这样,你需要设置序列名来匹配已经存在的schema,你可以使用generatedIdSequence序列名的值。

复制代码
public class Order {

@DatabaseField(generatedIdSequence = "order_id_seq")

private int id;

...

}
复制代码

 

在上面的示例中,虽然id值再次自动生成,但是仍然使用序列名:order_id_seq 。如果你使用的数据库不支持序列,那么这将会抛出一个异常。

注意:根据数据库类型,你不能改变自动生成id字段的值。

 

2.9 DAO 的使用

下面通过使用DAO方法简单完成数据库操作:

①创建并且持久化对象到数据库。

插入一条和对象相关的记录到数据库中。

复制代码
Account account = new Account();

account.name = "Jim Coakley";

accountDao.create(account);

 
复制代码

 

②查询它的id字段

如果对象有个id成员变量通过注解定义的,我们可以通过它的id在数据库中查找一个对象。

复制代码
Account account = accountDao.queryForId(name);

if (account == null) {

account not found handling ...

}
复制代码

 

③更新与对象相关的数据库记录

如果你在内存中改变一个对象的成员变量,你必须调用update把它持久化到数据库。这需要一个id字段。

account.password = "_secret";

accountDao.update(account);

 

④当数据库有改变,刷新对象

如果一些与内存中对象相关的数据库实体发生了改变,你就需要刷新来得到最新的存储对象。这需要一个id字段。

accountDao.refresh(account);

 

⑤从数据库中删除数据

从数据库删除与对象关联的记录。一旦对象从数据库删除,你可以继续使用内存中的对象但是任何的更新或者刷新都很可能失败。这需要一个id字段。

accountDao.delete(account);

 

⑥遍历表中所有记录

DAO也有迭代器,所以你可以简单的执行数据库中所有的记录。

复制代码
// page through all of the accounts in the database

for (Account account : accountDao) {

System.out.println(account.getName());

}
复制代码

 注意:你必须遍历迭代器所有的条目来关闭底层的SQL对象。如果你没有通过循环走所有的途径,那么ORMLite不知道关闭底层对象,并且一个到数据库的连接可能泄露,如果更迟一些垃圾回收器才获得它,那么它将被迫关闭,这会在你的代码中产出漏洞。使用下面的try ... finally模板包住迭代器。

例如,下面是一个不好的循环模板。

复制代码
for (Account account : accountDao) {

if (account.getName().equals("Bob Smith")) {

// you can't return, break, or throw from here

return account;

}

}
复制代码

 如果一个异常仍出循环这种bug照样会发生,所以如果这样的话循环就不应该被使用。这也是一个用迟加载收集的一个案例。

 

⑦直接使用迭代器

你也可以直接使用迭代器,因为用循环并不是最佳选择。这种方式允许你使用更好的try ... finally模板。

复制代码
CloseableIterator<Account> iterator =

accountDao.closeableIterator();

try {

while (iterator.hasNext()) {

Account account = iterator.next();

System.out.println(account.getName());

}

} finally {

// close it at the end to close underlying SQL statement

iterator.close();

}
复制代码

 

⑧获得"wrapped iterable"

你也可以使用"wrapped iterable",它允许你在finally中使用close而一直使用循环。

复制代码
CloseableWrappedIterable<Account> wrappedIterable =

accountDao.getWrappedIterable();

try {

for (Account account : wrappedIterable) {

...

}

} finally {

wrappedIterable.close();

}
复制代码

2.10 索引成员

在你的数据类中ORMLite提供了一些多种成员索引有限的支持。首先,它重点指明任何已经被标记成id的成员变量已经被编入索引。一个id成员变量不需要添加额外构建的索引并且如果他们被指定的话那么数据库会产生错误。

添加一个索引到没有id的成员变量,你需要添加index = true布尔域到@DatabaseField注解。这将会在表被创建时创建一个非唯一的索引给成员变量并且如果表被删除那么将删除索引。索引用于帮助优化查询并且在查询媒介中数据量大的表时显著优化了查询时间。

复制代码
public class Account {

@DatabaseField(id = true)

private String name;

// this indexes the city field so queries on city

// will go faster for large tables

@DatabaseField(index = true)

private String city;

...

}
复制代码

 

这个例子在Account表中创建一个account_city_idx索引。如果你想用不同的名字,你可以使用indexName = "othername",用允许你指定的索引名来替换othername成员。

 

复制代码
@DatabaseField(indexName = "account_citystate_idx")

private String city;

@DatabaseField(indexName = "account_citystate_idx")

private String state;
复制代码

 

这个示例会为citystate成员变量都创建一个索引。注意,通过city本身查询是没有优化的,只有在citystate多关键字查询时才会被优化。有些数据库,它可能更好的创建一个单一字段索引在每个字段上而且如果你用citystate多关键字查询时它会让数据库同时使用两个索引。对于另一些数据库,推荐在多个成员变量上创建一个索引。你可能需要尝试使用SQL EXPLAIN命令来查明你的数据库是怎么使用你的索引的。

创建一个唯一的索引,uniqueIndex = trueuniqueIndexName ="othername"成员变量仅仅在@DatabaseField注解中有效。这些操作和上面的设置一样但是将会不用创建唯一索引来确保没有任何两条记录的索引有相同的值。

 

2.11 发出原生SQL语句

在大量实例中,使用DAO定义的功能操作数据库可能还不够。由于这个原因,ORMLite允许你发出查找、更新、执行等数据库原生语句给数据库。

 

2.11.1 发出原生查找

通过Dao接口的内置方法并且QueryBuilder类没有提供操作所有查询类型的能力。比如,聚合查询(sum,count,avg等等)不能当做一个对象进行操作,因为每个查询有不同的结果列表。为了这样的查询操作,你可以使用DAO中的queryRaw方法发出原生的数据库查询。这些方法返回一个GenericRawResults对象,它表示一个结果是一个字符串数组,对象数组或者用户映射对象。查看关于GenericRawResults的文档有更多如何使用它的详解说明,或者看看下面的示例。

复制代码
// find out how many orders account-id #10 has

GenericRawResults<String[]> rawResults =

orderDao.queryRaw(

"select count(*) from orders where account_id = 10");

// there should be 1 result

List<String[]> results = rawResults.getResults();

// the results array should have 1 value

String[] resultArray = results.get(0);

// this should print the number of orders that have this account-id

System.out.println("Account-id 10 has " + resultArray[0] + " orders");
复制代码

 

你甚至可以使用QueryBuilder构建原生的查询,如果你喜欢使用prepareStatementString()方法的话。

QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();

qb.where().ge("orderCount", 10);

results = accountDao.queryRaw(qb.prepareStatementString());

 

如果你想以参数的形式使用QueryBuilder原生查询,那么你应该像这样的:

复制代码
QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();

// we specify a SelectArg here to generate a ? in statement string below

qb.where().ge("orderCount", new SelectArg());

// the 10 at the end is an optional argument to fulfill SelectArg above

results = accountDao.queryRaw(qb.prepareStatementString(), 10);
复制代码

 

如果你想以聚合的方式使用QueryBuilder或者是其他原生、自定义的参数那么像下面这样做。因为只有一个结果输出你可以使用genericRawResults.getFirstResult()方法:

复制代码
QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();

// select 2 aggregate functions as the return

qb.selectRaw("MIN(orderCount)", "MAX(orderCount)");

// the results will contain 2 string values for the min and max

results = accountDao.queryRaw(qb.prepareStatementString());

String[] values = results.getFirstResult();
复制代码

 

对于有大量的结果集,你可以考虑使用利用数据库分页的GenericRawResults对象的the iterator()方法。示例:

复制代码
// return the orders with the sum of their amounts per account

GenericRawResults<String[]> rawResults =

orderDao.queryRaw(

"select account_id,sum(amount) from orders group by account_id");

// page through the results

for (String[] resultArray : rawResults) {

System.out.println("Account-id " + resultArray[0] + " has "

+ resultArray[1] + " total orders");

}

rawResults.close();
复制代码

 

如果你传进去的结果字段类型有些字段不能合适的映射到字符串,你也可以以Object[]形式返回字段。例如:

复制代码
// return the orders with the sum of their amounts per account

GenericRawResults<Object[]> rawResults =

orderDao.queryRaw(

"select account_id,sum(amount) from orders group by account_id",

new DataType[] { DataType.LONG, DataType.INTEGER });

// page through the results

for (Object[] resultArray : rawResults) {

System.out.println("Account-id " + resultArray[0] + " has "

+ resultArray[1] + " total orders");

}

rawResults.close();
复制代码

 

注意:select * 能返回在不同的orders表中的字段,这依赖于数据库类型。

为了保证数组数据类型和返回的列匹配,你必须具体地指定字段并且不能用SQL中的 

你也可以通过在RawRowMapper对象传一个你自己的对象来映射结果集。这将调用对象和一个字符串数组的映射并且它把字符串转化为对象。例如:

复制代码
// return the orders with the sum of their amounts per account

GenericRawResults<Foo> rawResults =

orderDao.queryRaw(

"select account_id,sum(amount) from orders group by account_id",

new RawRowMapper<Foo>() {

public Foo mapRow(String[] columnNames,

String[] resultColumns) {

return new Foo(Long.parseLong(resultColumns[0]),

Integer.parseInt(resultColumns[1]));

}

});

// page through the results

for (Foo foo : rawResults) {

System.out.println("Account-id " + foo.accountId + " has "

+ foo.totalOrders + " total orders");

}

rawResults.close();
复制代码

 

注意:查询和结果字符串可以是非常具体的数据库类型。比如:

1、某一数据库需要一些字段名指定成大写,另一些需要指定成小写。

2、你必须引用你的字段名或表明,如果他们是关键字的话。

3、结果集字段名也可以是大写或者是小写。

4、Select * 可以根据数据库类型返回orders表中不同的字段。

注意:就像其他的ORMLite迭代器,你将需要确定循环遍历所以结果集后有自动关闭的申明。你也可以调用GenericRawResults.close()方法来确保迭代器和其他相关数据库连接被关闭。

 

2.11.2 发出原生更新语句

如果DAO给你的功能不够灵活的话,你也可以发出数据的原生更新语句。更新的SQL语句必须包含关键字INSERT,DELETE、 UPDATE。例如:

fooDao.updateRaw("INSERT INTO accountlog (account_id, total) "

+ "VALUES ((SELECT account_id,sum(amount) FROM accounts))

 

 

2.11.3 发出原生的执行语句

如果DAO给你的功能不够灵活的话,你也可以发出数据的原生更新语句。例如:

fooDao.executeRaw("ALTER TABLE accountlog DROP COLUMN partner");

 

 

2.12 外部对象字段

ORMLite支持"foreign"对象的概念,一个或多个与对象相关的字段被持久化到同一数据库的另一张表中。比如,如果在你的数据库中有一个order对象, 并且每个order有一个对应的Account对象,那么这个order对象就会有外部Account字段。有一个外部对象,只有Account中的id字段被持久化到order表中的account_id列。例如,这个order类可以像这样:

复制代码
@DatabaseTable(tableName = "orders")

public class Order {

@DatabaseField(generatedId = true)

private int id;

@DatabaseField(canBeNull = false, foreign = true)

private Account account;

...

}
复制代码

 

order表被创建时,有些像下面的SQL将会被生产:

CREATE TABLE `orders`

(`id` INTEGER AUTO_INCREMENT , `account_id` INTEGER,

PRIMARY KEY (`id`));

 

注意:字段名不是account,而是account_id。如果你查询的时候你将会使用这个字段名。你可以在DatabaseField注解中使用columnName成员来设置字段名。

当你用外部对象创建一个字段时,请注意这个外键对象不会为你自动创建。如果你的外部对象有一个数据库提供的generated-id,那么你需要在你创建其他引用它的对象之前创建它。例如:

复制代码
Account account = new Account("Jim Coakley");

accountDao.create(account);

// this will create the account object and set any generated ids

// now we can set the account on the order and create it

Order order = new Order("Jim Sanders", 12.34);

order.setAccount(account);

...

orderDao.create(order);
复制代码

 

如果你希望一些自动创建的等级,那么你可以使用foreignAutoCreate进行设置。

当你查询一个order表时,你将会得到一个Order对象,这对象拥有一个有它id集合的account字段。在外部Account对象中剩下的字段将有默认值(null0false等)。如果你想使用Account中的其他字段,你必须调用accountDao类的refresh来得到填充了的Account对象。比如:

复制代码
Order order = orderDao.queryForId(orderId);

System.out.println("Account-id on the order should be set: " +

order.account.id);

// this should print null for order.account.name

System.out.println("But other fields on the account should not be set: "

+ order.account.name);

// so we refresh the account using the AccountDao

accountDao.refresh(order.getAccount());

System.out.println("Now the account fields will be set: " +

order.account.name);
复制代码

 

你可以通过使用foreignAutoRefresh设置拥有一个自动刷新的外部对象。

注意:因为我们使用refresh,所以外部对象需要有一个id字段。

你可以用两三种不同的方式查询外部字段。下面实例演示代码,代码是查询所有匹配确定的account字段的所有order。因为id字段是name字段,所有你可以通过accountname字段来进行查询。

复制代码
// query for all orders that match a certain account

List<Order> results =

orderDao.queryBuilder().where().

eq("account_id", account.getName()).query();
复制代码

 

 

或者你可以仅仅让ORMLiteaccount取得id字段。这将演示一个和上面等同的查询:

复制代码
// ORMLite will extract and use the id field internally

List<Order> results =

orderDao.queryBuilder().where().

eq("account_id", account).query();
复制代码

 

 

2.13 外部集合

在本手册前面章节中我们有个Order类的例子,它有一个到Account表的外部对象字段。一个外部集合允许你添加account表中的orders集合。每当Account对象通过DAO的查询或刷新返回时,order表和设置在accountorders集合规定了一个单独的查询。所有的orders在集合中有一个对应的和account匹配的外部对象。例如:

复制代码
public class Account {

...

@ForeignCollectionField(eager = false)

ForeignCollection<Order> orders;

...

}
复制代码

 

在上面的示例中,@ForeignCollectionField注解标记了orders成员变量是一个匹配accountorders集合。成员变量orders的类型必须要么是ForeignCollection<T>要么是Collection<T>,没有其他的集合被支持,因为其他集合难以有更多的方法支持。@ForeignCollectionField注解支持下面的成员:

成员名

eager

maxEagerLevel

columnName

orderColumnName

foreignFieldName

备注:具体成员描述参见官方文档。

记住,当你有个ForeignCollection成员变量,集合中的类必须得有一个外部成员。如果Account有个Orders的外部集合,那么Order必须有一个Account外部成员。它是这么要求的,所以ORMLite能找到匹配具体accountorders

警告:用lazy集合甚至是size()方法导致迭代器跨越数据库。你可能最想只使用lazy集合中的iterator() toArray()方法。

注意:就像使用Dao.iterator()方法类似,迭代器被lazy集合返回,当你用了它那么必须关闭它,因为有链接在数据库底层一直开着。下面的方式关闭操作会执行:那么是你通过迭代器把所有的方式走一遍,那么是你调用close()方法。只有ForeignCollection会返回一个可以关闭的迭代器。这意味着循环懒加载集合是不好的模式。

在这种情况下外部集合支持add()和remove()方法:如果一个集合想对象被添加和从内部列表删除,并且DAO被调用用来影响order表以及eagerlazy集合。

注意:当你在一个使用了外部集合的对象上调用upate时,保存在集合中的对象不是自动写到数据库的。可惜在ORMLite中没有方法可以检测到对象被更新了。如果你更新一个集合中的对象你需要在ForeignCollection上调用update(data)方法来确保对象被持久化。例如:

复制代码
for (Order order : account.orders()) {

// if we are changing some field in the order

order.setAmount(123);

// then we need to update it in the database

account.orders.update(order);

}
复制代码

 

 

2.14 DAO激活对象

另一种ORM模式是:有对象执行和他们自己相关的数据库操作来代替使用DAO。比如,给一个数据对象foo,你会调用foo.refresh()来代替fooDao.refresh(foo)。默认的模式是使用DAO类,它允许你的数据类有他们自己的层次并且它独立于Daos中的数据库代码。但是,如果你喜欢这种模式的话你可以自由使用BaseDaoEnabled类。

要使所有的类能够刷新(更新、删除等等)他们自己,那么需要继承BaseDaoEnabled类。例如:

复制代码
@DatabaseTable(tableName = "accounts")

public class Account extends BaseDaoEnabled {

@DatabaseField(id = true)

private String name;

@DatabaseField(canBeNull = false)

private String password;

...
复制代码

 

首先创建对象,你需要使用DAO对象或者你需要设置相关对象的dao以便它能自我创建:

account.setDao(accountDao);

account.create();

 

不过,任何时候一个对象被ORMLite作为一个查询结果返回,那么DAO已经被设置在继承BaseDaoEnabled类的对象上了。

Account account = accountDao.queryForId(name);

account.setPassword(newPassword);

account.update();

 

这也将会为外部成员工作。

Order order = orderDao.queryForId(orderId);

// load all of the fields from the account

order.getAccount().refresh();

 

 

这个BaseDaoEnabled文档有最新的操作列表,现在类仅仅可以做:

操作名称

描述

create

创建对象,你需要使用DAO或者在对象上调用setDao()。

refresh

当数据库中数据发生更新时刷新对象。

update

你改变了内存中的对象之后把它更新到数据库。

updateId

如果你需要更新对象的ID那么你需要使用这个方法。你不能改变对象的id成员然后调用更新方法,因为这样对象会找不到。

delete

从数据库删除。


你可能感兴趣的:(Android数据库ORMlite框架)