前面一篇文章通过一个实例,介绍了ORMLite的使用方法,为了控制篇幅,里面的代码有些是不完整的,但是基本能够说明整个流程。对于这类开源框架,要记住所有内容,是不可能的,而且也不是最合理的学习方法,其实只要理解了这类框架的实现机制,记住了关键的内容,遇到问题知道怎么去寻找答案就可以了,后面在使用的过程中,再慢慢熟能生巧。通过前面一篇文章,结合官方实例,自己运行一下,修改体验一下,应该就能够理解。后面就看自己的积累了。
鉴于目前好像比较少这个框架的完整文档翻译(也可能是我不会搜索)。为了方便自己学习,我对其中的主要内容进行了翻译,也完整的逐句翻译,而是翻译了关键的讲解性的地方。对于一些方法的介绍,没有详细翻译,其一,我觉得,这些方法的方法名基本上能够表达他们的用途,其二,真正不懂的地方,我个人还是倾向于直接阅读英文文档,能够更好更完整的理解。也希望能够帮助到有需要的人,实在不理解的,还请看文档吧。这篇文章是对英文文档第二章的主要内容翻译,理解有误的地方,还请批评指正。
原文档:http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_2.html#Using
尊重原创,转载请说明出处,谢谢! http://blog.csdn.net/oyangyujun
第二章 如何使用
columnName
dataType
defaultValue
width
canBeNull
id
generatedId
generatedIdSequence
foreign
useGetSet
unknownEnumName
throwIfNull
persisted
format
unique
uniqueCombo
index
uniqueIndex
indexName
uniqueIndexName
foreignAutoRefresh
maxForeignAutoRefreshLevel
allowGeneratedIdInsert
columnDefinition
foreignAutoCreate
version
foreignColumnName
2.1.2 使用标准的JPA(javax.persistence
annotations)注解(暂不考虑)
使用@Entity代替@DatabaseTable代替,可以指定一个name参数值表示表名,如果不指定,默认使用类名。
用@Column,@GeneratedValue,@OneToOne ,@ManyToOne, @JoinColumn, 和@Version指定字段属性。
支持的JPA注解包括
@Entity
@Column
@Id
@GeneratedValue
@OneToOne or @ManyToOne
@JoinColumn
@Version
2.1.3 添加一个无参构造函数通过
2.2 支持持久化的数据类型
DatabaseField注解是有一个dataType属性,表明java数据类型和sql数据类型进行的对应数据转换,包括如下:
String (DataType.STRING)
String (DataType.LONG_STRING)
String (DataType.STRING_BYTES)
boolean or Boolean (DataType.BOOLEAN or DataType.BOOLEAN_OBJ)
java.util.Date (DataType.DATE)
java.util.Date (DataType.DATE_LONG)
java.util.Date (DATE_STRING)
byte or Byte (DataType.BYTE or DataType.BYTE_OBJ)
byte array (DataType.BYTE_ARRAY)
char or Character (DataType.CHAR or DataType.CHAR_OBJ)
short or Short (DataType.SHORT or DataType.SHORT_OBJ)
int or Integer (DataType.INTEGER or DataType.INTEGER_OBJ)
long or Long (DataType.LONG or DataType.LONG_OBJ)
float or Float (DataType.FLOAT or DataType.FLOAT_OBJ)
double or Double (DataType.DOUBLE or DataType.DOUBLE_OBJ)
Serializable (DataType.SERIALIZABLE)
enum or Enum (DataType.ENUM_STRING)
enum or Enum (DataType.ENUM_INTEGER)
UUID (DataType.UUID)
BigInteger (DataType.BIG_INTEGER)
BigDecimal (DataType.BIG_DECIMAL)
BigDecimal (DataType.BIG_DECIMAL_NUMERIC)
DateTime (DataType.DATE_TIME)
java.sql.Date (DataType.SQL_DATE)
java.sql.Timestamp (DataType.TIME_STAMP)
2.3 创建链接资源
在第一篇中已经有介绍
2.4 创建dao类,每个dao类负责操作一个对应的持久化类。
DAO类的创建,包含两个参数,第一个是持久化类的类名,第二个是该类对应的id类型,可以通过DaoManager的静态方法直接创建,如:
Dao<Account, String> accountDao = DaoManager.createDao(connectionSource, Account.class);建议创建DAO接口,然后创建实现类,并且通过 daoClass指定
|
/** JDBC implementation of the AccountDao interface. */ public class AccountDaoImpl extends BaseDaoImpl<Account, String> implements AccountDao { // this constructor must be defined public AccountDaoImpl(ConnectionSource connectionSource) throws SQLException { super(connectionSource, Account.class); } }
要让自定义DAO类生效,需要将其指定到对应实体中@DatabaseTable的daoClass属性上。
|
@DatabaseTable(daoClass = AccountDaoImpl.class) public class Account { … } |
2.5 ORMLite支持的数据库类型
2.6 结合使用 Tying It All Together
基本流程
|
TableUtils.createTable(connectionSource, Account.class);
createTableIfNotExists(ConnectionSource, Class) 同上,但不支持所有类型的数据库createTable(ConnectionSource, DatabaseTableConfig) 同上,
getCreateTableStatements(ConnectionSource, Class)
createTable(ConnectionSource, DatabaseTableConfig) 功能同上,不过通过一个DatabaseTableConfig对象类表示需要创建的表的字段和字段类型。
createTableIfNotExists(ConnectionSource, DatabaseTableConfig) 功能同上,但不支持所有的数据库。
dropTable(ConnectionSource, Class, boolean ignoreErrors) 删除表,不能恢复,慎用。ignoreErrors参数,如
果删除时,表没有创建,那么将ignoreErrors设为true则忽视所有异常。
dropTable(ConnectionSource, DatabaseTableConfig, boolean ignoreErrors) 删除表,同上
getCreateTableStatements(ConnectionSource, Class) 类似于createTable,但是该方法返回的是一个语句列表,
这个列表可用于创建一个类,在部分数据库的初始化过程中,比较有用。
getCreateTableStatements(ConnectionSource, DatabaseTableConfig) 同上
clearTable(ConnectionSource, Class) 清除所有数据
clearTable(ConnectionSource, DatabaseTableConfig) 使用DatabaseTableConfig清除所有数据
2.7.2 TableCreator类
TableCreator类是为Spring设计的,但是在其他的配置中也可用。它通过ConnectionSource和DAOs列表配置。
如果系统属性ormlite.auto.create.tables被设为true则,与DAOs关联的表会被自动创建。如果系统属性
ormlite.auto.drop.tables属性被设为true,则会自动删除表。这点在测试的时候比较有用。你可以在测试开始的脚
本中设置系统属性,并在生产脚本中关闭脚本。
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 标识列(字段)
数据库行可以通过特定的id列标识。行本身不需要一个标识字段,但是许多DAO操作(update, delete, refresh
)需要一个表示字段。这个标识字段可以有用户指定也可以由数据库自动生成。数据行中id列的值时唯一的,如果需
要根据id操作数据,则必须指定id字段,可以通过下列三种方法中的一种,且只允许用一种指定。@DatabaseField属
性的id,generatedId, generatedIdSequence
2.8.1 通过id指定
例如:
public class Account {
@DatabaseField(id = true)
private String name;
…
}
使用:
Account account = accountDao.queryForId("John Smith");
if (account == null) {
// the name "John Smith" does not match any rows
}
2.8.2 通过generatedId指定
可以通过long或者int型属性生成标识字段,数据行的字段会自动生成
例如:
public class Order {
@DatabaseField(generatedId = true)
private int id;
…
}
当订单对象被创建并被存储到数据库中的时候,生成的id值会被返回并被设置给对象,大多数数据库类型,生成的id
值从1开始,并每次递增1。
使用:
//通过指定id创建对象
Order order = new Order("Jim Sanders", 12.34);
…
orderDao.create(order);
System.out.println("Order id " + order.getId() +
" was persisted to the database");
//查询id为1372的订单对象
order = orderDao.queryForId(1372);
if (order == null) {
// none of the order rows have an id of 1372
}
2.8.3 通过generatedIdSequence创建id
有些数据库使用序列号生成器提供id值,如果你使用generatedId = true创建数据库,则序列名会由ORMLite自动生
成,不过,你需要设置一个序列蜜瓜匹配现有的schema,你可以使用generatedIdSequence值。
例如:
public class Order {
@DatabaseField(generatedIdSequence = "order_id_seq")
private int id;
…
}
2.9 DAO使用
通过使用DAO的方法,下列数据库操作可以很轻松的完成。
1. 创建并持久化对象到数据库
Account account = new Account();
account.name = "Jim Coakley";
accountDao.create(account);
2. 通过id列查询
Account account = accountDao.queryForId(name);
if (account == null) {
account not found handling …
}
3. 更新域数据库关联的对象的数据行。
account.password = "_secret";
accountDao.update(account);
4. 在数据库数据发生改变时刷新对象,需要id值。
accountDao.refresh(account);
5. 删除数据行。数据库中删除后,内存中持有的数据库还可用。
accountDao.delete(account);
6. 通过DAO对象遍历数据行。DAO本身也是一个迭代器,因此可以直接遍历数据库数据行。
// page through all of the accounts in the database
for (Account account : accountDao) {
System.out.println(account.getName());
}
你必须遍历迭代器的所有数据库对象,并关闭潜在的sql对象。 如果你没有通过一个循环遍历所有的数据,
ORMLite并不会关闭潜在对象,数据库的连接会被弱化并且在后面被GC回收,这回导致程序Bug,所以最好使用一个
try...finally包装迭代器。下面的情况是错误的“必须遍历所有的对象”。
for (Account account : accountDao) {
if (account.getName().equals("Bob Smith")) {
// you can't return, break, or throw from here
return account;
}
}
7. 直接使用迭代器
你也可以选择直接使用迭代器,因为for循环式最佳的。这样允许你使用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();
}
8. 获得一个包装迭代器“wrapped iterable”
CloseableWrappedIterable<Account> wrappedIterable =
accountDao.getWrappedIterable();
try {
for (Account account : wrappedIterable) {
…
}
} finally {
wrappedIterable.close();
}
2.10 索引属性
id字段默认是索引属性,非id字段在DatabaseField属性属性中指定index值为true,所以优化查询,在表被删除
时,索引也会被删除。
public class Account {
@DatabaseField(id = true)
private String name;
// 给city属性索引索引,便于款速查询更大的表
@DatabaseField(index = true)
private String city;
…
}
索引名默认为account_city_idx,可以通过一个indexName=“othername”指定其他的索引名称。如果经常通过两
个字段进行查询,可以通过为两个字段设置相同的索引名,如:
@DatabaseField(indexName = "account_citystate_idx")
private String city;
@DatabaseField(indexName = "account_citystate_idx")
private String state;
这种情况下,如果只根据其中的一个字段查询,则并不会优化查询,不同的数据库类型,单字段索引和多字段索
引的效率比较可能不行同。
创建唯一索引可以通过@DatabaseField注解的uniqueIndex=true和uniqueIndexName='indexname'属性指定。这样
,效果上和上面的设置时一样的,但是这样可以创建确保没有两行数据具有相同的索引属性值。
2.11 原生的SQL语句问题
2.11.11 原生的查询操作
1. 使用DAO中的queryRaw方法创建原生数据库查询,这个方法会返回一个GenericRawResult对象,代表一个字符
串数组,对象数组,或者用户包装对象的数组。 可以查看HenericRaeResult的文档获取更多用法。
// 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");
2. 如果喜欢使用preparedStatementString()方法的话,也可以使用QueryBuilder创建原生查询语句,详情可以
查看QueryBuilderBasic这节。
QueryBuilder<Account, Integer> qb = accountDao.queryBuilder();
qb.where().ge("orderCount", 10);
results = accountDao.queryRaw(qb.prepareStatementString());
3. 如果需要使用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);
4. 也可以使用下面的方式携带参数创建原生查询
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();
5. 对于数据量大的结果集,应该考虑使用GenericRawResults对象的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();
6. 如果你只需要查询单个值(如聚合函数MAX的值),则可以使用queryRawValue(...)方法:
long maxUnits = orderDao.queryRawValue("select max(units) from orders");
7. 如果你的部分属性值不能被合适地转换成字符串,并且你在结果字段中传递了返回类型,那么可以返回属性
为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 的 *。
8. 你也可以通过传递一个RawRowMapper对象,将返回的结果映射到你自己的对象中。 这样会通过一个字符串数
组调用映射对象,并允许将字符创转换到对象中。DAO提供了一个默认的RawRowMapper,可以通过
orderDAO.getRawRowMapper()获得,这个对象知道如何将字符串数组转换到对象中。
如果结果比较复杂,你也可以自定义Mapper,如:
// 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 * 可能根据数据库类型返回的顺序不同。
注: 和其他ORMLite迭代器一样,你必须遍历迭代器中的所有结果,以使语句自动关闭,或者调用
GeneratedRawResults.close()方法确保迭代器及相关的数据库连接关闭。
注: 如果你正在使用QueryBuilder#preparedStatementString()方法创建查询,则可能会自动添加一个id列。
2.11.2 原生的更新语句
在DAO的函数不满足你的灵活性时,可能要用到原生更新语句,更新语句必须包含保留关键字 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支持外键的概念,既一个对象的一个或者多个属性对应相同数据库中其他表中的一个对象。例如,如果有
一个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;
…
}
当订单表创建的时候,会生成下面的语句
CREATE TABLE `orders`(`id` INTEGER AUTO_INCREMENT , `account_id` INTEGER,
PRIMARY KEY (`id`));
注:属性名并不叫account,而是account_id。 如果你需要通过这个属性去查询的话,应该指定为account_id,
或者是通过DatabaseField注解的columnName参数指定一个字段名。
通过外步对象创建属性的时候,一定注意,外在对象不会自动被创建。如果你的外在对象有一个数据库提供的id
,那么,你需要在这个对象被引用之前创建这个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设置,参考foreignAutoCreate
当你查询一个订单时,你会获得一个account的属性对象,这个对象只有id属性被设定了值,其他属性都是默认值
。如果你需要使用这个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设置自动刷新外部对象的数据。参考foreignAutoRefresh.
注意: 由于我们使用到刷新,所以外部对象必须有id属性。
你可以使用不同的方式查询外部对象属性。下面的例子展示了查询匹配所有account属性的订单的代码。因为id属
性石name,所以你可以通过account的name属性进行查询。
// query for all orders that match a certain account
List<Order> results =
orderDao.queryBuilder().where().eq("account_id", account.getName()).query();
或者,你可以让ORMLite从account对象中取出id属性,下面的代码与上面的代码等价:
// ORMLite will extract and use the id field internally
List<Order> results = orderDao.queryBuilder().where().eq("account_id", account).query();
2.13 外部集合属性
在上一届的指南中我们给出了订单类具有一个外部对象属性的实例。外部集合允许你添加一个订单集合到账户表
中。无论何时,当账户对象通过查询语句返回,或者通过DAO对象刷新时,一个独立的查询会创建,用于查询对应的
订单,并且将这个订单集合设置到账户对象中。集合中所有的订单都有一个关联的外部对象鱼账户对象匹配。
public class Account {
…
@ForeignCollectionField(eager = false)
ForeignCollection<Order> orders;
…
}
上面的例子显示,@ForeignCollectionField注解表明了订单属性是一个与账户相匹配的订单集合。订单的属性类
型必须是@ForeignCollection<T>或者是Collection<T>,其他类型的集合不支持,因为他们有太多方法要支持,太过
重量级。@ForeignCollectionField注解支持的属性包括下面这些:
eager
有两种类型的外部集合:eager和lazy,如果设置eager为true,则独立的查询语句会被创建,对应的订单会被存
储在一个集合中。如果eager设为false(默认值),则集合会被认为是“lazy”的,只有集合中的方法被调用时,才
会调用DAO.iterator()方法,遍历数据库。
注:默认情况下,如果如果你有一个eager类型的对象集合,并且他们本身也有eager集合,那么内部的集合会
因为性能原因被创建为lazy的。如果需要改变这一点的话, 可以查看下面的maxEagerLevel设置。
maxEagerLevel
这个属性能设置一个eager外部集合的外部集合扩展次数。如果你查询A,并且A具有一个外部eager集合属性B, B
具有一个外部集合属性C,...,无论何时,当你查询A的时候,一些列的数据库操作会发生。莫仍情况下,这个值为1
,表示如果你查询A,那么集合B会以eager方式获得,但是B中的每个对象的eager集合C会以lazy集合的方式加载。你
应该根据自己的实际情况设置这个值。
columnName
列名。只有当你想要匹配传递到DAO.assignEmptyForeignCollection(Object, String)中的string,或者指定想
要通过queryBuilder。selectColumn(...)返回的集合时使用。
orderColumnName
orderAscending
foreignFieldName
2.14 支持DAO的对象
下面是让对象本身支持数据库操作,而不是使用DAO进行的ORM模式的另外一种方式。例如,给定一个数据对象foo,可以调用foo.refresh()而不是调用fooDao.refresh(foo)。默认是使用Dao类,这样可以让你的数据类拥有它自己的层次,并将数据库代码和Dao分离。不过,如果你乐意使用这种模式的话,可以使用BaseDaoEnable类。
所有可以属性自身的类都需要继承BaseDaoEnable类。例如:
@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已经被设置到这个对象中。
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();
BaseDaoEnable的javadoc具有最新的自操作列表,不过,目前这种类能做的内容如下:
create
使用DAO或者调用对象的setDao()方法。
refresh
刷新对象,以免其在数据库中有更新。
update
对象在内容中改变后,将数据更新到数据库。
updateId
如果需要更新对象的id,必须使用这个方法。不能改变对象的id属性,然后调用update方法,否则数据这个对象
无法找到。
delete
从数据库中删除对象。