用sql查询姓名和身份证
在本系列的第一篇文章中,我讨论了RDBMS作为Java™对象存储解决方案的失败。 正如我所解释的那样,在当今的面向对象的世界中,像db4o这样的对象数据库可以为面向对象的开发人员提供更多的功能,而不仅仅是关系数据库。
在本文和以后的文章中,我将继续介绍对象数据库。 我将使用示例来展示存储系统的强大功能,该存储系统针对您在选择的面向对象编程语言(在本例中为Java语言)中使用的相同“形状”实体进行了优化。 特别是,我将介绍各种可用于将对象检索,修改和还原回db4o的机制。 正如您将学到的,一旦您摆脱了SQL的约束,您可以做的事实际上令人惊讶。
如果尚未这样做,则可能要立即下载db4o 。 您需要它来编译示例。
示例查询(QBE)是一种数据库查询语言,它允许您通过设计要进行比较的“模板”来创建查询,而不是使用谓词条件的语言(如SQL)。 我上次演示了使用db4o的QBE引擎进行数据检索,但是在这里我将快速回顾一下。 首先看一下我公认的原始数据库。 它由一种类型组成,其定义如清单1所示:
package com.tedneward.model;
public class Person
{
public Person()
{ }
public Person(String firstName, String lastName, int age)
{
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public String getFirstName() { return firstName; }
public void setFirstName(String value) { firstName = value; }
public String getLastName() { return lastName; }
public void setLastName(String value) { lastName = value; }
public int getAge() { return age; }
public void setAge(int value) { age = value; }
public String toString()
{
return
"[Person: " +
"firstName = " + firstName + " " +
"lastName = " + lastName + " " +
"age = " + age +
"]";
}
public boolean equals(Object rhs)
{
if (rhs == this)
return true;
if (!(rhs instanceof Person))
return false;
Person other = (Person)rhs;
return (this.firstName.equals(other.firstName) &&
this.lastName.equals(other.lastName) &&
this.age == other.age);
}
private String firstName;
private String lastName;
private int age;
}
随着POJO的发展, Person
几乎不是一个复杂的野兽。 它由三个字段和一些支持类似于POJO的活动的基本方法组成,即toString()
和equals()
。 (约书亚·布洛赫(Joshua Bloch)的《 有效Java》的精明读者会注意到,我省略了hashCode()
实现,这明显违反了规则8。在经典的作者看来,我将hashCode()
留为“对读者的一种练习, “这通常意味着作者要么不想打扰它,要么认为手头的例子不必要。我也把它留给读者作为练习,以决定在这里是哪种情况。”
在清单2中,我创建了六个对象,将它们放入文件中,然后使用QBE调用名字与模式“ Brian”匹配的两个对象。 这种查询风格使用原型对象(传递给get()
调用的原型对象)来确定数据库中的对象是否匹配,并返回那些符合条件的对象的ObjectSet
(本质上是一个集合)。
import com.db4o.*;
import com.tedneward.model.*;
public class Hellodb4o
{
public static void main(String[] args)
throws Exception
{
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
Person brian = new Person("Brian", "Goetz", 39);
Person jason = new Person("Jason", "Hunter", 35);
Person brians = new Person("Brian", "Sletten", 38);
Person david = new Person("David", "Geary", 55);
Person glenn = new Person("Glenn", "Vanderberg", 40);
Person neal = new Person("Neal", "Ford", 39);
db.set(brian);
db.set(jason);
db.set(brians);
db.set(david);
db.set(glenn);
db.set(neal);
db.commit();
// Find all the Brians
ObjectSet brians = db.get(new Person("Brian", null, 0));
while (brians.hasNext())
System.out.println(brians.next());
}
finally
{
if (db != null)
db.close();
}
}
}
由于QBE使用原型对象作为其模板来搜索数据,因此有一些关于其用法的简单规则。 当db4o正在为给定目标搜索“ Person
类型的所有对象时(发生的事情过于简化,但从概念上讲是准确的),以确定数据存储中的特定对象是否符合条件,将字段值一一比较。 如果原型中的字段为“ null”,则该值与数据存储区中的任何值匹配;否则为0。 否则,值必须完全匹配。 对于基本类型,由于基本类型不能真正保存“ null”值,因此零用作通配符值。 (这也指出了QBE方法的局限性-不能有效地使用零作为搜索值。)如果指定了多个字段值,则数据库中的对象必须满足候选人的所有字段值对象符合查询条件; 从本质上讲,这意味着将字段“与”在一起以形成查询谓词。
在前面的示例中,查询正在查找firstName
字段等于“ Brian”且有效忽略了lastName
和age
字段的所有Person
类型。 在表中,此调用将大致对应于SELECT * FROM Person WHERE firstName = "Brian"
SQL查询。 (但是,要谨慎尝试将OODBMS查询映射到SQL:这种类比并不完美,并且可能导致对特定查询的性质和性能的误解。)
查询返回的对象是ObjectSet
,它与JDBC ResultSet
相似,因为它是对象的简单容器。 使用ObjectSet
实现的Iterator
接口,对结果进行ObjectSet
是一个简单的练习。 使用Person
的特定方法将需要对next()
返回的对象进行向下转换。
尽管简单的数据显示本身很有趣,但是大多数对象也需要进行修改并将其还原回数据库。 这可能是使用OODBMS的最棘手的部分,因为对象数据库使用的标识概念与关系数据库使用的标识概念不同。 实际上,这意味着在使用对象数据库时,必须更加注意内存中的对象与存储中的对象。
清单3中的简单示例演示了身份的不同概念:
import com.db4o.*;
import com.tedneward.model.*;
public class Hellodb4o
{
public static void main(String[] args)
throws Exception
{
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
Person brian = new Person("Brian", "Goetz", 39);
Person jason = new Person("Jason", "Hunter", 35);
Person brians = new Person("Brian", "Sletten", 38);
Person david = new Person("David", "Geary", 55);
Person glenn = new Person("Glenn", "Vanderberg", 40);
Person neal = new Person("Neal", "Ford", 39);
db.set(brian);
db.set(jason);
db.set(brians);
db.set(david);
db.set(glenn);
db.set(neal);
db.commit();
// Find all the Brians
ObjectSet brians = db.get(new Person("Brian", null, 0));
while (brians.hasNext())
System.out.println(brians.next());
Person brian2 = new Person("Brian", "Goetz", 39);db.set(brian2);db.commit();
// Find all the Brians
ObjectSet brians = db.get(new Person("Brian", null, 0));
while (brians.hasNext())
System.out.println(brians.next());
}
finally
{
if (db != null)
db.close();
}
}
}
当您运行清单3中的查询时,数据库报告三个Brian
,其中两个Brian Goetz。 (如果person.data文件已经存在于当前目录中,则会产生类似的效果-创建的所有Person
都将存储在person.data文件中,并且存储在其中的所有Brian
都将由查询返回。 )
显然,关于主键的旧规则在这里并没有生效; 那么对象数据库如何处理唯一性概念?
将对象存储到对象数据库中时,将创建一个唯一键,称为对象标识符或OID (与最后一个回避音节的发音类似),它唯一地标识该对象。 除非明确请求,否则OID就像C#和Java编程中的this指针/引用一样,是无声的。 在db4o中,可以通过调用db.ext().getID()
找到给定对象的OID。 (您还可以使用db.ext().getByID()
方法通过OID检索对象。调用此方法的含义有些复杂,因此此处db.ext().getByID()
讨论,但仍然可以选择。)
实际上,所有这些意味着开发人员可以通过先前在容器中查询该对象来确定对象是否存在于系统中,如清单4所示:
// ... as before
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
...
// We want to add Brian Goetz to the database; is he already there?
if (db.get(new Person("Brian", "Goetz", 0).hasNext() == false)
{
// Nope, no Brian Goetz here, go ahead and add him
db.set(new Person("Brian", "Goetz", 39));
db.commit();
}
}
在这种特殊情况下,我们假设一个Person
在系统中的唯一性是其名字-姓氏组合。 因此,在数据库中搜索Brian
时,只需在Person
实例上查找那些属性。 (也许布莱恩是在几年前加入的,那时他才39岁。)
如果要修改数据库中的对象,可以很简单地从容器中检索对象,以某种方式对其进行修改,然后再将其存储回去,如清单5所示:
// ... as before
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
...
// Happy Birthday, David Geary!
if ((ObjectSet set = db.get(new Person("David", "Geary", 0))).hasNext())
{
Person davidG = (Person)set.next();davidG.setAge(davidG.getAge() + 1);db.set(davidG);db.commit();
}
else
throw new MissingPersonsException(
"David Geary doesn't seem to be in the database");
}
db4o容器在这里不会遇到身份问题,因为相关对象已被识别为来自数据库的对象,这意味着其OID已存储在db4o簿记基础结构中。 因此,当您调用set
,db4o知道更新现有对象而不是插入新对象。
尽管不是QBE固有的,但特定于应用程序的主键的概念还是值得保留的。 您需要一种实用程序方法来简化基于身份的搜索。 本节向您展示了一种基于解决方案的解决方案,该解决方案使用了Reflection API在正确的字段中戳入正确的值,并提出了针对各种喜好和美感来调整解决方案的方法。
让我们从一个基本前提开始:我有一个db4o数据库,其中有一个我要基于其中具有某些值的字段集进行查询的类型( Person
)。 在此方法中,我使用Class
上的Reflection API来创建该类型的新实例(调用其默认构造函数)。 然后,我遍历其中包含字段的字符串数组,以获取Class
中的每个Field
对象。 之后,我遍历与每个字段的值相对应的对象数组,然后调用Field.set()
将那个值Field.set()
我的模板对象中。
完成这些操作后,我在db4o数据库上调用get()
并检查返回的ObjectSet
是否包含任何对象。 这给了我一个基本的方法概述,如清单6所示:
import java.lang.reflect.*;
import com.db4o.*;
public class Util
{
public static boolean identitySearch(ObjectContainer db, Class type,
String[] fields, Object[] values)
throws InstantiationException, IllegalAccessException,
NoSuchFieldException
{
// Create an instance of our type
Object template = type.newInstance();
// Populate its fields with the passed-in template values
for (int i=0; i
显然,可以做很多工作来调整此方法的味道,例如捕获所有异常类型并将它们作为运行时异常重新抛出,或者返回ObjectSet
本身而不是true / false ,甚至返回包含以下内容的对象数组: ObjectSet
内容(这将使检查返回数组的长度变得容易)。 但是,从清单7中可以明显看出,它的用法比已经显示的基本QBE版本简单得多:
// Is Brian already in the database?
if (Util.identitySearch(
db, Person.class, {"firstName", "lastName"}, {"Brian", "Goetz"}) == false)
{
db.set(new Person("Brian", "Goetz", 39));
db.commit();
}
实际上,当将实用程序方法的许多实用程序放置在存储的类本身上时,它们就会变得很明显,如清单8所示:
public class Person
{
// ... as before
public static boolean exists(ObjectContainer db, Person instance)
{
return (Util.identitySearch(db, Person.class,
{"firstName", "lastName"},
{instance.getFirstName(), instance.getLastName()});
}
}
或者,再次,您可以调整方法以返回找到的实例,以使Person
实例具有适当关联的OID,依此类推。 要记住的关键是,您可以在db4o基础结构之上构建便捷方法,以使其易于使用。
请注意,使用db4o SODA查询API可以对存储在磁盘上的基础对象执行这种查询样式,这是一种更有效的方法,但是它在本文的讨论范围之外,因此我将其留待以后的讨论。
到目前为止,您已经了解了如何查询单个对象或满足特定条件的对象。 尽管这使发出查询的方法相当容易,但也使选择受到了限制。 例如,如果你需要什么样的检索所有Person
(胡)的名字开始与G,或所有Person
的年龄大于21号第? 对于这些类型的查询,QBE方法将严重失败,因为QBE进行相等匹配而不是比较。
从历史上看,即使是比较复杂的比较也一直是OODBMS的弱点,也是关系模型和SQL的强项。 在SQL中发出比较查询很简单,但是要在OODBMS中执行相同的查询,则需要采用几种不引人注意的方法之一:
显然,上面的第一个选项仅对最普通的数据库才可行,因为它给您可以实际使用的数据库大小增加了明显的上限。 即使最坚硬的硬件也无法轻易摆脱百万个对象的负担,尤其是在通过网络连接的情况下。 (顺便说一下,这并不是对OODBMS的起诉-通过RDBMS服务器的能力可以通过网络连接获取一百万行,但仍会破坏其所在的网络。)
第二种选择污染了QBE方法的简单性,并导致了清单9中所示的怪异现象:
Query q = new Query();
q.setClass(Person.class);
q.setPredicate(new Predicate(
new And(
new Equals(new Field("firstName"), "David"),
new GreaterThan(new Field("age"), 21)
)));
q.Execute();
很容易看出使用这种技术,任何中等复杂的查询将如何Swift变得不可行,特别是与诸如SQL之类的查询语言相比时。
第三种选择是创建一种查询语言,然后该查询语言可用于查询数据库的对象模型。 过去,OODBMS员工创建了标准查询语言,对象查询语言或OQL,其外观类似于清单10:
SELECT p FROM Person
WHERE p.firstName = "David" AND p.age > 21
从表面上看,OQL看起来与SQL非常相似,因此据说同样强大且易于使用。 OQL的缺点是它想返回……什么? 类似于SQL的语言似乎像SQL一样想要返回列集(元组),但是对象数据库不能那样工作-它想要返回对象,而不是任意集。 特别是在像C#或Java编程这样的强类型语言中,这些对象类型必须先验地知道 ,这与基于集合SQL概念不同。
db4o并没有强迫开发人员使用复杂的查询API或引入新的“ something-QL”,而是提供了一种称为本机查询的功能 ,该功能既强大又非常易于使用,如清单11所示。对于db4o的API 是 SODA查询,这是主要用于细粒度查询控制的形式提供。正如你在第二个看不过来,SODA通常只用于手动优化查询必要的。)
// ... as before
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
...
// Who wants to get a beer?
List drinkers = db.query(new Predicate() {
public boolean match(Person candidate) {
return person.getAge() > 21;
}
}
for (Person drinker : drinkers)
System.out.println("Here's your beer, " + person.getFirstName());
}
查询的“本机”部分是这样的事实:它是用编程语言本身(在这种情况下是Java语言)编写的,而不是用某种任意的语言编写的,然后必须将其翻译成其他某种语言。 (Predicate API的非泛型版本可用于Java 5之前的版本,尽管使用起来不太容易。)
片刻考虑一下,您可能会开始怀疑这种特定方法是如何实施的。 或者必须使用源预处理器将其中包含查询的源文件转换为数据库引擎可以理解的内容( 例如 SQL / J或其他嵌入式预处理器),或者数据库会将所有Person
对象发送回给对整个集合执行谓词的客户端(换句话说,就是之前拒绝的方法)。
事实证明,db4o均不执行任何操作。 相反,db4o背后的负责人选择对本地查询采用一种有趣且创新的方法。 松散地说,db4o系统将一个谓词发送到数据库,在数据库中它在运行时对match()
方法的字节码执行字节码分析。 如果字节码足够容易理解,db4o会将该查询转换为SODA查询以提高效率,在这种情况下,无需实例化所有对象以传递给match()
方法。 这样,程序员可以继续使用自己喜欢的语言编写查询,但是查询本身可以转换为数据库可以理解和有效执行的内容。 (如果愿意的话,可以选择“ JQL” - Java查询语言。但是,请不要对db4o开发人员重复该名称;否则会给我带来麻烦。)
本机查询方法并不完美。 例如,完全有可能编写一个足以打败字节码分析器的本机查询,从而需要执行最坏情况的执行模型。 在这种最坏的情况下,db4o必须实例化数据库中查询类型的每个对象,并将每个对象都通过match()
实现。 可以预计,这会降低查询性能,但是您可以通过在需要的地方安装侦听器来解决此问题。
直觉并不总是足以预见优化失败,因为原因可能与代码审查所暗示的完全不同。 例如,在控制台代码中包含控制台打印语句(Java代码中为System.out.println
或C#中包含System.Console.WriteLine
)会导致优化器在db4o的.NET版本中失败,而Java版本会优化该语句。 您无法真正预料到这种类型的变化(尽管您可以通过经验来了解它们),所以让系统告诉您总是一个好主意,就像他们在极限编程中所说的那样。
只需在ObjectContainer
本身上注册一个侦听器( Db4oQueryExecutionListener
),以通知您是否无法优化本机查询,如清单12所示:
// ... as before
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
db.ext().configure().diagnostic().addListener(new DiagnosticListener() {
public void onDiagnostic(Diagnostic d) {
if (d instanceof NativeQueryNotOptimized)
{
// could display information here, but for simplicity
// let's just fail loudly
throw new RuntimeException("Native query failed optimization!");
}
}
});
}
显然,这仅在开发过程中是合乎需要的-在运行时,最好将此故障记录到log4j错误流或对用户分心的东西。
在《繁忙的Java开发人员db4o指南》的第二篇文章中,我将OODBMS身份概念用作解释db4o如何存储和检索对象以及引入其本机查询功能的起点。
QBE是用于简单查询情况的首选机制,因为它是一种更易于使用的API,但是它确实要求您的域对象允许将包含数据的任何或所有字段设置为null,这可能会违反您的某些域规则。 例如,能够为Person
对象强制使用名字和姓氏将是很好的。 但是,在QBE查询中仅使用Person
作为姓氏,就要求将名字允许为空,这实际上意味着我们必须选择域约束或查询功能,而这两者都不是完全可以接受的。
本机查询提供了执行复杂查询的强大方法,而无需学习新的查询语言或诉诸复杂的对象结构来对谓词建模。 对于db4o的本机查询工具无法满足需求的情况,SODA API(最初显示为任何对象系统的独立查询系统,并且仍然存在于SourceForge上)使您可以将查询调优至最细微的细节,以简单为代价。
这种多方面的数据库查询方法可能会让您感到复杂和混乱,并且与RDBMS的工作方式完全不同。 实际上,情况并非如此:大多数大型数据库将SQL文本转换为字节码格式,然后对其进行分析和优化,然后针对存储在磁盘上的数据执行,组合回文本并返回。 db4o本机查询方法将编译后的字节码重新交到Java(或C#)编译器手中,从而允许类型安全和更早地检测到错误的查询语法。 (顺便说一下,遗憾的是,JDBC访问SQL的方法中缺少类型安全性,因为它是一个调用级接口,因此仅限于只能在运行时检查的字符串。这对任何CLI都是如此,而不仅仅是JDBC; ODBC和.NET的ADO.NET受到相同的限制。)优化仍在数据库内部完成,但不是将文本返回,而是将真实对象发回,以备使用。 这与SQL / Hibernate或其他ORM方法形成鲜明对比,Esther Dyson著名地描述了以下方法:
使用桌子存放物品就像开车开车然后将其分解以放入车库一样。 它可以在早上再次组装,但最终有人问这是否是最有效的停车方式。
确实。 下次见。
翻译自: https://www.ibm.com/developerworks/opensource/library/j-db4o2/index.html
用sql查询姓名和身份证