在java中实现sql的select函数功能
发表于:2007-07-04来源:作者:点击数:
标签:
在 java 中实现 sql 的select函数功能 ――通过简单的机制实现对象数组或集合的选择,过滤,和排序 David Rappoport 摘要 在“filter collections”中,David Rappoport 讲述了一个过滤集合对象的简单方法。在本文中,他扩展了自己的思想,并展示了怎样实现,
在
java中实现
sql的select函数功能
――通过简单的机制实现对象数组或集合的选择,过滤,和排序
David Rappoport
摘要
在“filter collections”中,David Rappoport 讲述了一个过滤集合对象的简单方法。在本文中,他扩展了自己的思想,并展示了怎样实现,如同用sql的select处理关系
数据库中的表一样,处理对象数组或集合。他描述了用于选择,过滤,排序队形数组或集合的通用机制。通过这种机制,可以在java的对象数组或集合上实现sql的select函数功能。(2004-11-22)。
假设你希望以表格的形式,如行列的形式,显示数组或集合中的数据,并且希望可以选择显示的属性。另外,你还希望只显示满足一定条件的数据子集,希望用自定义先后顺序的的排序标准对数据排序。
这种类型的功能是sql的select函数提供的:在sql语句中,可以定义数据源(通过from指定);可以定义返回的属性(通过在select后面指定返回列);可以添加条件(where子句);并且可以定义数据显示的顺序(order by子句)。
本文讲述了一些使你能把这些函数使用在任何类型的对象数组和集合上的类和接口。为了实现这些函数功能,机制描述使用了一些设计模式。本文提出的方法有一下一些好处:
1。通过本文的代码(可以
下载。。。),你可以在不影响原始数据的前提下,以不同的方式显示数据。
2。排序,过滤和选择制定的属性十分简单,并且结果能很快得到。
3。通过这些类和接口,你可以独立的考虑select语句的各部分,从而可以产生纯净,可复用,可扩展的代码。
但是,你可能也会猜到,机制使用的是通用接口,如果你没有很好的理解它,可能会滥用它。
机制的类和接口
下面的列表给出了我们需要的类和接口的简短说明。后文中,你将看到他们怎么协作,实现设计的功能。每一个类和接口都非常简单,只含有一个到两个函数。
1.接口Invoker包含唯一一个函数:public boolean invoke(Object o)。通过这个接口,可以封装函数调用并返回函数结果。后文你将看到他是怎样使用的。
2.接口Condition也只包含一个函数:public boolean passes(Object o)。使用这个函数,可以检测任意类是否满足任意类型的条件,并返回true或false。同样,你也将在后文中看到本接口的使用。
3.类SelectStatement代表整个select语句,包含select,from,where和order by等子句。
4.类SelectInstruction表示select子句中的一个单一部分,选定列并指定显示时该列的名字。一个完整的select子句用一个SelectInstruction对象数组表示。
5.类OrderInstruction代表order by子句的一个单一部分,整个order by子句由一个OrderInstruction对象数组构成。
6.类Executor执行包含了整个执行逻辑的SelectStatement。
7.类ResultTable代表执行SelectStatement后返回的结果集。它用Object[][]的形式组织结果数据,从而可以容易的转换和显示数据。
下面将讲述这些类和接口时怎样协作的。类图显示了它们之间的关联。
一个SelectStatement含有一个对象集合或数组(因为集合和数组都只包含统一类型的对象,所以对象的类型没有关系);一个SelectInstruction对象数组;一个Condition对象数组;和一个OrderInstruction对象数组。然后把SelectStatement对象传入Excuter对象,在那里执行这个statement并返回一个ResultTable。
接下来的问题是,SelectInstruction,Condition,OrderInstruction和ResultTable,这些类都是做什么的?我们为什么需要他们?首先,不要一下全看他们,这可能会使你迷惑。一个一个的看,每一个类都是易于理解和使用的。
每一个SelectInstruction代表你希望在结果集中出现的一个列(或者对象的一个属性)。你为他定义一个名字和Invoker。名字将作为列标题出现,Invoker将在集合中的每一个对象上触发。在某个对象上触发后,Invoker在对应列中返回该对象的对应属性。这项函数功能组成了select子句,在这个子句中,可以定义显示的属性,并用as定义列名(如select speed as Max_speed)。注意:你也可以只有一个SelectInstruction,其中的列名串为*。在这种情况下,类中的所有getter()方法都被invoker包覆,其结果都出现在ResultSet中。
每一个Condition代表一个条件,源数据集合中每一个对象都需满足此条件。这个类对应于where子句,通过它可以通过给定的条件排除任意数目的行。注意:如果想要显示全部对象,只需传入空的Condintion[]即可。
每一个OrderInstruction代表一个怎样排序数据的规则,它由一个Invoker和一个Comparator组成。Invoker的invoke(Object o)方法(作用于对象集合中的一个对象时)会返回一个结果,并于作用于集合中其他对象返回的结果进行比较,从而决定结果数据的排序位置。Comparator(如果实现了)比较两个对象在invoke作用下返回的结果。如果comparator没有实现,结果将基于它们各自的compareTo(Object o)方法(假定他们都实现了java.lang,Comparatable)进行比较。
数组中的OrderInstruction之间的顺序也非常重要:OrderInstruction对象在数组中出现的越早,它的优先级就越高。这就是说,可以通过应用第一各OrderInstruction,忽略后面的OrderInstruction对象,决定优先级。OrderInstruction类对应于select语句中的order by子句。注意:如果你并不需要对结果进行排序,只需简单的传入空OrderInstrument[]。
最后,Executor以SelectStatement作为输入,基于Condition[]过滤对象,基于OrderInstruction[]排序过滤后的对象,然后使用SelectInstuction[]选择需要用来显示的属性。为了便于传送,结果数据放入ResultTable。
ResultTable有一个同样简单的接口:它定义了一个public String[] getColumnNames()方法,按序返回所有列名。它还定义了一个public Object[][] getResultData()方法,返回选择、过滤和排序后的行列数据。
为什么要用通用接口?
正如前文所说,本文的
解决方案依赖于一些通用的方法调用--即Invoker的invoke(Object o)方法和Condition的passes(Objdec o)方法。我们为什么需要它们?它们能作什么?
由于我们我们提供的解决方案平等的对待集合或数组中的对象,而不针对特定的对象类型;另外,选择、过滤和排序都是基于对象自身属性的,我们的选择只有两个:反射或者通用接口。反射使我们能构在运行的时候查找对象的方法,并指定调用方法的名字。但是,通常不在产品环境中使用反射,因为这样会使编译时问题推迟到运行时进行,并且反射的
性能一般不如编译形成代码好。代替反射的选择是使用通用接口:通用接口适用于所有类型的对象,在内部将对象映射到对应的类,并根据其类别处理对象。这就是Invoker接口所作的:它使Executor可以让Invoker调用类型相关的方法,从而同样的处理所有类型的对象。Condition接口使用了同样的思想。
注意:Apache基金组织的集合api定义了一个Predicate,实现了本文中Condition接口同样的功能,定义了一个Clouser接口,对应于本文的Invoker接口。笔者认为这些接口名字都不是很好,担确实受到了这个api的启发。
本方案涉及的设计模式
本文的解决方案用到了责任链模式:在集合对象中顺序使用条件对象和排序对象。一个对象只有满足前面的条件,才会被后面的条件对象处理;只有前面的OrderInstruction对象无法排序时,后面的OrderInstruction对象才会发生作用。
本文还用到了聚合模式。java.util.Collection类的sort()方法只使用一个List和一个Comparator作参数。但是,我们希望基于一个comparator数组比较对象。通过创建一个我们的comparator,在内部顺序的调用其他的comparator,得以实现。如果你对这种聚合方式敢兴趣,请查看源代码。
简单的例子:使用我们的类
让我们看一个例子:类Car有三个属性:String color, double maxSpeed, boolean fourWheelDrive。
在你的应用中,可以支持基于上面三个属性作条件的搜索:用户可以输入喜欢的颜色查询,或者指定期望的最大速度。
你可能希望只显示颜色和最大时速,可能希望按照最大时速排序,第二排序是颜色。(实际上,你通常需要让用户决定选择什么,怎样排序。)
下面是实现上面功能的步骤:
1.定义对象数组或集合,并从数据源填充入对象。
Object[] data = getDataFromSomewhere();
或者:
Collection data = getDataFromSomewhere();
2.为你选择属性定义SelectInstruction:
//Define the Color column
Invoker colorInvoker = new Invoker(){
...public Object invoke(Object o){
......return ((Car)o).getColor();
...}
};
SelectInstruction colorSelectInstruction = new SelectInstruction("COLOR", colorInvoker);
//Define the maxSpeed column
Invoker speedInvoker = new Invoker(){
...public Object invoke(Object o){
......return new Integer(((Car)o).getMaxSpeed());
...}
};
SelectInstruction maxSpeedSelectInstruction = new SelectInstruction("MAXIMUM_SPEED", speedInvoker);
然后把这两个SelectInstruction对象加入SelectInstruction[]:
SelectInstruction [] instructions = new SelectInstruction [2];
instructions [0] = colorSelectInstruction;
instructions [1] = maxSpeedSelectInstruction;
3.创建两个条件对象,分别对应于用户选择的条件:
final Color color = getColorFromUserInput();
Condition colorCondition = new Condition {
public boolean passes(Object o){
return ((Car)o).getColor().equals(color);
}
};
final int maxSpeed = getSpeedFromUserInput();
Condition maxSpeedCondition = new Condition {
public boolean passes(Object o){
return ((Car)o).getMaxSpeed() >= maxSpeed;
}
}
然后把这些Condition对象加入一个Condition[]:
Condition[] conditions = new Condition[2];
conditions[0] = colorCondition;
conditions[1] = maxSpeedCondition;
4.创建两个OrderInstruction对象,分别对应于各个排序标准:
//Note: We supply a null Comparator for this OrderInstruction, since
//the default ordering behavior of String suits our purposes.
//We can also reuse the invokers we defined for the SelectInstructions.
OrderInstruction colorOrderInstruction = new OrderInstruction(colorInvoker, null);
//Note: We supply a custom Comparator for this OrderInstruction, since the
//default ordering behavior of Integer (lower number first) does not suit our purposes.
//We want to show the higher speed at the top! (This corresponds to specifying DESC for order by
//in
SQL.)
OrderInstruction speedOrderInstruction = new OrderInstruction(speedInvoker, new Comparator(){
public int compare(Object o1, Object o2){
if(o1 == null && o2 == null)return 0;
if(o1 == null) return - 1;
if(o2 == null) return 1;
//By comparing o2 to o1, we get the opposite order.
return ((Integer)o2.compareTo(o1);
}
});
然后把这些OrderInstruction对象加入OrderInstruciton[]:
OrderInstruction [] orderInstructions = new OrderInstruction [2];
orderInstructions[0] = colorOrderInstruction;
orderInstructions [1] = speedOrderInstruction;
5.加入选择指令,数据,条件和排序指令到SelectStatement中:
SelectStatement selectStatement = new SelectStatement(selectInstructions, data,
conditions, orderInstructions);
6.执行这个Statemment并得到返回结果:
ResultTable resultTable = new Executor().execute(selectStatement );
7.操作得到的结果级。比如:
System.out.print("Columns:");
for(int i = 0; i < resultTable.getColumnNames().length; i ++){
System.out.print("\t");
System.out.print(resultTable.getColumnNames()[i]);
}
Object[][] resultData = resultTable.getResultData();
for(int i = 0; i < resultData.length; i ++){
System.out.println();
System.out.print("Row " + i + " :");
for(int e = 0; e < resultData[i].length; e ++){
System.out.print("\t");
System.out.print(resultData[i][e]);
}
}
这太复杂了,帮帮我吧!
如果你只想显示所有public getter()提供的属性该怎么做?如下代码就好了:
SelectInstruction[] selectInstructions = new SelectInstruction[]{new SelectInstruction("*", null)};
ResultTable resultTable = new Executor().execute(new SelectStatement(selectInstructions, data);
正如你所看到的,构建一个简单的java SELECT statement如同构建一个简短的SQL statement一样容易,你想使用的特点越多,问题就会越复杂。但是,你可以用invoker,conditions,和comparator实现SQL statement实现不了的事情。毕竟,这才是你是一个
程序员而不是数据库设计者的原因。
一些属于读者的问题
本文只提供了实现select函数功能的解决方案,并且仅限于对单个数组或集合的操作。如果我们需要操作多个数组和集合中的数据该怎么办?如何实现insert,update和delete呢?写一个解析器,以sql statement作为输入,创建对象描述,并应用于多数据源会是什么样子?如果在java中有完全的sql api,就可以像用sql处理数据库一样,用java处理缓存的数据库对象!现在。这些问题及一些其他的问题,都是读者的练习了!
原文转自:http://www.ltesting.net