Skip to content

Commit

Permalink
v1.0.79 新增 QueryBuilder设计
Browse files Browse the repository at this point in the history
  • Loading branch information
xixifeng committed Sep 2, 2019
1 parent 230b651 commit 03841ac
Show file tree
Hide file tree
Showing 38 changed files with 1,156 additions and 512 deletions.
71 changes: 48 additions & 23 deletions README.md
Expand Up @@ -5,13 +5,13 @@
<dependency> <dependency>
<groupId>org.fastquery</groupId> <groupId>org.fastquery</groupId>
<artifactId>fastquery</artifactId> <artifactId>fastquery</artifactId>
<version>1.0.77</version> <!-- fastquery.version --> <version>1.0.79</version> <!-- fastquery.version -->
</dependency> </dependency>
``` ```


### Gradle/Grails ### Gradle/Grails
```xml ```xml
compile 'org.fastquery:fastquery:1.0.77' compile 'org.fastquery:fastquery:1.0.79'
``` ```


# FastQuery 数据持久层框架 # FastQuery 数据持久层框架
Expand Down Expand Up @@ -71,7 +71,7 @@ JRE 8+
<beans> <beans>
<bean name="xkdb1" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" <bean name="xkdb1" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close"> init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://192.168.8.10:3305/xk" /> <property name="url" value="jdbc:mysql://db.fastquery.org:3305/xk" />
<property name="username" value="xk" /> <property name="username" value="xk" />
<property name="password" value="abc123" /> <property name="password" value="abc123" />
<property name="filters" value="stat" /> <property name="filters" value="stat" />
Expand All @@ -90,7 +90,7 @@ JRE 8+
<!-- 再配置一个数据源 --> <!-- 再配置一个数据源 -->
<bean name="xkdb2" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" <bean name="xkdb2" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close"> init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://192.168.8.10:3305/xk" /> <property name="url" value="jdbc:mysql://db.fastquery.org:3305/xk" />
<property name="username" value="xk" /> <property name="username" value="xk" />
<property name="password" value="abc123" /> <property name="password" value="abc123" />
</bean> </bean>
Expand Down Expand Up @@ -325,7 +325,7 @@ UserInformation findUserInfoById(Integer id);
## 动态条件查询 ## 动态条件查询


### 采用`Annotation`实现简单动态条件 ### 采用`Annotation`实现简单动态条件
看到这里,可别认为`SQL`只能写在Annotation(注解)里.`FastQuery`还提供了另二种方案: ① 采用`@QueryByNamed`(命名式查询),将`SQL`写入到模板文件中,并允许在模板文件里做复杂的逻辑判断,相当灵活. ② 通过`BuilderQuery`函数式接口构建`SQL`.下面章节有详细描述. 看到这里,可别认为`SQL`只能写在Annotation(注解)里.`FastQuery`还提供了另二种方案: ① 采用`@QueryByNamed`(命名式查询),将`SQL`写入到模板文件中,并允许在模板文件里做复杂的逻辑判断,相当灵活. ② 通过`QueryBuilder`构建`SQL`.下面章节有详细描述.


```java ```java
@Query("select no, name, sex from Student #{#where} order by age desc") @Query("select no, name, sex from Student #{#where} order by age desc")
Expand Down Expand Up @@ -690,7 +690,7 @@ int updateCourse(@Param("name") String name,@Param("credit") Integer credit,Stri
``` ```
关于修改`name`的那个设置项, 有三种可能使它作废: ① name的值是null; ② name的值是""; ③ NameJudge类的ignore方法返回了`true`. 关于修改`name`的那个设置项, 有三种可能使它作废: ① name的值是null; ② name的值是""; ③ NameJudge类的ignore方法返回了`true`.


根据参数动态增减set不同字段,除了用`@Set`实现之外,别忘了还有其他几种解决办法: a.调用内置方法`int executeUpdate(E entity)`,实体的字段若是`null`值,那么,该字段将不会参与set运算; b.使用SQL模版,在里头做逻辑判断; c.采用`BuilderQuery`; d.采用`$表达式`. 开发者将会发现很难不能选择出适合的解决方式. 根据参数动态增减set不同字段,除了用`@Set`实现之外,别忘了还有其他几种解决办法: a.调用内置方法`int executeUpdate(E entity)`,实体的字段若是`null`值,那么,该字段将不会参与set运算; b.使用SQL模版,在里头做逻辑判断; c.采用`QueryBuilder`; d.采用`$表达式`. 开发者将会发现很难不能选择出适合的解决方式.


## 事务 ## 事务


Expand Down Expand Up @@ -936,6 +936,19 @@ public List<Student> findSomeStudent();


**注意**: `$name``:name`这两种表达式的主要区别是——`$name`表示引用的是参数源值,可用于在模板中做逻辑判断,而`:name`用于标记参数位,SQL解析器会将其翻译成`?`号. **注意**: `$name``:name`这两种表达式的主要区别是——`$name`表示引用的是参数源值,可用于在模板中做逻辑判断,而`:name`用于标记参数位,SQL解析器会将其翻译成`?`号.


在模板中`:expression`表达式或`?N`表达式可以作为`SQL`函数的逻辑判断表达式,如跟这些函数一起参与运算:`IF(expr1,expr2,expr3)`,`IFNULL(expr1,expr2)`,`NULLIF(expr1,expr2)`,`ISNULL(expr)`.

```sql
-- 方法的第1个参数的值可以影响where的条件
select t.A from (select 11 as A,22 as B,33 as C) as T where if(?1 > 10,t.B>10,t.C>100)
-- 方法的第2个参数的值可以影响查询集
select if(?2 > 10,'大于10','不大于10') as msg
-- 名称为"number"的参数,其值可以影响where条件
select t.A from (select 11 as A,22 as B,33 as C) as T where if(:number > 10,t.B>10,t.C>100)
-- 名称为"number"的参数,其值可以影响查询集
select if(:number > 10,'大于10','不大于10') as msg
```

允许多个方法绑定同一个模板id. 在模板中使用`${_method}`可以引用到当前方法的`org.fastquery.core.MethodInfo`对象,该对象是反射`java.lang.reflect.Method`的缓存. 允许多个方法绑定同一个模板id. 在模板中使用`${_method}`可以引用到当前方法的`org.fastquery.core.MethodInfo`对象,该对象是反射`java.lang.reflect.Method`的缓存.
例: 根据当前方法名称的不同取不同的`SQL`语句 例: 根据当前方法名称的不同取不同的`SQL`语句


Expand Down Expand Up @@ -982,32 +995,44 @@ org.fastquery.dao.QueryByNamedDBExtend.queries.xml 模板文件的内容:


其中 `${_method.getName()}` 可简写成 `${_method.name}`. 在`Velocity`里调用对象或方法,不是本文的重点,点到为止. 其中 `${_method.getName()}` 可简写成 `${_method.name}`. 在`Velocity`里调用对象或方法,不是本文的重点,点到为止.


## BuilderQuery ## QueryBuilder
上面介绍了`SQL`不仅可以绑定在`@Query`里, 也可以写到`XML`里. 还有另一种方式,**通过函数式构建SQL语句**. 上面介绍了`SQL`不仅可以绑定在`@Query`里, 也可以写到`XML`里. 还有另一种方式,**通过构造QueryBuilder对象**构建`Query`语句.
用法举例: 用法举例:


```java ```java
public interface DefaultDBService extends QueryRepository { @Query
// 分页, 查询语句 和 count语句 通过 builderQuery 构建出来 Page<Map<String, Object>> pageByQueryBuilder(QueryBuilder queryBuilder,Pageable pageable);
@Query
Page<Map<String,Object>> findPage(Integer id,@Param("age")Integer age,Pageable p,BuilderQuery builder);
}
``` ```


如果分页不要求得到总页数,在接口的方法上加`@NotCount`便可(谁说分页一定要执行count语句?). 如果分页不要求得到总页数,在接口的方法上加`@NotCount`便可(谁说分页一定要执行count语句?).


不用去实现那个接口,直接调用: 不用去实现那个接口,直接调用:


```java ```java
DefaultDBService db = FQuery.getRepository(DefaultDBService.class); // 获取Repository实例
Pageable pageable = new PageableImpl(1, 3); UserInfoDBService db = FQuery.getRepository(UserInfoDBService.class);
Integer id = 500;
Integer age = 18; String query = "select id,name,age from userinfo #{#where}";
Page<Map<String, Object>> page = db.findPage(id, age, pageable, m -> { String countQuery = "select count(name) from userinfo #{#where}";
m.setQuery("select id,name,age from `userinfo`");// 设置查询语句 ConditionList conditions = ConditionList.of("age > :age","and id < :id");
m.setWhere("where id < ?1 and age > :age");// 设置条件(这样设计可以让条件复用) Map<String, Object> parameters = new HashMap<>();
m.setCountQuery("select count(`id`) from `userinfo`");// 设置count语句 parameters.put("age", 18);
parameters.put("id", 50);

QueryBuilder queryBuilder = new QueryBuilder(query, countQuery, conditions, parameters);
Page<Map<String, Object>> page = db.pageByQueryBuilder(queryBuilder,new PageableImpl(1, 3));
List<Map<String, Object>> content = page.getContent();
content.forEach(map -> {
Integer age = (Integer) map.get("age");
Integer id = (Integer) map.get("id");
assertThat(age, greaterThan(18));
assertThat(id, lessThan(50));
}); });

List<String> executedSQLs = rule.getExecutedSQLs();
assertThat("断言:执行过的sql有两条",executedSQLs.size(), is(2));
assertThat(executedSQLs.get(0), equalTo("select id,name,age from userinfo where age > ? and id < ? limit 0,3"));
assertThat(executedSQLs.get(1), equalTo("select count(name) from userinfo where age > ? and id < ?"));
``` ```


引用问号表达式(?expression) , 冒号表达式(:expression), 其中?1表示方法的第一个参数,`:age`表示匹配`@Param("age")`那个参数,采用问号或冒号表达式不会有注入问题. 引用问号表达式(?expression) , 冒号表达式(:expression), 其中?1表示方法的第一个参数,`:age`表示匹配`@Param("age")`那个参数,采用问号或冒号表达式不会有注入问题.
Expand Down Expand Up @@ -1048,7 +1073,7 @@ JSONObject callProcedure(String no,@Param("name")String name,String sex,int age,
``` ```


## 分页 ## 分页
要处理查询语句的参数,只需定义方法参数,为了在运行时对参数名称可见就额外加上`@Param`,上面有很多示例.另外,方法的设计还能识别某些特殊的类型,如`BuilderQuery`,`Pageable`,以便核心能智能地将动态构建查询和分页应用于查询中. 要处理查询语句的参数,只需定义方法参数,为了在运行时对参数名称可见就额外加上`@Param`,上面有很多示例.另外,方法的设计还能识别某些特殊的类型,如`QueryBuilder`,`Pageable`,以便核心能智能地将动态构建查询和分页应用于查询中.


- 通过`@QueryByNamed`实现分页 - 通过`@QueryByNamed`实现分页


Expand Down Expand Up @@ -1243,7 +1268,7 @@ String dataSourceName = "xk1";
// 连接池配置 // 连接池配置
Properties properties = new Properties(); Properties properties = new Properties();
properties.setProperty("driverClass", "com.mysql.cj.jdbc.Driver"); properties.setProperty("driverClass", "com.mysql.cj.jdbc.Driver");
properties.setProperty("jdbcUrl", "jdbc:mysql://192.168.8.10:3306/xk1"); properties.setProperty("jdbcUrl", "jdbc:mysql://db.fastquery.org:3306/xk1");
properties.setProperty("user", "xk1"); properties.setProperty("user", "xk1");
properties.setProperty("password", "abc1"); properties.setProperty("password", "abc1");


Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -4,7 +4,7 @@


<groupId>org.fastquery</groupId> <groupId>org.fastquery</groupId>
<artifactId>fastquery</artifactId> <artifactId>fastquery</artifactId>
<version>1.0.77</version> <!-- fastquery.version --> <version>1.0.79</version> <!-- fastquery.version -->
<packaging>jar</packaging> <packaging>jar</packaging>


<name>fastquery</name> <name>fastquery</name>
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/org/fastquery/analysis/AnnotationSynxFilter.java
Expand Up @@ -63,12 +63,12 @@ public void doFilter(Method method) {
if ((sql.indexOf(Placeholder.ID) != -1) && "".equals(id)) { if ((sql.indexOf(Placeholder.ID) != -1) && "".equals(id)) {
this.abortWith(method, sql + "中存在" + Placeholder.ID + ", 那么,@Modifying中的id属性为不能为空"); this.abortWith(method, sql + "中存在" + Placeholder.ID + ", 那么,@Modifying中的id属性为不能为空");
} }

}
// 当返回值为Map 或 JSONObject 或 bean时, Modifying中的id和table值是必选的
if (dependentId(returnType) && ("".equals(id) || "".equals(table))) { // 依赖主键吗? // 当返回值为Map 或 JSONObject 或 bean时, Modifying中的id和table值是必选的
// 1). id 或 table 不能为"" if (dependentId(returnType) && ("".equals(id) || "".equals(table))) { // 依赖主键吗?
this.abortWith(method, String.format("返回值是:%s 因此要求:%s中的id或table的值不能为空字符串.", returnType, modifying)); // 1). id 或 table 不能为""
} this.abortWith(method, String.format("返回值是:%s 因此要求:%s中的id或table的值不能为空字符串.", returnType, modifying));
} }


} }
Expand Down
Expand Up @@ -23,10 +23,13 @@
package org.fastquery.analysis; package org.fastquery.analysis;


import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;


import org.fastquery.core.Modifying; import org.fastquery.core.Modifying;
import org.fastquery.core.Query; import org.fastquery.core.Query;
import org.fastquery.core.QueryBuilder;
import org.fastquery.core.QueryByNamed; import org.fastquery.core.QueryByNamed;
import org.fastquery.util.TypeUtil;


/** /**
* 在QueryRepository中 {@link Modifying} 依赖检测 <br> * 在QueryRepository中 {@link Modifying} 依赖检测 <br>
Expand All @@ -41,9 +44,10 @@ public void doFilter(Method method) {
Modifying m = method.getAnnotation(Modifying.class); Modifying m = method.getAnnotation(Modifying.class);
int queryLen = method.getAnnotationsByType(Query.class).length; int queryLen = method.getAnnotationsByType(Query.class).length;
QueryByNamed queryByNamed = method.getAnnotation(QueryByNamed.class); QueryByNamed queryByNamed = method.getAnnotation(QueryByNamed.class);
if (m != null && queryLen == 0 && queryByNamed == null) { // m存在 并且 queryLen为0 并且 Parameter[] parameters = method.getParameters();
// queryByNamed不存在 boolean hasQueryBuilder = TypeUtil.hasType(QueryBuilder.class, parameters);
this.abortWith(method, "@Modifying 要么跟 @Query 组合, 要么跟@QueryByNamed组合不能独存!"); if (m != null && queryLen == 0 && queryByNamed == null && !hasQueryBuilder) {
this.abortWith(method, "@Modifying 要么跟 @Query 组合, 要么跟@QueryByNamed组合, 要么跟QueryBuilder参数组合!");
} }
} }


Expand Down
8 changes: 4 additions & 4 deletions src/main/java/org/fastquery/analysis/SQLFilter.java
Expand Up @@ -24,8 +24,8 @@


import java.lang.reflect.Method; import java.lang.reflect.Method;


import org.fastquery.core.BuilderQuery;
import org.fastquery.core.Query; import org.fastquery.core.Query;
import org.fastquery.core.QueryBuilder;
import org.fastquery.util.TypeUtil; import org.fastquery.util.TypeUtil;


/** /**
Expand All @@ -38,12 +38,12 @@ class SQLFilter implements MethodFilter {
@Override @Override
public void doFilter(Method method) { public void doFilter(Method method) {
Query[] queries = method.getAnnotationsByType(Query.class); Query[] queries = method.getAnnotationsByType(Query.class);
boolean b = TypeUtil.hasType(BuilderQuery.class, method.getParameters()); boolean b = TypeUtil.hasType(QueryBuilder.class, method.getParameters());
for (Query query : queries) { for (Query query : queries) {
String sql = query.value(); String sql = query.value();


if ("".equals(sql) && !b) { // 如果@Query的value为"",并且又没有传递BuilderQuery(BuilderQuery:用于构建SQL) if ("".equals(sql) && !b) { // 如果@Query的value为"",并且又没有传递QueryBuilder(用于构建SQL)
this.abortWith(method, sql + "该方法,没有标注任何SQL语句. 帮定SQL又多种方式:通过@Query;采用xml模板;用BuilderQuery,或参数传入..."); this.abortWith(method, sql + "该方法,没有标注任何SQL语句. 帮定SQL又多种方式:通过@Query;采用xml模板;用QueryBuilder,或参数传入...");
}else if (sql.length() < 2 && !b) { }else if (sql.length() < 2 && !b) {
this.abortWith(method, sql + "SQL语法错误"); this.abortWith(method, sql + "SQL语法错误");
} }
Expand Down
Expand Up @@ -22,17 +22,22 @@


package org.fastquery.core; package org.fastquery.core;


import java.util.ArrayList;

/** /**
* 构建查询语句
* *
* @author mei.sir@aliyun.cn * @author mei.sir@aliyun.cn
*/ */
@FunctionalInterface public class ConditionList extends ArrayList<String> {
public interface BuilderQuery {
/** private static final long serialVersionUID = 1L;
* 对给定的参数进行操作. 通过MetaData可以设置这些语句query,count,where.
* public static ConditionList of(String...conditions) {
* @param m 元数据 ConditionList conditionList = new ConditionList();
*/ for (String condition : conditions) {
void accept(MetaData m); conditionList.add(condition);
}
return conditionList;
}

} }
105 changes: 0 additions & 105 deletions src/main/java/org/fastquery/core/MetaData.java

This file was deleted.

0 comments on commit 03841ac

Please sign in to comment.