(Demo's in Chinese but you'll get the idea in 30 seconds) π
https://demo-bs.zhxu.cn/A backend engineer's confession: you just wrote 100 lines of Java code to do exactly one thing β glue a few HTTP parameters into a SQL string.
One Page of Requirements
My PM sent me a mockup. Pretty standard admin panel stuff:
- Paginated table
- Four filter fields at the top: name, age range, department, hire date
- Sort by any column
- Footer stats: total count, average age
"Easy, right? Can we ship this afternoon?" he asked.
"Sure," I said.
Then I opened IntelliJ.
If You Use MyBatis, Here's What the Next 30 Minutes Looks Like
<select id="searchUsers" resultType="UserVO">
SELECT u.*, d.name as deptName
FROM user u LEFT JOIN dept d ON u.dept_id = d.id
<where>
<if test="name != null and name != ''">
AND u.name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="ageMin != null">
AND u.age >= #{ageMin}
</if>
<if test="ageMax != null">
AND u.age <= #{ageMax}
</if>
<if test="dept != null and dept != ''">
AND d.name = #{dept}
</if>
<if test="entryDateStart != null">
AND u.entry_date >= #{entryDateStart}
</if>
<if test="entryDateEnd != null">
AND u.entry_date <= #{entryDateEnd}
</if>
</where>
<if test="sortField != null and sortOrder != null">
ORDER BY ${sortField} ${sortOrder}
</if>
LIMIT #{offset}, #{size}
</select>
And you're not done. You still need: a stats SQL, a Controller to parse parameters, a Service layer for pagination logic, a Mapper interface, a VO class just to hold the results β plus careful ${} sanitization to prevent SQL injection.
Four filter conditions. Almost 100 lines of code.
Then the PM says: "Oh, by the way β make department multi-select. And add a gender filter."
You take a deep breath and keep writing.
This isn't a MyBatis problem. Try JPA β you won't suffer any less:
Specification<User> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
Join<User, Department> deptJoin = root.join("dept", JoinType.LEFT);
if (StringUtils.isNotBlank(name)) {
predicates.add(cb.like(root.get("name"), "%" + name + "%"));
}
if (ageMin != null) predicates.add(cb.ge(root.get("age"), ageMin));
if (ageMax != null) predicates.add(cb.le(root.get("age"), ageMax));
if (StringUtils.isNotBlank(dept)) {
predicates.add(cb.equal(deptJoin.get("name"), dept));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
MyBatis wraps if-else in XML. JPA wraps if-else in Predicate chains. Same problem, different syntax β you're still manually translating parameters into query conditions, line by line.
In 2026, frontend devs are doing vibe coding β describe intent, let tools handle the details. Meanwhile, backend list queries are still stuck in the artisanal stone age: hand-written XML, hand-stitched Predicates, hand-typed if-else chains. Not because we want to. Because there was never a tool that let us vibe.
The root cause of all this pain: you've mashed "declaration" and "execution" together.
A Different Mindset
Let's step back for a second.
Your database tables are already defined. What users want to search is up to them.
So why do you β the translator between HTTP parameters and SQL β have to write every single if-else by hand?
What if a smart middle layer existed that just⦠knew?
- It knows what fields your entity has β that's your search boundary
- It knows what parameters the frontend sent β that's your search intent
- Combine the two β generate the SQL
This idea isn't mine. In the API world, it has a name:
GraphQL β clients specify what fields they want. The server returns exactly those fields. One request replaces multiple REST calls.
So⦠what about list queries?
| GraphQL | Bean Searcher | |
|---|---|---|
| Domain | API data queries | Database list retrieval |
| Client controls | Which fields to return | Which fields + filters + sort order + page size |
| Protocol | POST + GraphQL body | Standard HTTP params (GET/POST) |
| Core idea | Declare what data you want | Declare search boundary, let params drive the query |
| Adoption cost | Change your API layer, add schema | One dependency. Zero refactoring. |
In one sentence: GraphQL lets the frontend control data fields in one request. Bean Searcher lets the frontend control filtering, sorting, pagination, and stats β using the URL params you already know.
The GraphQL of List Retrieval
You define one entity:
@SearchBean(tables = "user u, dept d", where = "u.dept_id = d.id", autoMapTo = "u")
public class UserVO {
private Long id;
private String name;
private Integer age;
private String gender;
@DbField("d.name")
private String deptName;
private LocalDate entryDate;
// getters & setters...
}
Then, the entire search endpoint is one line of code:
@GetMapping("/user/search")
public SearchResult<UserVO> search(HttpServletRequest request) {
return beanSearcher.search(UserVO.class, MapUtils.flat(request.getParameterMap()));
}
Frontend sends a GET request:
GET /user/search?name=Li&age-0=20&age-1=30&age-op=bt&sort=age&order=desc
And the response:
{
"dataList": [
{ "id": 1, "name": "Li Wei", "age": 25, "deptName": "Engineering", "entryDate": "2023-03-01" },
{ "id": 2, "name": "Li Ming", "age": 28, "deptName": "Product", "entryDate": "2022-11-15" }
],
"totalCount": 47,
"summaries": [ 1350 ]
}
Pagination. Table joins. Multi-condition filtering. Sorting. Stats. One endpoint. Everything. No XML. No if-else. No VO conversion layer.
This is Bean Searcher β a framework I've been using for three years and can't stop recommending.
What It Actually Does
One sentence explains 90% of the code it saves:
Entity classes declare the search boundary. HTTP parameters drive the query logic.
This is vibe coding for list queries. You declare "what can be searched." The frontend declares "what should be searched." All the translation in between β the framework handles it.
| Traditional (MyBatis / JPA) | Bean Searcher | |
|---|---|---|
| Define filter conditions | Write <if> in XML / stitch QueryWrapper in Java |
Parameter names map directly to field names |
| Add a new filter | Change XML / Java code β recompile β redeploy | Frontend sends a new parameter. Zero backend changes. |
| Multi-table joins | Hand-write JOIN SQL | Entity class declares relationships |
| Return results | Need a VO conversion layer | The SearchBean is the VO |
| Security | Write your own validation | Anti-injection, pagination caps, deep-offset throttling β all on by default |
A metaphor: traditional ORMs are imperative β you tell the framework every step. Bean Searcher is declarative β you declare the boundary (what fields exist), and the frontend declares the intent (what to filter/sort/paginate).
You're not writing queries anymore. You're declaring search boundaries.
A Question You're Probably Thinking
"Doesn't this force the frontend to pass too many parameters?"
I get this question constantly. The answer: how many parameters the frontend sends depends on product complexity, not on your backend framework. Period.
If the product only needs a fuzzy search box? Frontend sends ?name=Li. That's it. No name-op. No name-ic. You can even use it with zero annotations β a plain POJO gets its fields auto-mapped to database columns (camelCase β snake_case).
Remember: annotations are for constraints and fine-tuning, not mandatory setup. A single-table entity is searchable out of the box, no annotations required.
Is It the Enemy of MyBatis/JPA?
Absolutely not.
MyBatis handles writes. Bean Searcher handles list reads. Different tools, different jobs, peaceful coexistence.
Just as GraphQL wasn't created to kill REST β it just makes API queries more flexible β Bean Searcher wasn't created to kill MyBatis. It just makes list retrieval stop being painful.
| When to use | |
|---|---|
| MyBatis / JPA | Writes, transactions, complex business logic |
| Bean Searcher | Admin dashboards, data exports, report queries, any "dynamic multi-filter" scenario |
| The relationship | Complementary, not competing. Just add one dependency. |
Real Project Experience
I've used Bean Searcher in three production projects (Spring Boot 2/3/4, Solon 3/4). The biggest change isn't "less code" β it's a different way of thinking.
Before: I'd get a list query requirement and my brain would go "OK, how do I write this SQL? How do I stitch the parameters? What object do I pass for pagination? What about sorting?"
Now: I get the same requirement and think "What fields does the page need? Which tables do they come from? Which fields should the frontend be able to filter on?"
You stop being a SQL stitcher. You become a domain modeler.
2026's hottest term is vibe coding β describe your intent, let tools handle the details. Frontend devs have Cursor, v0, and bolt to vibe with. Why should backend list queries still be done the old-fashioned way?
When I explain this to colleagues, their first reaction is always the same: "So it's like⦠GraphQL, but for Java backends?"
"Exactly. Or put another way β it's vibe coding for list queries. Declare the boundary, drive with parameters, one line of code."
Try It
If you've read this far and recognized every pain point I described β you should try this.
No refactoring your project. No replacing your ORM. No changing your architecture. It's a completely non-invasive framework that coexists with MyBatis, JPA, and Spring Data JDBC.
- π Full Documentation
- π₯ Live Demo (zero setup)
- β GitHub
If this solves a real pain point for you, star the repo. Help more Java engineers trapped in if-else hell find their way out.
After all β your life is worth more than writing if-else.
Top comments (0)