README.md
July 29, 2026 · View on GitHub
Bean Searcher
✨ A read-only ORM for complex queries in Java ✨
English | 中文
- 🚀 Online Demo: https://demo-bs.zhxu.cn/
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 point | Traditional approach | Bean Searcher |
|---|---|---|
| Multi-condition list queries | if-else SQL concatenation / Specification | One line, parameter-driven |
| Multi-table joins | Manual JOIN / XML mapping | Annotation-declared, auto-generated SQL |
| Frontend dynamic filtering | Add fields, change backend APIs | Zero backend changes, frontend freely combines |
| Non-invasive integration | Spring Data REST: expose repositories, restructure URLs | One 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
agefield
For example, this API can be requested as follows:
GET: /user/index— default paginationGET: /user/index? page=1 & size=10— specified paginationGET: /user/index? status=1— filterstatus = 1GET: /user/index? name=Jac & name-op=sw—namestarts withJacGET: /user/index? name=Jack & name-ic=true—name = Jack(case ignored)GET: /user/index? sort=age & order=desc— sort byagedescendingGET: /user/index? onlySelect=username,age— returnusernameandageonlyGET: /user/index? selectExclude=joinDate— excludejoinDatefield
📊 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:
| Aspect | Bean Searcher | MyBatis | Spring Data JPA |
|---|---|---|---|
| Controller code | 1 line | ~280 lines | ~280 lines |
| Extra files | 0 | 1 Mapper + 1 XML | 2 Entities + 1 Repository |
| SQL generation | Declarative annotations | Manual XML | Criteria API |
| Source | backend-springboot4 | backend-vs-mybatis | backend-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
FieldOpto support other field operators - Customizing
DbMappingto support other ORM annotations - Customizing
ParamResolverto support JSON query params - Customizing
FieldConvertorto support any type of field - Customizing
Dialectto support more databases - and so on
🏗 Architecture

🤝 Friendship links
- [ 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
- Fork the code!
- Create your own branch:
git checkout -b feat/xxxx - Submit your changes:
git commit -am 'feat(function): add xxxxx' - Push your branch:
git push origin feat/xxxx - Submit
pull request