2.??BM???? 2012-01-05 13:37:19 by SeaCat
通过BM生成SQL查询的基本逻辑是:
select 字段1, 字段2, 字段3, ... , 字段n from {baseTable} as {alias}
其中,如果某个字段定义的name和physicalName不一致,对此字段将生成select {physicalName} as {name}这样的SQL语句。
如果某个字段希望定义在BM中,但又不希望让该字段出现在select语句中(例如,只用于更新的字段),可以设置该字段的forSelect="false"属性。
如果某个字段不是基于实际存在表字段,而是一段SQL表达式,或者希望对该字段调用SQL函数进行处理,那么可以设置该字段的expression属性。例如,下面的例子在获取creation_date字段时,调用oracle内建to_char()函数进行格式化:
<bm:field name="creation_date" expression="trunc(user_name)" />
如果BM可能会用于多种数据库,请谨慎使用自定义的SQL语句,以免带来SQL语法兼容性问题。
实际应用中我们经常需要将多个表join在一起查询。在BM中,通过配置基表与其它表的关系,可实现这个需求。
在以下的例子中,我们假设有一个dept表存储公司中的部门,emp表存储公司中的员工,每个员工归属于一个特定的部门。首先,对部门表建立BM:
<bm:model xmlns:bm="http://www.aurora-framework.org/schema/bm" baseTable="dept" >
<bm:fields>
<bm:field name="deptno" databaseType="BIGINT" dataType="java.lang.Long"/>
<bm:field name="dname" databaseType="VARCHAR" dataType="java.lang.String"/>
</bm:fields>
<bm:primary-key>
<bm:pk-field name="empno"/>
</bm:primary-key>
</bm:model>
然后,对前面一节的例子进行扩展,建立emp与dept表的关联关系,并在emp表中引用
<?xml version="1.0" encoding="UTF-8"?>
<bm:model xmlns:bm="http://www.aurora-framework.org/schema/bm" alias="e" baseTable="EMP" needAccessControl="false">
<bm:fields>
<bm:field name="empno" databaseType="BIGINT" datatype="java.lang.Long"/>
<bm:field name="employee_name" databaseType="VARCHAR" datatype="java.lang.String" physicalName="ename"/>
<bm:field name="job" databaseType="BIGINT" datatype="java.lang.String"/>
<bm:field name="mgr" databaseType="BIGINT" datatype="java.lang.Long"/>
<bm:field name="hiredate" databaseType="DATE" datatype="java.sql.Date"/>
<bm:field name="deptno" databaseType="BIGINT" datatype="java.lang.Long"/>
<bm:field name="sal" databaseType="FLOAT" datatype="java.lang.Long"/>
<bm:field name="comm" databaseType="FLOAT" datatype="java.lang.Long"/>
</bm:fields>
<bm:primary-key>
<bm:pk-field name="empno"/>
</bm:primary-key>
<bm:relations>
<bm:relation name="d" joinType="LEFT OUTER" refModel="test.dept">
<bm:reference foreignField="deptno" localField="deptno"/>
</bm:relation>
<bm:relation name="m" joinType="LEFT OUTER" refModel="test.emp">
<bm:reference expression="e.mgr = m.empno and m.deptno is not null"/>
</bm:relation>
</bm:relations>
<bm:ref-fields>
<bm:ref-field name="department_name" relationName="d" sourceField="dname"/>
<bm:ref-field name="manager_name" relationName="m" sourceField="employee_name"/>
</bm:ref-fields>
<bm:data-filters>
<bm:data-filter enforceOperations="query" expression="e.hiredate is not null"/>
</bm:data-filters>
<bm:query-fields>
<bm:query-field field="empno" queryOperator="="/>
<bm:query-field field="deptno" queryOperator="="/>
<bm:query-field field="employee_name" queryOperator="like"/>
<bm:query-field name="hiredate_from" dataType="java.sql.Date" queryExpression="e.hiredate > ${@hiredate_from}"/>
<bm:query-field name="hiredate_to" dataType="java.sql.Date" queryExpression="e.hiredate <= ${@hiredate_to}"/>
</bm:query-fields>
</bm:model>
在上例中,<relations>部分配置BM与其他BM的JOIN关系。通过relation标签定义一个与其他BM的关联,在下面通过1..n个reference标签定义关联的条件。上例中第一个关联BM:
<bm:relation name="d" joinType="LEFT OUTER" refModel="test.dept">
<bm:reference foreignField="deptno" localField="deptno"/>
</bm:relation>
表示与名为test.dept的BM进行left outer join,通过下面的reference标签,设置以本BM中的deptno字段(由localField属性指定)等于test.dept的deptno字段(由foreignField属性指定),来构造join条件。<relation>的name属性,是对此关联关系的命名,在别的地方将通过这个名字来引用,也相当于被关联的表的别名。这样,形成的sql语句将是:
FROM EMP e
LEFT OUTER JOIN dept d ON e.deptno = d.deptno
如果有多个关联字段(例如联合主键),就设置多个<reference>标签,逐一设置localField和foreignField属性。如果关联条件比较复杂,不是简单的A表某字段=B表某字段这样的形式,可以通过reference的expression属性来设置join的SQL表达式,如第二个reference:
<bm:relation name="m" joinType="LEFT OUTER" refModel="test.emp">
<bm:reference expression="e.mgr = m.empno and m.deptno is not null"/>
</bm:relation>
这里,EMP表又与自己进行left outer join,join条件由expression属性设定。这将构成如下SQL:
FROM EMP e
LEFT OUTER JOIN EMP m ON e.mgr = m.empno and m.deptno is not null
join条件设置好之后,就可以在<ref-fields>部分设置要引用的关联表中的字段。上面的例子中:
<bm:ref-fields>
<bm:ref-field name="department_name" relationName="d" sourceField="dname"/>
<bm:ref-field name="manager_name" relationName="m" sourceField="employee_name"/>
</bm:ref-fields>
我们将员工所在部门的名字和上级主管的名字取出来,并给它们分别命名为department_name和manager_name。其中,ref-field的relationName属性为要引用的表在前面所设置的relation的名字,sourceField为要引用的字段在关联的BM中的字段命名。
注意:同一个BM中,所有字段,包括基本字段和引用的字段,其name属性必须唯一。如果基表中已经有了一个名叫name的字段,再引用其他表中也叫name的字段,就必须通过name属性为这个引用字段起一个新的不重复的名字。
经过上述步骤,设置好关联的BM和引用的字段之后,最终形成的SQL语句将是:
SELECT e.empno,e.ename AS employee_name,e.job,e.mgr,e.hiredate,e.deptno,e.sal,e.comm,d.dname AS department_name,m.ename AS manager_name
FROM EMP e
LEFT OUTER JOIN dept d ON e.deptno = d.deptno
LEFT OUTER JOIN EMP m ON e.mgr = m.empno and m.deptno is not null
通常,一个典型的查询界面会列出一些字段,让用户输入条件,然后根据用户输入,列出符合条件的结果。BM的<query-fields<部分可以定义哪些字段是可用于查询的,以及查询条件是什么。在本例中:
<bm:query-fields>
<bm:query-field field="empno" queryOperator="="/>
<bm:query-field field="deptno" queryOperator="="/>
<bm:query-field field="employee_name" queryOperator="like"/>
<bm:query-field name="hiredate_from" dataType="java.sql.Date" queryExpression="e.hiredate > ${@hiredate_from}"/>
<bm:query-field name="hiredate_to" dataType="java.sql.Date" queryExpression="e.hiredate <= ${@hiredate_to}"/>
</bm:query-fields>
这里定义了5个查询字段。以第二个为例,指定字段deptno为查询参数,查询操作符为"=",即查询条件为deptno=(用户输入的值)。为测试这个查询字段的效果,我们在浏览器中输入:
[Web目录]/autocrud/test.emp_for_query/query?deptno=10
可以看到,返回的查询结果是只包含deptno=10的emp记录:
{"result":{"record":[{"hiredate":"2011-07-26 00:00:00","comm":100,"empno":110,"job":"MANAGER","deptno":10,"manager_name":"BLAKE","department_name":"ACCOUNTING","mgr":7698,"sal":3000,"employee_name":"jack"},{"hiredate":"2011-06-20 00:00:00","comm":0,"empno":3287,"job":"mas","deptno":10,"manager_name":"test","department_name":"ACCOUNTING","mgr":3287,"sal":3000,"employee_name":"test"},{"hiredate":"1981-06-09 00:00:00","comm":550,"empno":7782,"job":"MANAGER","deptno":10,"manager_name":"dsf","department_name":"ACCOUNTING","mgr":7839,"sal":2695,"employee_name":"CLARK"},{"hiredate":"1982-01-23 00:00:00","empno":7934,"job":"1","deptno":10,"manager_name":"CLARK","department_name":"ACCOUNTING","mgr":7782,"sal":1300,"employee_name":"a%bc"},{"hiredate":"2011-07-26 00:00:00","comm":40,"empno":8000,"job":"masjs","deptno":10,"department_name":"ACCOUNTING","mgr":2,"sal":300,"employee_name":"sdf"}]},"success":true}
如果再加上hiredate_from参数:
[Web目录]autocrud/test.emp_for_query/query?deptno=10&hiredate_from=2011-1-1
可以看到,返回结果进一步筛选,只返回deptno=10,并且hiredate在2011-1-1之前的记录。
{"result":{"record":[{"hiredate":"2011-07-26 00:00:00","comm":100,"empno":110,"job":"MANAGER","deptno":10,"manager_name":"BLAKE","department_name":"ACCOUNTING","mgr":7698,"sal":3000,"employee_name":"jack"},{"hiredate":"2011-06-20 00:00:00","comm":0,"empno":3287,"job":"mas","deptno":10,"manager_name":"test","department_name":"ACCOUNTING","mgr":3287,"sal":3000,"employee_name":"test"},{"hiredate":"1981-06-09 00:00:00","comm":550,"empno":7782,"job":"MANAGER","deptno":10,"manager_name":"dsf","department_name":"ACCOUNTING","mgr":7839,"sal":2695,"employee_name":"CLARK"},{"hiredate":"1982-01-23 00:00:00","empno":7934,"job":"1","deptno":10,"manager_name":"CLARK","department_name":"ACCOUNTING","mgr":7782,"sal":1300,"employee_name":"a%bc"},{"hiredate":"2011-07-26 00:00:00","comm":40,"empno":8000,"job":"masjs","deptno":10,"department_name":"ACCOUNTING","mgr":2,"sal":300,"employee_name":"sdf"}]},"success":true}
每个<query-field>定义一个可以查询的字段。如果该字段是BM中已经定义过的字段,就设置field属性为字段的名称,例如<query-field field="deptno" />。如果查询参数的名字并不直接对应BM中已定义的字段,如上例中,我们要对hiredate字段进行区间查询,可能会输入hiredate_from和hiredate_to两个值,分别代表区间的上下限,而BM中并没有一个叫hiredate_from的字段。这时,就直接设置query-field的name属性。如果该参数不是字符串类型,还应设置datatype属性。
如果查询条件是简单的字段+操作符模式,可以设置query-field的queryOperator="操作符"。例如,queryOperator="=",代表字段="输入的值";queryOperator=">=",代表字段>=输入的值。
如果查询条件比较复杂,不能简单地用某字段=某值的方式来表达,可以用queryExpression来设定任意的SQL表达式或子查询语句。例如:
<bm:query-field name="hiredate_from" dataType="java.sql.Date" queryExpression="e.hiredate > ${@hiredate_from}"/>
这个配置表示,如果用户提交参数中包含hiredate_from,就拼接 "hiredate > (用户输入的hiredate值) "这样一个查询条件。其中,${@hiredate_from}表示用户输入的名为hiredate_from的参数值,其完整XPath路径是${/parameter/@hiredate_from},对于SQL查询操作来说,缺省的传入参数是/parameter,所以可以用相对路径@hiredate_from来表示。
在queryExpression中,如需使用基表字段,应注意使用别名,以避免多表关联时不同表存在同名字段,而引起的SQL错误。
再举一个自定义查询字段的例子,假设希望查到是管理者的员工,而管理者就是至少有一个员工的直接上级是其本人的员工。这个查询无法直接通过对基表字段的筛选来完成,需要使用SQL子查询,例如:
select ... from emp e where exists ( select 1 from emp e1 where e1.mgr = e.empno )
那么,我们可以定义一个名为is_manager的参数,如果这个参数传递了值,就在构造上述子查询条件:
<bm:query-field name="is_manager" queryExpression="exists ( select 1 from emp e1 where e1.mgr = e.empno )"/>
很多时候,我们需要对查询SQL添加一些限制性的条件。例如,当前用户只能看他所在的部门的员工,或者只列出状态不等于作废的订单。不论用户输入什么查询参数,这些限制条件都是必须加上的。这时,我们可以配置BM的<data-filters>标记,添加必需的数据筛选条件:
<bm:data-filters>
<bm:data-filter enforceOperations="query" expression="e.hiredate is not null"/>
</bm:data-filters>
这表示,在执行查询的时候,e.hiredate is not null这个条件将会被无条件地加上。如果我们传递一个deptno=10的查询条件,可以看到对该BM执行query操作时候,实际拼接的SQL语句是:
SELECT e.empno,e.ename AS employee_name,e.job,e.mgr,e.hiredate,e.deptno,e.sal,e.comm,d.dname AS department_name,m.ename AS manager_name
FROM EMP e
LEFT OUTER JOIN dept d ON e.deptno = d.deptno
LEFT OUTER JOIN EMP m ON e.mgr = m.empno and m.deptno is not null
WHERE e.hiredate is not null AND e.deptno = ?
如果有多个限制条件,可以设置多个data-filter标记,每一个data-filter设置的expression属性会用and连接起来,再与可选查询条件做and连接,形成最终的where从句。
缺省状况下,限制条件会被拼接到所有可包含where从句的SQL操作中,包括update,delete。这样可以实现比较严密的记录级权限控制。例如,要求用户只能操作自己创建的订单,可以设置created_by=${/session/@user_id}。即使用户执行update操作,提交了一个别人创建的订单号,后台所执行的SQL语句将类似于:
update ord_order set .... where order_id=${/parameter/@order_id} and created_by=${/session/@user_id}
由于where从句的表达式不成立(其他人创建的订单,created_by不等于当前用户id),最终也不会有记录被更新。
如果希望某些限制条件只在特定操作时执行,例如只在select中出现,不在update中出现,或者只在select,update中出现,不在delete中出现,那么可以用enforceOperations属性,设定该data-filter希望被使用的操作,用逗号分隔每一个操作,如:enforceOperations="query,update"
在以autocrud方式执行BM的查询时,可通过传递特定的参数,来控制查询结果是否要分页,以及分页的配置
Table 1. 分页控制参数
参数 含义 _fetchall 是否返回所有记录,缺省为false,即启用分页。如果设置为true,将一次向客户端传递查询结果中所有的记录。 pagesize 每页记录数 pagenum 返回第几页 _autocount 是否自动计算记录总数。如果设置为true,将自动执行select count(1) from (实际查询语句),并将结果放置到totalCount属性中。Aurora标准分页控件需要总记录数来显示分页相关的属性。
例如,在浏览器中输入以下地址:
[Web目录]/autocrud/test.emp_for_query/query?pagenum=2&pagesize=3&_autocount=true
即表示,按每页3条记录,返回第2页的数据,并自动统计记录总数。返回结果如下:
{"result":
{"record":
[
{"hiredate":"1981-02-22 00:00:00","comm":500,"empno":7521,"job":"SALESMAN","deptno":30,"manager_name":"BLAKE","department_name":"SALES","mgr":7698,"sal":1375,"employee_name":"WARD"},
{"hiredate":"2007-09-01 00:00:00","comm":1400,"empno":7654,"job":"SALESMAN","deptno":30,"manager_name":"BLAKE","department_name":"SALES","mgr":7698,"sal":1250,"employee_name":"MARTIN"},
{"hiredate":"1981-09-08 00:00:00","comm":0,"empno":7844,"job":"SALESMAN","deptno":30,"manager_name":"BLAKE","department_name":"SALES","mgr":7698,"sal":1650,"employee_name":"TURNER"}
],
"totalCount":17
},
"success":true
}
以下参数可以控制查询结果的排序:
Table 2. 排序控制参数
参数 含义 ORDER_FIELD 用于排序的字段,与 ORDER_TYPE 排序的顺序,可以是asc或desc,必须与ORDER_FIELD同时时候 _ORDER_FIELD_PARAM_NAME 用于传递排序字段名称的参数名。如果不能用缺省的ORDER_FIELD来命名排序字段,可以通过这个字段来设置排序字段参数的实际名称。例如,设置_ORDER_FIELD_PARAM_NAME=my_order,然后设置my_order=ename,即表示按照ename字段排序。 _ORDER_TYPE_PARAM_NAME 用于传递排序顺序的参数名,如果不能用缺省的ORDER_TYPE来命名排序方式,可以通过这个字段来设置排序方式参数的实际名称。
例如,在浏览器中输入以下地址:
[Web目录]/autocrud/test.emp_for_query/query?ORDER_FIELD=employee_name&ORDER_TYPE=desc
查看执行日志,生成的SQL将是:
SELECT e.empno,e.ename AS employee_name,e.job,e.mgr,e.hiredate,e.deptno,e.sal,e.comm,d.dname AS department_name,m.ename AS manager_name
FROM EMP e
LEFT OUTER JOIN dept d ON e.deptno = d.deptno
LEFT OUTER JOIN EMP m ON e.mgr = m.empno and m.deptno is not null
WHERE e.hiredate is not null
ORDER BY ename desc
此外,也可以在BM上设置defaultOrderBy属性,如果客户端没有传递排序相关的参数,就使用这个属性设置的表达式,来构造order by部分。例如:
<bm:model defaultOrderBy="deptno asc,employee_name desc">
...
</bm:model>
这样,如果直接访问该BM的/query方法,生成的的SQL将包含:ORDER BY deptno asc,employee_name desc
如果BM自动生成的查询SQL无法满足实际需求,也可以使用自定义SQL语句来实现查询。在下面的例子中,我们使用Oracle数据库特有的group by rollup语法,来实现对员工数据按部门,职务的分类汇总。
<bm:model xmlns:bm="http://www.aurora-framework.org/schema/bm" needAccessControl="false">
<bm:operations>
<bm:operation name="query">
<bm:query-sql>
select
decode(grouping(e.job),0,d.dname,1,d.dname||' Total:') as department_name,
e.job,
count(e.empno) as total_employee,
sum(e.sal) as total_salary
from emp e, dept d
#WHERE_CLAUSE#
group by rollup(d.dname, e.job)
#ORDER_BY_CLAUSE#
</bm:query-sql>
</bm:operation>
</bm:operations>
<bm:fields>
<bm:field name="department_name"/>
<bm:field name="job"/>
<bm:field name="total_employee" datatype="java.lang.Long"/>
<bm:field name="total_salary" datatype="java.lang.Long"/>
</bm:fields>
<bm:data-filters>
<bm:data-filter expression="e.deptno = d.deptno"/>
</bm:data-filters>
<bm:query-fields>
<bm:query-field name="deptno" queryExpression="d.deptno=${@deptno}" />
<bm:query-field name="job" queryExpression="e.job=${@job}" />
</bm:query-fields>
</bm:model>
在operations部分,定义一个name="query"的operation,在其下用query-sql标记定义查询SQL。这样,在调用BM执行查询时,由于有名为query的operation存在,Aurora框架将会用query-sql中定义的SQL语句,替代框架自动生成的SQL。
自定义查询语句的BM通常也需要设置fields部分,以定义查询结果集的每一个字段的名称、数据类型。如果没有定义fields部分,Aurora会根据SQL查询生成的结果集的实际结构,来读取数据,这样就无法对字段数据类型及名称的精确控制。例如,对于SQL结果集中的数字字段,用缺省方式获取的可能是java.math.BigDecimal对象。如果希望得到的是java.lang.Long,就必须像上例一样,明确定义一个field,设置其datatype属性。
和常规BM一样,自定义SQL也可以定义query-fields及data-filters,来使用可选查询参数和必须的限制条件。所不同的是,如果使用了这些设置,在SQL语句中不能直接出现where部分,而是要用#WHERE_CLAUSE#来标记SQL语句中准备放入where从句的位置。Aurora将根据data-filters和query-fields的设置,先后添加必需的数据筛选条件,以及可选的查询条件,自动生成where部分,再替代#WHERE_CLAUSE#。
在自定义SQL中,如果希望能够像常规BM一样,根据客户端传递的参数来动态排序,就在SQL语句中使用#ORDER_BY_CLAUSE#来标记order by从句放置的地方。如果查询参数中传递了ORDER_FIELD,ORDER_TYPE字段,或者设置了BM的defaultOrderBy属性,Aurora将会自动生成order by从句,替换#ORDER_BY_CLAUSE#标记。
如果访问地址:
[Web目录]/autocrud/test.emp_summary/query?deptno=10&ORDER_FIELD=total_employee&ORDER_TYPE=asc
生成的SQL将是:
select
decode(grouping(e.job),0,d.dname,1,d.dname||' Total:') as department_name,
e.job,
count(e.empno) as total_employee,
sum(e.sal) as total_salary
from emp e, dept d
WHERE e.deptno = d.deptno AND d.deptno=?
group by rollup(d.dname, e.job)
ORDER BY total_employee asc
其中,#WHERE_CLAUSE#和#ORDER_BY_CLAUSE#经过框架自动处理,将被替代为实际的SQL语句。
Comments
1 Responses to the article