π₯π₯π₯ A read-only ORM for complex queries in Java
See the codeEnglish | δΈζ
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.
| 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.
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:
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.
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:
age fieldFor 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 β filter status = 1GET: /user/index? name=Jac & name-op=sw β name starts with JacGET: /user/index? name=Jack & name-ic=true β name = Jack (case ignored)GET: /user/index? sort=age & order=desc β sort by age descendingGET: /user/index? onlySelect=username,age β return username and age onlyGET: /user/index? selectExclude=joinDate β exclude joinDate fieldThe 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.
π₯ Online Demo ο½ π» Run Locally β Bean Searcher, MyBatis, and JPA comparison implementations
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);
Bean Searcher works with any Java Web framework, such as: SpringBoot, Spring MVC, Grails, Jfinal and so on.
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
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;
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();
You can customize and extend any component in Bean Searcher.
FieldOp to support other field operatorsDbMapping to support other ORM annotationsParamResolver to support JSON query paramsFieldConvertor to support any type of fieldDialect to support more databases
git checkout -b feat/xxxxgit commit -am 'feat(function): add xxxxx'git push origin feat/xxxxpull request29 followers Β· starred Oct 2024
26 followers Β· starred Jul 2023
67 followers Β· starred Oct 2022
11 followers Β· starred Oct 2024
π₯π₯π₯ A read-only ORM for complex queries in Java
See the codeEnglish | δΈζ
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.
| 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.
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:
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.
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:
age fieldFor 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 β filter status = 1GET: /user/index? name=Jac & name-op=sw β name starts with JacGET: /user/index? name=Jack & name-ic=true β name = Jack (case ignored)GET: /user/index? sort=age & order=desc β sort by age descendingGET: /user/index? onlySelect=username,age β return username and age onlyGET: /user/index? selectExclude=joinDate β exclude joinDate fieldThe 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.
π₯ Online Demo ο½ π» Run Locally β Bean Searcher, MyBatis, and JPA comparison implementations
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);
Bean Searcher works with any Java Web framework, such as: SpringBoot, Spring MVC, Grails, Jfinal and so on.
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
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;
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();
You can customize and extend any component in Bean Searcher.
FieldOp to support other field operatorsDbMapping to support other ORM annotationsParamResolver to support JSON query paramsFieldConvertor to support any type of fieldDialect to support more databases
git checkout -b feat/xxxxgit commit -am 'feat(function): add xxxxx'git push origin feat/xxxxpull request29 followers Β· starred Oct 2024
26 followers Β· starred Jul 2023
67 followers Β· starred Oct 2022
11 followers Β· starred Oct 2024