DataQL(Data Query Language)DataQL 是一种查询语言。旨在通过提供直观、灵活的语法来描述客户端应用程序的数据需求和交互。
数据的存储根据其业务形式通常是较为简单的,并不适合直接在页面上进行展示。因此开发页面的前端工程师需要为此做大量的工作,这就是 DataQL 极力解决的问题。
请注意 DataQL 不是一门编程语言,它是查询语言。它对逻辑的处理仅限于简单场景。DataQL 的解决问题的重点集中在:数据的聚合和转换以及过程中的简单加工处理。
注释支持://、/*…*/
支持任意空格,提高可读性,支持\t,\n,\r,\f
关键字:
关键字 | 含义 |
---|---|
if | 条件语句的引导词 |
else | 用在条件语句中,表明当条件不成立时的分支 |
return | 三大退出指令之一,终止当前过程的执行并正常退出到上一个执行过程中 |
throw | 三大退出指令之一,终止所有后续指令的执行并抛出异常 |
exit | 三大退出指令之一,终止所有后续指令的执行并正常退出 |
var | 执行一个查询动作,并把查询结果保存到临时变量中 |
run | 仅仅执行查询动作,不保留查询的结果 |
hint | 写在 DataQL 查询语句的最前面,用于设置一些执行选项参数 |
import | 将另外一个 DataQL 查询导入并作为一个 Udf 形式存在、或直接导入一个 Udf 到当前查询中 |
as | 与 import 关键字配合使用,用作将导入的 Udf 命名为一个本地变量名 |
true | 基础类型之一,表示 Boolean 的:真值 |
false | 基础类型之一,表示 Boolean 的:假值 |
null | 基础类型之一,表示 NULL 值 |
标识符:表示查询中的一些实体,如变量名参数名。必须满足正则表达式:[_a-zA-Z][_0-9a-zA-Z]*,一些对象的key可能会超出范围,可以用反引号`xxx`。
分隔符:()、{}、[]、,、:、;(非必须)
运算符:
DataQL 是弱类型定义的查询语言,在DataQL 中所有数据都会被归结到有限的几种类型上。无需定义数据类型结构,在弱类型系统中编写查询会非常方便,它去掉了繁杂的类型定义。
数据类型 | 表示方式 | 详情 |
---|---|---|
布尔 | true 或 false | 表示真假值 |
数值 | 负无穷大 或 0 或 正无穷大 | 浮点数、整数、科学计数法表示的数 |
字符串 | ‘…’ 或 “…” | 字符串 或 单个字符 |
空值 | null 或 NULL | 空值 |
集合 | […] 数组 或 多维数组 | 带有顺序的多组数据的集合 |
对象 | {‘key’:…} 具有键值对的数据体 | DataQL 的对象不支持方法,但是可以具备 Udf 类型的属性。 |
UDF | lambda 函数 或 一个外部的 Udf | 一个外部的 net.hasor.dataql.Udf 接口函数定义。 |
DataQL 中书写的 lambda 函数也被称作为 UDF。 | ||
一个扩展代码片段的定义,也属于 UDF 的范畴。 |
数值类型
UDF
JSON
当两类型不匹配时能自动类型提升。
支持:byte、short、int、long、float、double、BigInt、Decimal
数值宽度默认是int和double
浮点数计算时默认保留20位小数,多余的四舍五入。修改精度使用hint MAX_DECIMAL_DIGITS=20
,更换舍入规则hint NUMBER_ROUNDING = 'HALF_EBEN'
。
import
var
run
return、throw、exit
if
令DataQL变得灵活的存在
if` `(boolean_expression) {
``/* 如果布尔表达式为真将执行的语句 */
} ``else` `{
``/* 如果布尔表达式为假将执行的语句 */
}
hint
取值域:
获取程序传来的参数:<访问符>{<参数名>} ,如${abc}
表达式中的访问符:$(环境栈根),#(环境栈顶),@(整个环境栈(数组形态))
DataQL 查询过程中一般情况下环境栈始终是空的,当遇到 => 操作时。
DataQL 会把 => 符左边的表达式值放入环境栈,当转换结束时 DataQL 会把表达式值从环境栈中删掉。
如果在转换过程中遇到第二次 => 操作,那么会在环境栈顶中放入新的数据。
对象取值:return userInfo.username;
函数结果取值:return userByID({'id': 4}).username;
数组中取值:return userList()[0].username;
下标取值:return userInfo['username'];
连续下标取值:return userList[0]['username'];
数组下标取值:
list[3] = 3
,list[-3] = 7
,下标变量:使用变量代替下标
// 定义一个变量,变量表示要取值的字段名。
var columnKey = 'username';
// 通过下标变量方式来取值
return userInfo[columnKey];
赋值
结果转换是DataQL的核心能力,可以大体归纳为:组装(凭空构造)和变换。
组装:
case1:
var userName = "马三"; // 姓名
var userAge = 23; // 年龄
// 返回一个对象数据,将用户名称和年龄组装到一个对象中
return {
"name" : userName,
"age" : userAge
};
case2:
var data1 = 123; // 值1
var data2 = 456; // 值2
// 返回2个元素的数组
return [
data1, data2
];
case3:
var data = [123, 456];
//它组装了两个字段。但是两个字段分别来自于同一个数组数据的不同元素
return {
"element_0" : data[0], // 123
"element_1" : data[1] // 456
};
数组的变换(是一个一个元素依次按照规则进行转换的)
//首先我们有一个对象数组
var data = [{
"userID" : 1234567891,
"age" : 31,
"name" : "this is name1.",
"nick" : "my name is nick1.",
"sex" : "F",
"status" : true
},{
"userID" : 1234567892,
"age" : 32,
"name" : "this is name2.",
"nick" : "my name is nick2.",
"sex" : "F",
"status" : true
},{
"userID" : 1234567893,
"age" : 33,
"name" : "this is name3.",
"nick" : "my name is nick3.",
"sex" : "M",
"status" : true
}
]
case1:
//得到一个新的数据集,只包含name和age字段
return data => [
{
"name", "age"
}
];
case2:
//只包含name和age字段,同时修改一下字段名
return data => [
{
"userName" : name, // 取 name 字段的值作为 userName 的值
"userAge" : age
}
];
case3:
//返回所有用户名的列表,得到字符串数组
return data => [ name ];
case4:
//将 一组值类型 变换成 一组对象
var data = ["马三", "马四"];
return data => [
{
"name" : # // 符号 "#" 表示在对每个元素进行转换的过程中的那个元素本身。
}
];
//得到的就是
[ {"name":"马三"}, {"name":"马四"} ]
case5:
//为二维数组的每个值都加上一个字符串前缀
var data = [
[1,2,3],
[4,5,6],
[7,8,9]
]
return data => [
# => [ // 在结果转换中对当前元素进行二次转换
"值:" + #
]
]
//查询结果:
[
["值:1","值:2","值:3"],
["值:4","值:5","值:6"],
["值:7","值:8","值:9"]
]
对象的变换
//首先我们有一个对象
var data = {
"userID" : 1234567890,
"age" : 31,
"name" : "this is name.",
"nick" : "my name is nick.",
"sex" : "F",
"status" : true
}
case1:
//通过变换而非组装的方式将其转换为一个数组,内容是一个对象
return data => [ # ];
//---------结果---------
[
{
"userID": 1234567890,
"age": 31,
"name": "this is name.",
"nick": "my name is nick.",
"sex": "F",
"status": true
}
]
case2:
//对象的变换通常都是结构上的变化。对象可以是数组形式的一个元素,数组中可以叠加对象的变换
return data => {
"name",
"info" : {
"age",
"sex"
}
}
//查询结果
{
"name": "this is name.",
"info": {
"age": 31,
"sex": "F"
}
}
使用表达式
case1:
//对结果变换过程中通过表达式来对字段重新计算
return data => {
"name",
"age" : age + "岁",
"old" : age, // 这是有效的,age能够使用多次
"old" : name, // 这是无效的,"old"只会显示上面那个
"sex" : (sex == 'F') ? '男' : '女'
}
通过Lambda模拟for循环
import 'net.hasor.dataql.fx.basic.CollectionUdfSource' as collect;
var map = {
"a" : 123,
"b" : 321
}
var data = [
{
"name" : "马三",
"type" : "a"
},
{
"name" : "n2",
"type" : "b"
}
]
var appendData = (dat) -> {//这个dat是data中的一个对象。lambda表达式只用处理一个元素即可,具体的整个数组的调用逻辑在return的时候。
var newMap = collect.newMap(dat);
run newMap.put('type',map[dat.type])//给这个newMap中设置'type'属性,值是这样取出来的(从map取出(key是(dat.type的value)的键值对)的值)。
return newMap.data()
};
return data => [
appendData(#)
]
// 变量appendData定义为一个函数,这个函数有一个参数。
// 对data进行变换时,变换规则是之前定义的函数appendData,
// 在这个函数中参数为当前data数组中的一个对象元素,
// 所以在函数中是对一个对象进行操作,不用考虑数组的问题。
// 数组是在return的时候对person进行变换,对每个元素#调用定义的lambda函数appendData实现的。
//执行结果
[
{
"name": "马三",
"type": 123
},
{
"name": "n2",
"type": 321
}
]
Tips:
定义函数:可以直接用DataQL语言定义一个函数,然后在后续查询中使用它。
var convertSex = (sex) -> {//定义一个函数
return (sex == 'F') ? '男' : '女'
};
var data = {
"userID" : 1234567890,
"age" : 31,
"name" : "this is name.",
"nick" : "my name is nick.",
"sex" : "F",
"status" : true
};
return data => {
"name",
"age" : age + "岁",
"sex" : convertSex(sex)//使用函数得到值
}
外部函数:DataQL具有官方标准函数库,可以通过import语句导入。
//通过时间函数库获取时间
import 'net.hasor.dataql.fx.basic.DateTimeUdfSource' as time;
return time.now();
//通过json函数库来生成JSON数据
import 'net.hasor.dataql.fx.basic.JsonUdfSource' as json;
return json.toJson([0,1,2])// "[0,1,2]"
Lambda写法
import 'net.hasor.dataql.fx.basic.CollectionUdfSource' as collect;
// 数据
var dataList = [
{"name" : "马一" , "age" : 18 },
{"name" : "马二" , "age" : 28 },
{"name" : "马三" , "age" : 30 },
{"name" : "马四" , "age" : 25 }
]
//只保留年龄大于20岁的数据
// 使用非Lambda的写法----------------
// 年龄过滤逻辑
var filterAge = (dat) -> {
return return dat.age > 20;
};
// 调用 filter 函数
return collect.filter(dataList, filterAge);
//使用Lambda的写法----------------------省略了一个函数定义
var result = collect.filter(dataList, (dat) -> { // lambda 写法
return dat.age > 20;// 年龄过滤条件
});
在DataQL中混合其他语言一起协同处理DataQL查询,需要定义一个片段执行器。
典型的场景是把SQL语句混合在DataQL中。
var dataSet = @@sql(item_code) <%
select * from category where co_code = #{item_code}
%>
return dataSet() => [
{ "id","name","code","body" }
]
// @@sql 是 FunctionX 扩展包中提供的一组片段执行器,这个片段执行器相当于让 DataQL 有能力执行数据库的 SQL 语句。
定义:定义一个片段执行器需要,实现 net.hasor.dataql.FragmentProcess 接口(更多信息请参考开发手册)并且将其注册到 DataQL 环境中
// 方式一:通过Dataql接口
FragmentProcess process = ...
AppContext = appContext = ...
DataQL dataQL = appContext.getInstance(DataQL.class);//获取 DataQL 接口
dataQL.addFragmentProcess("sql", process); //注册片段执行器
// 方式二:通过QueryModule
FragmentProcess process = ...
public class MyQueryModule implements QueryModule {
public void loadModule(QueryApiBinder apiBinder) {
dataQL.addFragmentProcess("sql", process); //注册片段执行器
}
}
使用:
定义一个片段执行器需要使用 @@xxxx(arg1,arg2,arg3,…)<% … %> 语法,其中:
// 在 MySQL 中插入一条数据,并返回自增的ID
var saveData = @@sql(data) <%
insert into my_option (
`key`,
`value`,
`desc`
) values (
#{data.key},
#{data.value},
#{data.desc}
);
select LAST_INSERT_ID();
%>
return saveData(${root});
DataQL的数据模型是通过net.hasor.dataql.domain.DataModel接口表示的,共计4个实现类:ValueModel,ListModel,ObjectModel,UdfModel。
还有一个非常重要的unwrap方法,能解除DataModel形态的封装,直接变成Map/List结构,注意UdfModel类型解开是Udf接口。
引入依赖
<dependency>
<groupId>net.hasor</groupId>
<artifactId>hasor-dataql</artifactId>
<version>4.2.1</version>
</dependency>
通过Hasor使用DataQL
//由于 AppContext 有自身的声明周期特性,因此需要做一个单例模式来创建 DataQL 接口。
public class DataQueryContext {
private static AppContext appContext = null;
private static DataQL dataQL = null;
public static DataQL getDataQL() {
if (appContext == null) {
appContext = Hasor.create().build();
dataQL = appContext.getInstance(DataQL.class);
}
return dataQL;
}
}
//然后在Test中执行查询
HashMap<String, Object> tempData = new HashMap<String, Object>() {{
put("uid", "uid is 123");
put("sid", "sid is 456");
}};
DataQL dataQL = DataQueryContext.getDataQL();
Query dataQuery = dataQL.createQuery("return [${uid},${sid}]");
QueryResult queryResult = dataQuery.execute(tempData);
DataModel dataModel = queryResult.getData();
List list = (List)dataModel.unwrap();
for (Object o : list) {
System.out.println(o);
}
通过JSR223使用DataQL:我这里略
基于底层接口使用DataQL
DataQL 的运行基于三个步骤:
QueryModel queryModel = QueryHelper.queryParser(query1);
QIL qil = QueryHelper.queryCompiler(queryModel, ``null``, Finder.DEFAULT);
Query dataQuery = QueryHelper.createQuery(qil, Finder.DEFAULT);
查询接口(Query)
无论使用何种方式查询都会通过DataQL的查询接口发出查询指令。
查询接口提供了三种不同参数类型的查询重载,所有入参数最后都被转换成为 Map 结构然后统一变换成为 CustomizeScope 数据域形式。
查询结果(QueryResult)
发出DataQL查询后,如果顺利执行完查询,结果会以QueryResult接口形式返回。
/** 执行结果是否通过 EXIT 形式返回的 */
public boolean isExit();
/** 获得退出码。如果未指定退出码,则默认值为 0 */
public int getCode();
/** 获得返回值 */
public DataModel getData();
/** 获得本次执行耗时 */
public long executionTime();
DataQL 的所有返回值都会包装成 DataModel 接口类型。如果想拿到 Map/List 结构数据,只需要调用 unwrap 方法即可。
添加全局变量有两种方式:
在QueryModule中初始化环节添加
AppContext appContext = Hasor.create().build((QueryModule) apiBinder -> {
apiBinder.addShareVarInstance("global_var", "g1");
});
通过DataQL接口添加
DataQL dataQL = appContext.getInstance(DataQL.class);
dataQL.addShareVarInstance("global_var", "g2");
获取全局变量
return global_var;
开发Udf
一个Udf必须是实现了net.hasor.dataql.Udf
接口,注册Udf的方式和添加全局变量相同。
public class UserByIdUdf implements Udf {
private UserManager userManager;
public Object call(Hints readOnly, Object[] params) {
return userManager.findById(params[0]);
}
}
参数中的Udf
DataQL 允许在执行查询时通过参数形式提供 Udf ,这种方式传入的 Udf 在调用时也需要使用 ${…} 来获取
HashMap<String, Object> tempData = new HashMap<String, Object>() {{
put("findUserById", new UserByIdUdf());
}};
AppContext appContext = Hasor.create().build();
DataQL dataQL = appContext.getInstance(DataQL.class);//得到 DataQL接口
Query dataQuery = dataQL.createQuery("return ${findUserById}(1) => { 'name','sex' }"); // 创建查询
QueryResult queryResult = dataQuery.execute(tempData);
DataModel dataModel = queryResult.getData();
函数包(UdfSource)
UdfSource 是一个函数包接口,接口中只有一个 getUdfResource 方法,用于返回函数包中的所有 Udf(Map形式返回)但是一般情况下更推荐使用 UdfSourceAssembly 接口。
使用函数包的好处是可以像平常开发一样编写 Udf,无需考虑 Udf 接口的细节。装配器会自动帮助进行参数和结果的转换。
public class DateTimeUdfSource implements UdfSourceAssembly {
/** 返回当前时间戳 long 格式 */
public long now() { ... }
/** 返回当前系统时区的:年 */
public int year(long time) { ... }
/** 返回当前系统时区的:月 */
public int month(long time) { ... }
/** 返回当前系统时区的:日 */
public int day(long time) { ... }
...
}
// 最后在查询中通过 <函数包名>.<函数> 的形式调用函数包。
inport导入(函数/函数包)
如果 Classpath 中已经存在某个 Udf 类,还可以通过 import 语句导入使用。
import 'net.xxxx.foo.udfs.UserByIdUdf' as findUserById;
return findUserById(1) => { 'name','sex' };
函数包的导入语句相同,只是在调用函数包中函数的时需要指明函数包
import 'net.xxxx.foo.udfs.DateTimeUdfSource' as timeUtil;
return timeUtil.now();
使用注解批量注册
通过 @DimUdf 注解可以快速的声明函数
@DimUdf("findUserById")
public class UserByIdUdf implements Udf {
private UserManager userManager;
public Object call(Hints readOnly, Object[] params) {
return userManager.findById(params[0]);
}
}
通过 @DimUdfSource 注解可以快速的声明函数包:
@DimUdfSource("time_util")
public class DateTimeUdfSource implements UdfSourceAssembly {
...
}
然后在初始化时扫描加载它们
AppContext appContext = Hasor.create().build(apiBinder -> {
QueryApiBinder queryBinder = apiBinder.tryCast(QueryApiBinder.class);
queryBinder.loadUdf(queryBinder.findClass(DimUdf.class));
queryBinder.loadUdfSource(queryBinder.findClass(DimUdfSource.class));
});
外部代码执行器
外部代码片段是 DataQL 特有能力,它允许在 DataQL 查询中混合其它语言的脚本。并将引入的外部语言脚本转换为 Udf 形式进行调用。使用这一特性时需要扩展 FragmentProcess 接口,并注册对应的外部代码执行器。
//外部代码执行器,接收<% %>包裹的代码,然后调用jdbcTemplate的query方法执行具体的SQL查询
@DimFragment("sql")
public class SqlQueryFragment implements FragmentProcess {
@Inject
private JdbcTemplate jdbcTemplate;
public Object runFragment(Hints hint, Map<String, Object> paramMap, String fragmentString) throws Throwable {
return this.jdbcTemplate.queryForList(fragmentString, paramMap);
}
}
//在初始化阶段注册这个代码执行器,就可以在查询时使用了这个外部代码片段了
public class MyFragment implements QueryModule {
public void loadModule(QueryApiBinder apiBinder) {
//扫描所有标记了@DimFragment注解的类并加载它
apiBinder.loadFragment(queryBinder.findClass(DimFragment.class));
}
}
//DataQL语句,通过@@指令开启了一段外部代码的定义,执行器的名字是sql
var dataSet = @@sql(item_code) <%
select * from category where co_code = #{item_code}
%>
return dataSet() => [
{ "id","name","code","body" }
]
资源加载器(Finder)
资源加载器是net.hasor.dataql.FInder,其主要负责import语句导入资源/对象的加载。通常不会接触到它。
import ``'userBean'` `as ub;``//userBean 是 Bean 的名字
return` `ub().name;
SQL 执行器是 DataQL 的一个 FragmentProcess 扩展,其作用是让 DataQL 可以执行 SQL。执行器的实现是 FunctionX 扩展包提供的。使用执行器需要引入扩展包。
<dependency>
<groupId>net.hasor</groupId>
<artifactId>hasor-dataql-fx</artifactId>
<version>4.2.1</version>
</dependency>
配置数据源
//普通方式配置数据源,在Hasor中初始化数据源即可
public class ExampleModule implements Module {
public void loadModule(ApiBinder apiBinder) throws Throwable {
// .创建数据源
DataSource dataSource = null;
// .初始化Hasor Jdbc 模块,并配置数据源
apiBinder.installModule(new JdbcModule(Level.Full, this.dataSource));
}
}
方言
配置方言使用 hint HASOR_DATAQL_FX_PAGE_DIALECT = mysql
,即可设置方言。支持Mysql,Oracle,SqlServer2012,PostgreSQL,DB2,Infomix。
执行SQL
// 声明一个 SQL
var dataSet = @@sql() <%
select * from category limit 10;
%>
// 执行这个 SQL,并返回结果
return dataSet();
SQL参数化
// 声明一个 SQL
var dataSet = @@sql(itemCode) <%
select * from category where co_code = #{itemCode} limit 10;
%>
// 执行这个 SQL,并返回结果
return dataSet(${itemCode});
SQL注入
//SQL注入是为了一些特殊场景需要拼接SQL而准备的,如:动态排序字段和排序规则
// 使用 DataQL 拼接字符串
var orderBy = ${orderField} + " " + ${orderType};
// 声明一个可以注入的 SQL
var dataSet = @@sql(itemCode,orderString) <%
select * from category where co_code = #{itemCode} order by ${orderString} limit 10;
%>
// 执行这个 SQL,并返回结果
return dataSet(${itemCode}, orderBy);
Ognl表达式
//同Mybatis一样,SQL执行器可以将一个对象作为参数传入
// 例子数据
var testData = {
"name" : "马三",
"age" : 26,
"status" : 0
}
// insert语句模版
var insertSQL = @@sql(userInfo) <%
insert into user_info (
name,
age,
status,
create_time
) values (
#{userInfo.name},
#{userInfo.age},
#{userInfo.status},
now()
)
%>
// 插入数据
return insertSQL(testData);
批量操作
DataQL 的 SQL 执行器支持批量 Insert\Update\Delete\Select 操作,最常见的场景是批量插入数据。批量操作必须满足下列几点要求:
// 例子数据
var testData = [
{ "name" : "马一", "age" : 26, "status" : 0 },
{ "name" : "马二", "age" : 26, "status" : 0 },
{ "name" : "马三", "age" : 26, "status" : 0 }
]
// insert语句模版
var insertSQL = @@sql[](userInfo) <%
insert into user_info (
name,
age,
status,
create_time
) values (
#{userInfo.name},
#{userInfo.age},
#{userInfo.status},
now()
)
%>
// 批量操作
return insertSQL(testData);
执行结果拆包
拆包是指将只返回一行一列的数据如count(*),拆解为int类型。
有三种模式,默认为column:
拆包模式可以通过hint改变,hint FRAGMENT_SQL_OPEN_PACKAGE = 'row'
//hint FRAGMENT_SQL_OPEN_PACKAGE = "off"
var dataSet = @@sql() <% select count(*) as cnt from category; %>
var result = dataSet();
// 不指定 hint 的情况下,会返回 category 表的总记录数,返回值为:10。
// 拆包模式变更为 row ,返回值为: { "cnt" : 10 }
// 关闭拆包,返回值为标准的 List/Map: [ { "cnt" : 10 } ]
结果列名拼写转换
是指从数据库查询返回的列名信息,按照某一规则统一处理,如所有key转为驼峰。可以使返回的列信息具有很高的可读性。
hint FRAGMENT_SQL_COLUMN_CASE = "hump"
几个可供配置的值:
分页查询
默认关闭,通过hint FRAGMENT_SQL_QUERY_BY_PAGE = true
打开。
打开分页后经过3个步骤:
// SQL 执行器切换为分页模式
hint FRAGMENT_SQL_QUERY_BY_PAGE = true
// 定义查询SQL
var dataSet = @@sql() <%
select * from category
%>
// 创建分页查询对象
var pageQuery = dataSet();//从数据库查出来的是一个对象,有多种属性,而不是仅有数据
// 设置分页信息
run pageQuery.setPageInfo({
"pageSize" : 10, // 页大小
"currentPage" : 3 // 第3页
});
// 执行分页查询
var result = pageQuery.data();
// 获取分页信息
var info = pageQuery.pageInfo();
由于大部分前端是以1为第一页,而默认情况下SQL执行器是以0为第一页的,所以需要-1,如果是GET方式发布的话,还需要使用转换函数。
import 'net.hasor.dataql.fx.basic.ConvertUdfSource' as convert;
hint FRAGMENT_SQL_QUERY_BY_PAGE = true
...
run queryPage.setPageInfo({
"pageSize" : 5, // 页大小
"currentPage" : (convert.toInt(${pageNumber}) -1)
});
还有第二种方式,DataQL在4.1.8版本中加入FRAGMENT_SQL_QUERY_BY_PAGE_NUMBER_OFFSET
Hint,可以设置让SQL执行器以1作为开始。
数据库事务
SQL执行器本身并不支持事务,需要借助事务函数来实现。
事务函数还可以嵌套使用。
import 'net.hasor.dataql.fx.db.TransactionUdfSource' as tran; //引入事务函数
...
return tran.required(() -> {
... // 事务
return ...
});
...
支持完整的7个传播属性:
类型 | 说明 | 用法 |
---|---|---|
REQUIRED | 加入已有事务 | tran.required(() -> { … }); |
REQUIRES_NEW | 独立事务 | tran.requiresNew(() -> { … }); |
NESTED | 嵌套事务 | tran.nested(() -> { … }); |
SUPPORTS | 跟随环境 | tran.supports(() -> { … }); |
NOT_SUPPORTED | 非事务方式 | tran.notSupported(() -> { … }); |
NEVER | 排除事务 | tran.never(() -> { … }); |
MANDATORY | 要求环境中存在事务 | tran.tranMandatory(() -> { … }); |
多数据源
SQL执行器在4.1.4版本之后提供了通过hint来切换数据源的能力
public class MyModule implements Module {
public void loadModule(ApiBinder apiBinder) throws Throwable {
DataSource defaultDs = ...;
DataSource dsA = ...;
DataSource dsB = ...;
apiBinder.installModule(new JdbcModule(Level.Full, defaultDs)); // 默认数据源
apiBinder.installModule(new JdbcModule(Level.Full, "ds_A", dsA)); // 数据源A
apiBinder.installModule(new JdbcModule(Level.Full, "ds_B", dsB)); // 数据源B
}
}
// 如果不设置 FRAGMENT_SQL_DATA_SOURCE 使用的是 defaultDs 数据源。
// - 设置值为 "ds_A" ,使用的是 dsA 数据源。
// - 设置值为 "ds_B" ,使用的是 dsB 数据源。
hint FRAGMENT_SQL_DATA_SOURCE = "ds_A"
// 声明一个 SQL
var dataSet = @@sql() <% select * from category limit 10; %>
// 使用 特定数据源来执行SQL。
return dataSet();
多条查询
是指一次SQL执行的过程中,包含了一个以上的SQL语句。
var dataSet = @@sql() <%
set character_set_connection = 'utf8';
select * from my_option;
%>
return dataSet();
// 默认返回最后一个SQL语句的结果。
// 可以通过 FRAGMENT_SQL_MUTIPLE_QUERIES hint来控制,例如:保留每一条结果。
在4.1.8版本后加入了@@Mybatis执行器,这是对@@sql执行器的扩展,继承了@@sql的能力,并提供了Mybatis的配置方式,提供了动态SQL的能力。
对比@@sql的优势
var dimSQL = @@mybatis(userName)<%
<select>
select * from user_info where `name` like concat('%',#{userName},'%') order by id asc
</select>
%>;
提供的标签
select
update
insert
delete
foreach:循环拼接SQL
<foreach collection="userIds.split(',')" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
if:判断条件,成立时拼接标签内内容
<if test="userId != null and userId != ''">
and user_id = #{userId}
</if>
依赖:
<dependency>
<groupId>net.hasor</groupId>
<artifactId>hasor-dataql-fx</artifactId>
<version>4.2.1</version>
</dependency>
引入转换函数库:import 'net.hasor.dataql.fx.basic.ConvertUdfSource' as convert;
引入集合函数库:import 'net.hasor.dataql.fx.basic.CollectionUdfSource' as collect;
isEmpty:注意:collect.isEmpty(null) = false// 不支持的基本类型会返回 false
size
merge:返回 List
mergeMap
filter(dataList, filterUDF):根据一个规则来对集合进行过滤。filterUDF类型:Udf/Lambda
var result = collect.filter(dataList, (dat) -> {
return dat.age > 20;
});
filterMap(dataMap, keyFilterUDF)
limit(dataList, start, limit):截取List的一部分,返回一个List
newList
newMap(target):将一个map创建为带状态的Map,具有put(),putAll(),data(),size()方法
mapJoin(data_1,data_2,joinMapping):将两个Map/List进行左连接,joinMaping是Map类型,表示两表的join关系。
import 'net.hasor.dataql.fx.basic.CollectionUdfSource' as collect;
var year2019 = [
{ "pt":2019, "item_code":"code_1", "sum_price":2234 },
{ "pt":2019, "item_code":"code_2", "sum_price":234 },
{ "pt":2019, "item_code":"code_3", "sum_price":12340 },
{ "pt":2019, "item_code":"code_4", "sum_price":2344 }
];
var year2018 = [
{ "pt":2018, "item_code":"code_1", "sum_price":1234.0 },
{ "pt":2018, "item_code":"code_2", "sum_price":1234.0 },
{ "pt":2018, "item_code":"code_3", "sum_price":1234.0 },
{ "pt":2018, "item_code":"code_4", "sum_price":1234.0 }
];
var result = collect.mapJoin(year2019,year2018, { "item_code":"item_code" }) => [
{
"商品Code": data1.item_code,
"去年同期": data2.sum_price,
"今年总额": data1.sum_price,
"环比去年增长": ((data1.sum_price - data2.sum_price) / data2.sum_price * 100) + "%"
}
]
return result;
mapKeyToLowerCase:将Map的Key全部转为小写,如果Key冲突会产生覆盖
mapKeyToUpperCase:将Map的Key全部转为大写,如果Key冲突会产生覆盖
mapKeyToHumpCase:将Map的Key中下划线转为驼峰,如果Key冲突会产生覆盖
mapKeys:提取Map的Key,返回List
mapValues:提取Map的Values,返回List
mapKeyReplace(dataMap, replaceKey):循环遍历每一个Map元素,并对Map的Key进行替换,replaceKey是用于生成新key的函数。
var data = {"key1":1, "key2":2, "key3":3 };
var result = collect.mapKeyReplace(data, (oldKey,value) -> {
return "new_" + oldKey
});
// result = {"new_key1":1, "new_key2":2, "new_key3":3 }
mapValueReplace:同上,不过是对值的处理
list2map(listData, dataKey, convertUDF):将List转为Map,dataKey是键的名字,可以用字符串,也可以直接获取,convertUDF是转换的函数,可以不写。
//通过字符串指明Key字段
var yearData = [
{ "pt":2018, "item_code":"code_1", "sum_price":12.0 },
{ "pt":2018, "item_code":"code_2", "sum_price":23.0 },
{ "pt":2018, "item_code":"code_3", "sum_price":34.0 },
{ "pt":2018, "item_code":"code_4", "sum_price":45.0 }
];
var result = collect.list2map(yearData, "item_code");
// result = {
// "code_1": { "pt":2018, "item_code":"code_1", "sum_price":12.0 },
// "code_2": { "pt":2018, "item_code":"code_2", "sum_price":23.0 },
// "code_3": { "pt":2018, "item_code":"code_3", "sum_price":34.0 },
// "code_4": { "pt":2018, "item_code":"code_4", "sum_price":45.0 }
// };
//使用提取出来的值作为key
var yearData = [ 1,2,3,4,5];
var result = collect.list2map(yearData, (idx,dat)-> {
// Key 提取函数,直接把数组的数字元素内容作为 key 返回
return dat;
},(idx,dat) -> {
// 构造 value
return { "index": idx, "value": dat };
});
// result = {
// "1": { "index": 0, "value": 1 },
// "2": { "index": 1, "value": 2 },
// "3": { "index": 2, "value": 3 },
// "4": { "index": 3, "value": 4 },
// "5": { "index": 4, "value": 5 }
// }
map2list(dataMap, convert):将List转换为Map,convert是转换函数。
// 不指定转换函数
var data = {"key1":1, "key2":2, "key3":3 };
var result = collect.map2list(data);
// result = [
// { "key": "key1", "value": 1},
// { "key": "key2", "value": 2},
// { "key": "key3", "value": 3}
// ]
// 指定转换函数
var data = {"key1":1, "key2":2, "key3":3 };
var result = collect.map2list(data, (key,value) -> {
return { "k" : key, "v" : value };
});
// result = [
// { "k": "key1", "v": 1},
// { "k": "key2", "v": 2},
// { "k": "key3", "v": 3}
// ]
map2string(dataMap, joinStr, convert):将Map转换成字符串,通常在生成Url参数时用到,joinStr表示连接符。
var data = {"key1":1, "key2":2, "key3":3 };
var result = collect.map2string(data,"&",(key,value) -> {
return key + "=" + value;
});
// result = "key1=1&key2=2&key
3=3"
mapSort(dataMap, sortUdf):DataQL中的Map是有序的,因此可以排序。
listSort(dataList, sortUdf):对List进行排序
groupBy(dataList, groupByKey):根据公共字段对数据进行分组。groupByKey是String是要分组的字段名。数据集中需要有一个公共字段。
uniqueBy(dataList, uniqueByKey):根据公共字段去重,只返回第一次出现的。数据集中需要有一个公共字段。
引入函数库:import 'net.hasor.dataql.fx.basic.DateTimeUdfSource' as time;
time.year(time.now())
java.text.SimpleDateFormat
引入函数库:import 'net.hasor.dataql.fx.basic.JsonUdfSource' as json;
引入函数库:import 'net.hasor.dataql.fx.basic.StringUdfSource' as string;
引入函数库:import 'net.hasor.dataql.fx.basic.StateUdfSource' as state;
引入函数库:import 'net.hasor.dataql.fx.web.WebUdfSource' as webData;
引入函数库:import 'net.hasor.dataql.fx.encryt.CodecUdfSource' as codec;