起步

安装

Maven

<dependency>
    <groupId>com.ejlchina</groupId>
    <artifactId>bean-searcher</artifactId>
    <version>2.2.0</version>
</dependency>
1
2
3
4
5

Gradle

implementation 'com.ejlchina:bean-searcher:2.2.0'
1

集成

通常情况下,我们都是在一个后端的 Java Web 项目中使用 Bean Searcher,它可以在任意的 Web 框架中使用,以下介绍在常见的几种 Web 框架的集成方法:

Spring Boot

由于 Bean Searcher 自 v2.0 起就已实现了 Spring Boot Starter 化,所以在 Spring Boot Web 项目中集成 Bean Searcher 最为简单,只需要在应用的配置文件内配置 Search Bean 实体类的包名路径即可,例如在 src/main/resources/application.properties 文件中:

bean-searcher.packages = com.example.sbean
1

其中 com.example.sbean 为 Search Bean 实体类所在的包名路径,可配多个。

Spring MVC

在传统的 Spring MVC 项目中集成 Bean Searcher 需要在项目的 xml 文件内配置以下两个 Bean:

<bean id="searchSqlExecutor" 
        class="com.ejlchina.searcher.implement.MainSearchSqlExecutor" 
        p:dataSource-ref="dataSource" />

<bean id="searcher" 
        class="com.ejlchina.searcher.support.spring.SpringSearcher"
        p:searchSqlExecutor-ref="searchSqlExecutor"
        p:scanPackages="{'com.example.sbean'}" />
1
2
3
4
5
6
7
8

其中com.example.sbean为 Search Bean 实体类的包名路径,可配多个。

Grails

在 Grails 项目中集成 Bean Searcher,只需要在 grails-app/conf/spring/resources.groovy 文件内配置一个 Bean 即可:

searcher(SpringSearcher) {
    scanPackages = [
        'com.example.sbean'
    ]
    searchSqlExecutor = { MainSearchSqlExecutor e ->
        dataSource = ref('dataSource')
    }
}
1
2
3
4
5
6
7
8

其中com.example.sbean为 Search Bean 实体类的包名路径,可配多个。

Jfinal

在 Jfinal 项目中集成 Bean Searcher,需要在配置插件的地方配置 SearchPlugin,例如:

public class App extends JFinalConfig implements SearcherReceiver, SearcherConfiger {

    @Override
    public void configPlugin(Plugins me) {
        // 首先获得一个 IDataSourceProvider 实例,比如 DruidPlugin 插件
        DruidPlugin dp = new DruidPlugin(...);
        // 省略 DruidPlugin 相关的配置
        // Bean Searcher 插件,第一个参数接收 IDataSourceProvider 实例
        // 第二个参数为 Search Bean 实体类的包名路径,可配多个
        SearchPlugin sp = new SearchPlugin(dp, "com.example.sbean");
        sp.setSearcherReceiver(this);
        sp.setSearcherConfiger(this);
        me.add(dp);
        me.add(sp);
        // 省略其它配置
    }

    @Override
    public void receive(Searcher searcher) {
        // 接收到 searcher 实例,可以用一个容器接收,留待后续使用
        Ioc.add(Searcher.class, searcher);
    }

    @Override
    public void config(SearcherBuilder builder) {
        // TODO: 这里可以对 Bean Searcher 做一些自定义配置
    }
    // 省略 Jfinal 的其它配置
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

其中com.example.sbean为 Search Bean 实体类的包名路径,可配置多个。

Others

在其它任意的 Java 项目中,都可以使用如下方法使用 Bean Searcher :

1、启动 Bean Searcher

SearcherStarter starter = new SearcherStarter();
// 传入 Search Bean 实体类的包名(可传多个),并启动
starter.start("com.example.sbean");
1
2
3

2、构建 Searcher 实例

// 获取配置好的数据源
DataSource dataSource = getDataSource();
// 构建 Searcher 实例
Searcher searcher = SearcherBuilder.builder()
        .configSearchSqlExecutor(new MainSearchSqlExecutor(dataSource))
        .build();
1
2
3
4
5
6

得到 Searcher 实例后,便可以在项目中使用它了。

3、停止 Bean Searcher

当项目停止时,可调用 SearcherStarter 对象的 shutdown 方法优雅关闭:

// 关闭检索器
starter.shutdown();
1
2

使用

Searcher

当在项目中成功集成后,接下来便可以在我们的业务代码中拿到 Searcher 实例了。

Spring Boot、Spring MVC:

// 可以使用注解注入
@Autowired
private Searcher searcher;
1
2
3

Grails:

// 直接按名称注入
def searcher;
1
2

Jfinal:

// 从容器里取出
private Searcher searcher = Ioc.get(Searcher.class);
1
2

编写实体类

然后便是定义检索实体类了,即 SearchBean,一个简单的 SearchBean 如下所示:

package com.example.sbean;

import com.ejlchina.searcher.bean.SearchBean;
import com.ejlchina.searcher.bean.DbField;

@SearchBean(tables = "user") 
public class User {

    @DbField("id")
    private Long id;                

    @DbField("name")
    private String name;

    @DbField("age")
    private int age;

    // Getter and Setter ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

如上:使用 @SearchBean@DbField 注解指定实体类与数据库表以及字段之间的映射关系。需要注意的是:实体类必须定义在 集成 Bean Searcher 时指定的 包路径下

本例是一个简单的单表检索实体类,更多复杂的联表实体类请参考 实体类 > 多表关联 章节。

开始检索

编写好实体类后,我们便可以尽情地检索了, 首先我们看一下 Searcher 接口提供了哪些方法:

  • search(Class<T> beanClass, Map<String, Object> params) 适合需要分页的查询;
  • search(Class<T> beanClass, Map<String, Object> params, String[] summaryFields) 适合需要分页与统计的查询;
  • searchFirst(Class<T> beanClass, Map<String, Object> params) 查询满足条件的的第一个 Bean
  • searchList(Class<T> beanClass, Map<String, Object> params) 只检索 Bean 列表,不返回满足条件的总条数
  • searchAll(Class<T> beanClass, Map<String, Object> params) 检索满足条件的所有 Bean,不分页
  • searchCount(Class<T> beanClass, Map<String, Object> params) 只查询满足条件的 Bean 个数
  • searchSum(Class<T> beanClass, Map<String, Object> params, String field) 检索某个字段的统计值
  • searchSum(Class<T> beanClass, Map<String, Object> params, String[] fields) 检索多个字段的统计值

完整的接口定义,可查看代码仓库中的 Searcher 文件。

例如,分页查询用户列表:

Map<String, Object> params = new HashMap<>();
params.put("page", 0);                          // 第 0 页
params.put("size", 15);                         // 每页 15 条
SearchResult<User> result = searcher.search(User.class, params);
List<User> users = result.getDataList();           // 第 0 页的用户
Number totalCount = result.getTotalCount();        // 数据总条数
1
2
3
4
5
6

使用参数构建器的等效写法(since v2.2.0):

Map<String, Object> params = MapUtils.builder()
        .page(0, 15)                            // 第 0 页, 每页 15 条
        .build();
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4

分页查询年龄等于 20 的用户列表:

Map<String, Object> params = new HashMap<>();
params.put("page", 0);                          // 第 0 页
params.put("size", 15);                         // 每页 15 条
params.put("age", 20);                          // 年龄参数为 20
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4
5

使用参数构建器的等效写法(since v2.2.0):

Map<String, Object> params = MapUtils.builder()
        .field(User::getAge, 20)                            // 年龄参数为 20
        .page(0, 15)                                        // 第 0 页, 每页 15 条
        .build();
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4
5

分页查询年龄大于 20 的用户列表:

Map<String, Object> params = new HashMap<>();
params.put("page", 0);                                      // 第 0 页
params.put("size", 15);                                     // 每页 15 条
params.put("age", 20);                                      // 年龄参数为 20
params.put("age-op", GreaterThan.class);                    // 年龄字段运算符为:大于
SearchResult<User> res = searcher.search(User.class, params);
1
2
3
4
5
6

使用参数构建器的等效写法(since v2.2.0):

Map<String, Object> params = MapUtils.builder()
        .field(User::getAge, 20).op(GreaterThan.class)      // 年龄参数为 20, 运算符为:GreaterThan
        .page(0, 15)                                        // 第 0 页, 每页 15 条
        .build();
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4
5

分页查询年龄在 20 与 30 之间用户列表:

Map<String, Object> params = new HashMap<>();
params.put("page", 0);                                      // 第 0 页
params.put("size", 15);                                     // 每页 15 条
params.put("age-0", 20);                                    // 年龄第 0 个参数为 20
params.put("age-1", 30);                                    // 年龄第 1 个参数为 30
params.put("age-op", Between.class);                        // 年龄字段运算符为:Between
SearchResult<User> res = searcher.search(User.class, params);
1
2
3
4
5
6
7

使用参数构建器的等效写法(since v2.2.0):

Map<String, Object> params = MapUtils.builder()
        .field(User::getAge, 20, 30).op(Between.class)      // 年龄参数为 20 和 30, 运算符为:Between
        .page(0, 15)                                        // 第 0 页, 每页 15 条
        .build();
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4
5

分页查询年龄为 18, 20, 25 的用户列表:

Map<String, Object> params = new HashMap<>();
params.put("page", 0);                                      // 第 0 页
params.put("size", 15);                                     // 每页 15 条
params.put("age-0", 18);                                    // 年龄第 0 个参数为 18
params.put("age-1", 20);                                    // 年龄第 1 个参数为 20
params.put("age-2", 25);                                    // 年龄第 2 个参数为 25
params.put("age-op", MultiValue.class);                     // 年龄字段运算符为:多值查询
SearchResult<User> res = searcher.search(User.class, params);
1
2
3
4
5
6
7
8

使用参数构建器的等效写法(since v2.2.0):

Map<String, Object> params = MapUtils.builder()
        .field(User::getAge, 18, 20, 25).op(MultiValue.class)   // 年龄参数为 18, 20, 25, 运算符为:MultiValue
        .page(0, 15)                                        // 第 0 页, 每页 15 条
        .build();
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4
5

分页查询姓张的用户:

Map<String, Object> params = new HashMap<>();
params.put("page", 0);                                      // 第 0 页
params.put("size", 15);                                     // 每页 15 条
params.put("name", "张");                                   // 姓名参数为 张
params.put("name-op", StartWith.class);                     // 姓名字段运算符为:以...开头
SearchResult<User> res = searcher.search(User.class, params);
1
2
3
4
5
6

使用参数构建器的等效写法(since v2.2.0):

Map<String, Object> params = MapUtils.builder()
        .field(User::getName, "张").op(StartWith.class)     // 姓名参数为 张, 运算符为:StartWith
        .page(0, 15)                                        // 第 0 页, 每页 15 条
        .build();
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4
5

分页查询并按年龄排序:

Map<String, Object> params = new HashMap<>();
params.put("page", 0);                                      // 第 0 页
params.put("size", 15);                                     // 每页 15 条
params.put("sort", "age");                                  // 排序字段:年龄
params.put("order", "asc");                                 // 排序方法:升序 asc, 降序 desc
SearchResult<User> res = searcher.search(User.class, params);
1
2
3
4
5
6

使用参数构建器的等效写法(since v2.2.0):

Map<String, Object> params = MapUtils.builder()
        .orderBy(User::getAge, "asc")                       // 按年龄排序,升序
        .page(0, 15)                                        // 第 0 页, 每页 15 条
        .build();
SearchResult<User> result = searcher.search(User.class, params);
1
2
3
4
5

SQL 日志

如果需要查看 Bean Searcher 的 SQL 执行日志,只需在您的日志配置文件中将 com.ejlchina.searcher.implement.MainSearchSqlExecutor 的日志级别调整为 DEBUG 即可。在 SpringBoot 项目中,可参考 logback-spring.xml 文件。

输出级别配置好后,SQL 日志的效果如下: