MyBatis keeps the SQL visible
MyBatis sits between raw JDBC and a full object-relational mapper. You define a mapper interface, attach SQL through XML or annotations, and receive Java objects instead of manually walking a ResultSet. A SqlSessionFactory owns configuration, while each SqlSession executes statements and manages transaction scope. The appeal is control: a database query remains recognizable to the engineer reviewing it.
That control comes with a sizeable codebase and an explicit mapping model. commit 673798b contained 2,073 files and about 116,941 lines of source in a 6.6 MB checkout. Result maps describe how columns populate constructors, properties, collections, and nested objects. Simple rows can map by convention, but a complex join still asks someone to understand aliases, identifiers, association rules, and the resulting object graph.
XML and annotations both leave query design with you
The README calls simplicity the advantage over object-relational mapping, which is accurate only if your team prefers SQL to hidden persistence behavior. Mapper annotations keep short statements next to Java methods. XML is better suited to reusable fragments and detailed result maps. Neither route designs the query, manages a schema migration, or decides how vendor differences should behave. Those choices stay in application code and review.
Dynamic SQL covers if, choose, trim, where, set, and foreach. It can prevent dangling AND clauses and trailing commas without string concatenation. The same mechanism uses OGNL expressions, and the 116,941-line source tree reflects how much machinery sits behind a compact mapper file. Teams should test every meaningful combination of optional filters because syntactically tidy XML can still produce a slow or logically wrong query.
What happened when we ran it
Our sandbox install succeeded in 636 seconds, but the build exited with code 1 after 10 seconds. The Maven enforcer reported one precise problem: Maven 3.9.9 did not satisfy the allowed range beginning at 3.9.16. The log reached that rule before compilation, so it does not show whether the Java source would have compiled after the toolchain requirement was met.
The test command reached the same enforcer rule and failed after 9 seconds. It did not produce a test summary, which means we cannot claim that any tests passed or failed. This was commit 673798b in an unprivileged container with 3 CPUs, 10 GB of RAM, and JDK 21. The useful result is a reproducible toolchain mismatch, not a judgment about test quality.
Our scan found 7 CI workflow files, no Dockerfile, and no separate tests directory. The README does explain that Maven test groups vary by JDK and operating system, with container-backed cases excluded by default. A contributor therefore needs to read the workflow and POM, install Maven 3.9.16 or newer, and decide whether the default exclusions match the change being checked.
A session leak can become a transaction leak
MyBatis makes session behavior explicit. Opening a default session starts a transaction, obtains a JDBC connection from the configured data source, and uses the driver's isolation default. The Java guide says every opened session must be closed. Its thread-local SqlSessionManager warning is sharper: failure to close in a pooled server can carry a SQL session into another request and cause unpredictable transactions or connection leaks.
Spring and Guice integrations can own that lifecycle, but they are separate integration layers. Without them, use a try-with-resources block and decide when to commit or roll back. The 636-second source install tells you nothing about this runtime discipline. Before adoption, test failed writes, request cancellation, nested service calls, batch execution, and connection-pool exhaustion against the same transaction setup used in production.
Nested selects and namespace caches need deliberate limits
The mapping guide plainly names the N+1 select problem. Loading a list and then issuing a nested select for every row can look elegant in a result map while multiplying database calls. MyBatis supports joined result maps and multiple result sets, so the fix is available, but the framework cannot choose it for you. Query logging and database-level inspection still belong in performance testing.
Caching carries similar conditions. Each session has a local cache, while the optional second-level cache is bound to a mapper namespace and updates at transaction completion. The measured checkout had 2,073 files, yet the risk can hide in one <cache/> line: another namespace touching the same tables may require a shared cache reference or an explicit invalidation choice. Start without second-level caching unless its ownership is clear.
The January 2025 release is followed by active 2026 work
GitHub showed 20,441 stars and 209 combined issues and pull requests when fetched. Release 3.5.19 was published on January 2, 2025, while the repository was pushed on August 29, 2026. The dates do not support an abandonment claim. They show a stable published line alongside continued work on the next source state.
Issue 3574 also shows why release age and development activity must be read together. A user reported OGNL lock contention with 3.5.19, and pull request 3763 proposed an OGNL upgrade on August 31, 2026. That report is workload-specific and does not prove every MyBatis service will block. High-concurrency users should reproduce it with their dynamic queries before choosing the current release.
Pick it for explicit SQL, not automatic persistence
MyBatis earns its place when engineers want SQL to be a maintained application asset. It handles parameter binding, mapper proxies, result construction, transaction hooks, and configurable caches while allowing stored procedures and vendor-specific branches. The documentation is detailed enough to expose footguns such as N+1 queries and unclosed sessions instead of pretending they disappear.
The failed 10-second build and 9-second test steps make the contributor path less forgiving than the brief README suggests. For application use, the stronger question is whether the team wants to own every query and its mapping for years. If yes, MyBatis is mature and direct. If no, Hibernate, jOOQ, or Jdbi each offers a different place to put that complexity.

