README.md

July 29, 2026 · View on GitHub

logo

Bean Searcher

✨ A read-only ORM for complex queries in Java ✨

License Sponsor on Ko-fi

Docs:https://bs.zhxu.cn


English | 中文

Bean Searcher is the GraphQL of list retrieval — entities define search boundaries, parameters drive query logic. No special protocol required, just add one dependency.

Single-table entities are searchable with zero annotations. Multi-table joins, pagination, filtering, sorting, and stats — all in one line of code.

🎯 Core Capabilities

Pain pointTraditional approachBean Searcher
Multi-condition list queriesif-else SQL concatenation / SpecificationOne line, parameter-driven
Multi-table joinsManual JOIN / XML mappingAnnotation-declared, auto-generated SQL
Frontend dynamic filteringAdd fields, change backend APIsZero backend changes, frontend freely combines
Non-invasive integrationSpring Data REST: expose repositories, restructure URLsOne dependency, inject BeanSearcher, existing code untouched

Get started in one minute:

implementation "cn.zhxu:bean-searcher-boot-starter:${latestVersion}"

Single-table search works out-of-the-box with zero annotations on your existing entity.

⁉️ WHY

Declarative Search vs Imperative Coding

MyBatis / Hibernate excel at CRUD, but when it comes to list retrieval with multi-condition filtering, table joins, sorting, and pagination, they often require piles of if-else condition stitching and VO conversion code.

Bean Searcher solves this with declarative search:

  • Entity as Declaration — The SearchBean defines "what can be searched"; annotations are optional
  • Parameters as Query — The frontend controls "what is searched"; one endpoint handles endless combinations
  • Zero Protocol Burden — Works on standard HTTP parameters, no special protocol needed

Just as GraphQL lets clients freely specify which fields to return in one request, Bean Searcher lets clients freely specify filter conditions, sort order, pagination, and stats — without a dedicated schema, all in one line of code.

See 📊 Comparison for concrete code-to-code comparison with MyBatis and Spring Data JPA implementations in the demo project.

💥 Achieved with one line of code

Start with your existing domain/VO class — annotations are optional (zero-annotation for single-table, add a few for joins):

@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u")
public class User {
  private long id;
  private String username;
  private int status;
  private int age;
  private String gender;
  private Date joinDate;
  private int roleId;
  @DbField("r.name")
  private String roleName;
  // Getters and setters...
}

Then you can complete the API with one line of code:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private BeanSearcher beanSearcher;              // Inject BeanSearcher

    @GetMapping("/index")
    public SearchResult<User> index(HttpServletRequest request) {
        // Only one line of code written here
        return beanSearcher.search(User.class, MapUtils.flat(request.getParameterMap()), User::getAge);
    }

}

This line of code can achieve:

  • Retrieval from multi tables
  • Pagination by any field
  • Combined filter by any field
  • Sorting by any field
  • Summary with age field

For example, this API can be requested as follows:

  • GET: /user/index — default pagination
  • GET: /user/index? page=1 & size=10 — specified pagination
  • GET: /user/index? status=1 — filter status = 1
  • GET: /user/index? name=Jac & name-op=swname starts with Jac
  • GET: /user/index? name=Jack & name-ic=truename = Jack (case ignored)
  • GET: /user/index? sort=age & order=desc — sort by age descending
  • GET: /user/index? onlySelect=username,age — return username and age only
  • GET: /user/index? selectExclude=joinDate — exclude joinDate field

📊 Comparison

The demo project includes implementations of the same API using MyBatis and Spring Data JPA — same tables, same data, same response — so you can compare side-by-side:

AspectBean SearcherMyBatisSpring Data JPA
Controller code1 line~280 lines~280 lines
Extra files01 Mapper + 1 XML2 Entities + 1 Repository
SQL generationDeclarative annotationsManual XMLCriteria API
Sourcebackend-springboot4backend-vs-mybatisbackend-vs-jpa

The Bean Searcher version:

return beanSearcher.search(User.class, User::getAge);

The MyBatis / JPA versions need handwritten parameter parsing, dynamic condition assembly, three separate queries (list / count / sum), pagination, sorting, and CSV streaming — all handled by this one line in Bean Searcher.

🖥 Demos

🖥 Online Demo | 💻 Run Locally — Bean Searcher, MyBatis, and JPA comparison implementations

✨ Parameter builder

For programmatic query building (not just HTTP parameters), use the type-safe builder API:

Map<String, Object> params = MapUtils.builder()
        .selectExclude(User::getJoinDate)                 // Exclude joinDate field
        .field(User::getStatus, 1)                        // Filter: status = 1
        .field(User::getName, "Jack").ic()                // Filter: name = 'Jack' (case ignored)
        .field(User::getAge, 20, 30).op(Opetator.Between) // Filter: age between 20 and 30
        .orderBy(User::getAge, "asc")                     // Sort by age ascending 
        .page(0, 15)                                      // Pagination: page=0 and size=15
        .build();
List<User> users = beanSearcher.searchList(User.class, params);

🌱 Easy integration

Bean Searcher works with any Java Web framework, such as: SpringBoot, Spring MVC, Grails, Jfinal and so on.

SpringBoot / Grails

All you need is to add a dependency:

implementation "cn.zhxu:bean-searcher-boot-starter:${latestVersion}"

and then you can inject Searcher into a Controller or Service:

@Autowired
private MapSearcher mapSearcher;      // Retrieved data as Map objects

@Autowired
private BeanSearcher beanSearcher;    // Retrieved data as generic objects

Solon Project

All you need is to add a dependency:

implementation "cn.zhxu:bean-searcher-solon-plugin:${latestVersion}"

and then you can inject Searcher into a Controller or Service:

@Inject
private MapSearcher mapSearcher;

@Inject
private BeanSearcher beanSearcher;

Other frameworks

Adding this dependency:

implementation "cn.zhxu:bean-searcher:${latestVersion}"

then you can build a Searcher with SearcherBuilder:

DataSource dataSource = ...     // Get the dataSource of the application

// DefaultSqlExecutor supports multi datasources
SqlExecutor sqlExecutor = new DefaultSqlExecutor(dataSource);

// Build a MapSearcher
MapSearcher mapSearcher = SearcherBuilder.mapSearcher()
        .sqlExecutor(sqlExecutor)
        .build();

// Build a BeanSearcher
BeanSearcher beanSearcher = SearcherBuilder.beanSearcher()
        .sqlExecutor(sqlExecutor)
        .build();

🔨 Easy extended

You can customize and extend any component in Bean Searcher.

Available extension points (click to expand)
  • Customizing FieldOp to support other field operators
  • Customizing DbMapping to support other ORM annotations
  • Customizing ParamResolver to support JSON query params
  • Customizing FieldConvertor to support any type of field
  • Customizing Dialect to support more databases
  • and so on

🏗 Architecture

  • [ Sa-Token ]: A lightweight Java permission authentication framework that makes authorization simple and elegant!
  • [ Fluent MyBatis ]: MyBatis syntax enhancement framework, combining features and advantages of MyBatisPlus, DynamicSql, Jpa etc., generating code with annotation processors
  • [ OkHttps ]: Lightweight yet powerful HTTP client, universal for front-end and back-end, supporting WebSocket and Stomp protocols
  • [ hrun4j ]: API automation testing solution
  • [ JsonKit ]: Ultra-lightweight JSON facade, simple to use, independent of specific implementation, decoupling business code from Jackson, Gson, Fastjson etc.!
  • [ Free UI ]: Based on Vue3 + TypeScript, a very lightweight and cool UI component library!

❤️ How to contribute

  1. Fork the code!
  2. Create your own branch: git checkout -b feat/xxxx
  3. Submit your changes: git commit -am 'feat(function): add xxxxx'
  4. Push your branch: git push origin feat/xxxx
  5. Submit pull request