第十章 数据库进阶
Hack 41 使用ORMLite构建数据库
ORMLite是一种对象关系映射(Object-Relational Mapping,简称ORM)工具,也可用于读写数据。
应用程序中所有数据库操作都通过ORMLite完成,而不需要手动编写任何SQL语句。该方法可以通过减少创建数据库schema的代码数量来节省时间。
41.1 开始
使用ORMLite时,需要用到其release版本中的两个JAR包:core和android。本例使用的是4.41版。获取依赖包后,就可以开始创建数据库schema了。
使用ORMLite的第一步是实现应用程序中需要操作的java实体类。在这过程中需要注意:在类中包含注解(Annotation)以允许ORMLite创建所需的数据库表。在有复杂数据库的情况下,当从对象中访问数据库时,这些注解还可以为ORMLite提供任何操作的信息。注意,使用注解的方法只是指定ORMLite所生成的数据库schema的多种方式中的一种。
使用ORMLite最常用的两种注解是DatabaseTable和DatabaseField。这些注解可以分别用于类及其成员变量,并运行生成最终的数据库表。使用注解的Article类的简单实现如下:
@DatabaseTable
public class Article{
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String title,text;
@DatabaseField
public Date publishedDate;
public Article(){}//ORMLite需要用到无参构造方法
}
该类,只是全部实现代码的一部分,它会生成如下创建表的SQL语句:
CREATE TABLE 'article'
('title' VARCHAR,'publishedDate' VARCHAR,'text' VARCHAR,'id' INTEGER PRIMARY KEY AUTOINCREMENT);
注意id字段对应的注解,对于该注解我们指定了generatedId = true参数,该参数表示该字段是主键(primary key),并且由SQLite数据库自动指定。另外还要注意,默认情况下,ORMLite使用类名作为SQL表名,使用成员变量名作为表的列名。
最后,我们可以注意到ORMLite需要类中提供一个无参构造函数。当ORMLite需要创建Article类的实例时,比如在查询并返回文章列表的情况下,ORMLite会使用无参构造函数,并通过反射机制设置成员变量(ORMLite也可以使用setter方法设置成员变量)。
41.2 坚如磐石的数据库schema
在这里我们会演示下述内容:
1.自定义数据库表和表列的名字
2.处理类之间的关系
3.关系的参照完整性(API Level 8 及以上)
4.级联删除(API Level 8 及以上)
5.交叉引用的唯一性约束
当定义schema时,第一个建议是使用final变量定义数据库表名和列名。因为在实践中,当成员变量被重构或移除时,该方法可以简化代码的维护工作。
下面我们来定义Category类,代码如下:
//指定表名
@DatabaseTable(tableName = Category.TABLE_NAME)
public class Category{
public static final String TABLE_NAME = "categories",ID_COLUMN = "_id",NAME_COLUMN = "name",PARENT_COLUMN = "parent";
//在DatabaseField中指定列名
@DatabaseField(generatedId = true,columnName = ID_COLUMN)
private int id;
//name成员不能为空
@DatabaseField(canBeNull = false,columnName = NAME_COLUMN)
private String name;
//标记为外键
@DatabaseField(foreign = true,columnName = PARENT_COLUMN)
private Category parent;
public Category(){}
}
我们在DatabaseTable注解里指定了数据库表名,在DatabaseField注解里指定了表的列名。我们可以在应用程序中的任何地方使用这些public变量用于查询。
parent的成员变量声明如下:
@DatabaseField(foreign = true,foreignAutoRefresh = true,columnName = PARENT_COLUMN,columnDefinition = "integer references"+TABLE_NAME+"("+ID_COLUMN+") on delete cascade")
private Category parent;
我们可以使用columnDefinition定义列来微调SQL语句。这里我们指定parent列有一个外键指向categories表(外键的值定义在该表中)。这表明parent列的值要么是空,要么存在于categories表的_id列中。我们还指定当parent指向的分类被删除时,parent的记录也会被删除,这就是级联删除。级联删除并不是数据库要求的,但为了演示,我们在代码中包含这部分内容。为Category类创建数据库表的SQL语句如下:
CREATE TABLE 'categories' ('parent' integer references categories(_id) on delete cascade,'name' VARCHAR NOT NULL,'_id' INTEGER PRIMARY KEY AUTOINCREMENT)
ArticleCategory类用于将articles表映射到categories表,我们会为其两个成员变量设置uniqueCombo = true,代码如下:
@DatabaseTable(tableName = ArticleCategory.TABLE_NAME)
public class ArticleCategory{
//final变量用于表示表名和列名
public static final String TABLE_NAME = "articlecategories",ARTICLE_ID_COLUMN = "article_id",CATEGORY_ID_COLUMN = "category_id";
//使用columnDefinition元素
@DatabaseField(foreign = true,canBeNull = false,uniqueCombo = true,columnName = ARTICLE_ID_COLUMN,columnDefinition = "integer references"+Article.TABLE_NAME+"("+Article.ID_COLUMN+") on delete cascade")
private Article article;
//设置foreign = true用于存储复杂对象
@DatabaseField(foreign = true,canBeNull = false,uniqueCombo = true,columnName = CATEGORY_ID_COLUMN,columnDefinition="integer references"+Category.TABLE_NAME+"("+Category.ID_COLUMN+") on delete cascade")
private Category category;
public ArticleCategory(){}
}
最终创建表的SQL语句如下所示:
CREATE TABLE 'articlecategories'('article_id' integer references articles(_id) on delete cascade,'category_id' integer references categories(_id) on