From JDBC to MyBatis: Transforming Your Java Persistence Layer
This article walks through the step‑by‑step evolution from raw JDBC to the MyBatis framework, explaining why JDBC should be wrapped, how MyBatis improves connection handling, SQL management, dynamic statements, result mapping, caching, and overall architecture with concrete code examples.
1 Introduction
This article explains how JDBC evolves into MyBatis, emphasizing why JDBC should be encapsulated into a persistence framework and pointing out areas where MyBatis itself can be further improved.
2 JDBC Query Implementation Analysis
We first examine the most familiar and basic way to query a database using JDBC, which generally requires the following seven steps:
Load the JDBC driver; Establish and obtain a database connection; Create a Statement object; Set input parameters for the SQL statement; Execute the SQL statement and obtain the result set; Convert the result set into the desired format and return it; Release related resources (close Connection , Statement , ResultSet ).
Recommended open‑source Spring Boot project:
https://github.com/javastacks/spring-boot-best-practice
Specific implementation code:
public static List<Map<String, Object>> queryForList(){
Connection connection = null;
ResultSet rs = null;
PreparedStatement stmt = null;
List<Map<String, Object>> resultList = new ArrayList<>();
try {
// Load JDBC driver
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
String url = "jdbc:oracle:thin:@localhost:1521:ORACLEDB";
String user = "trainer";
String password = "trainer";
// Get connection
connection = DriverManager.getConnection(url, user, password);
String sql = "select * from userinfo where user_id = ? ";
// Create statement
stmt = connection.prepareStatement(sql);
// Set parameter
stmt.setString(1, "zhangsan");
// Execute query
rs = stmt.executeQuery();
// Process result set
ResultSetMetaData rsmd = rs.getMetaData();
int num = rsmd.getColumnCount();
while(rs.next()){
Map<String, Object> map = new HashMap<>();
for(int i = 0; i < num; i++){
String columnName = rsmd.getColumnName(i+1);
map.put(columnName, rs.getString(columnName));
}
resultList.add(map);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null) { rs.close(); rs = null; }
if (stmt != null) { stmt.close(); stmt = null; }
if (connection != null) { connection.close(); connection = null; }
} catch (SQLException e) {
e.printStackTrace();
}
}
return resultList;
}3 The Transition from JDBC to MyBatis
Having seen the seven JDBC steps, we identify which steps can be further encapsulated to reduce developer code.
3.1 First Optimization: Connection acquisition and release
Problem description: Frequent opening and closing of database connections wastes resources and degrades performance.
Solution:
Use a database connection pool to reuse established connections, thereby reducing the time spent on opening and closing connections.
Because connection pool implementations vary (e.g., DBCP, container‑provided JNDI), we abstract the acquisition through DataSource, allowing the actual implementation to be configured by the user.
3.2 Second Optimization: Centralized SQL storage
Problem description: When using JDBC, SQL statements are scattered across Java classes, leading to poor readability, difficult maintenance, and extra compilation steps.
Solution:
Store all SQL statements in a unified location such as configuration files or a database using a key‑value format, and retrieve them by key at runtime.
3.3 Third Optimization: Parameter mapping and dynamic SQL
Problem description: Fixed placeholders work only when the number and order of parameters are known. When the number of parameters varies (e.g., conditional queries), developers must manually concatenate SQL strings, re‑introducing SQL in Java code.
Solution:
Introduce a lightweight SQL templating syntax (e.g., #variable# ) and a parser that evaluates conditional tags (similar to JSTL) to generate dynamic SQL based on a Map of parameters.
3.4 Fourth Optimization: Result mapping and caching
After executing a query, the ResultSet must be transformed before resources are released. By encapsulating result handling, developers can obtain List, Map, or JavaBean results directly, and optionally cache query results using a composite key of SQL + parameters.
3.5 Fifth Optimization: Eliminating duplicate SQL
Storing all SQL in configuration files can lead to duplicated fragments. By modularizing reusable SQL snippets and referencing them, maintenance becomes easier—changing a fragment updates all dependent statements.
4 MyBatis Areas for Improvement
MyBatis requires explicit SQL for every operation, resulting in a large amount of SQL in an application. For simple single‑table CRUD, generating SQL automatically from JavaBean metadata (e.g., class UserInfo ↔ table USER_INFO) could reduce boilerplate.
5 MyBatis Framework Overall Design
5.1 Interface Layer – Ways to Interact with the Database
Use the traditional MyBatis API (provide StatementId and parameters to SqlSession ); Use Mapper interfaces.
5.1.1 Traditional MyBatis API
The traditional approach creates a SqlSession, passes a StatementId and parameters, and executes the operation. While simple, it does not follow object‑oriented or interface‑driven design.
5.1.2 Mapper Interface
Each Mapper interface method corresponds to an XML node ( <select>, <insert>, etc.). The method name matches the node id, the method parameter type maps to parameterType, and the return type maps to resultMap. MyBatis generates a dynamic proxy for the interface, allowing clean, type‑safe calls.
5.2 Data Processing Layer
The core of MyBatis performs two functions:
Build dynamic SQL from input parameters; Execute the SQL and wrap the result set into a List .
5.2.1 Parameter Mapping and Dynamic SQL Generation
MyBatis uses OGNL to evaluate expressions and construct SQL dynamically, providing strong flexibility.
5.2.2 Executing SQL and Wrapping Results
After generating the SQL, MyBatis executes it and converts the ResultSet into Java collections, supporting one‑to‑many and many‑to‑one relationships via nested queries or nested result maps.
5.3 Framework Support Layer
Key supporting components include:
Transaction management – essential for any ORM framework; Connection pool management – critical for high‑throughput applications; Cache mechanism – session‑level caching reduces database load; SQL configuration – XML or annotation‑based mappings.
5.4 Bootstrap Layer
The bootstrap layer configures and starts MyBatis, supporting both XML‑based configuration and Java‑API configuration.
5.5 Main Components and Their Relationships
SqlSession: Top‑level API for database interaction. Executor: Core dispatcher that generates SQL, manages cache, and delegates to StatementHandler . StatementHandler: Creates PreparedStatement , sets parameters, and executes the query. ParameterHandler: Sets parameters on the PreparedStatement using appropriate TypeHandler s. ResultSetHandler: Converts ResultSet into Java objects. TypeHandler: Handles conversion between Java types and JDBC types. MappedStatement: Holds the configuration of a single SQL statement. SqlSource: Generates the final SQL (including dynamic parts) and wraps it into a BoundSql object. Configuration: Central container that stores all MyBatis configuration, mirroring the XML structure.
6 SqlSession Working Process Analysis
Creating a SqlSession and executing a query involves the following steps:
SqlSession sqlSession = factory.openSession();
List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary", params);The selectList method internally retrieves the corresponding MappedStatement from Configuration, delegates to the Executor, which builds a CacheKey, obtains or creates a BoundSql, prepares a Statement via StatementHandler, and finally returns a List of results.
6.1 Executor Query Flow
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1. Generate BoundSql
BoundSql boundSql = ms.getBoundSql(parameter);
// 2. Create cache key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 3. Delegate to doQuery (which may hit cache or DB)
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}6.2 StatementHandler Parameterization
public void parameterize(Statement statement) throws SQLException {
// Use ParameterHandler to set values on PreparedStatement
parameterHandler.setParameters((PreparedStatement) statement);
}6.3 StatementHandler Query Execution
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E>handleResultSets(ps);
}6.4 ResultSetHandler Conversion
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ResultSetWrapper rsw = getFirstResultSet(stmt);
// Iterate over result maps and convert each ResultSet
// ... (omitted for brevity) ...
return collapseSingleResultList(multipleResults);
}7 MyBatis Initialization Mechanism
7.1 What MyBatis Initialization Does
MyBatis loads all runtime configuration information (environments, data sources, mappers, type handlers, etc.) into a Configuration object, which mirrors the XML structure.
7.2 Creating Configuration from XML
The process is:
SqlSessionFactoryBuilder calls new XMLConfigBuilder(inputStream, environment, properties);
XMLConfigBuilder parses the XML into a DOM, wraps it with XPathParser; parse() extracts the /configuration node and calls parseConfiguration();
Each sub‑node (properties, typeAliases, plugins, environments, mappers, etc.) is processed and stored in the Configuration instance;
The fully populated Configuration is returned to the builder, which creates a DefaultSqlSessionFactory.
Environment Parsing Example
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder envBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(envBuilder.build());
}
}
}
}7.3 Manual Configuration via Java API
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null, null);
Configuration configuration = parser.parse();
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession();
List<?> list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");7.4 Design Patterns Used
MyBatis heavily employs the Builder pattern (e.g., SqlSessionFactoryBuilder, Environment.Builder) to construct complex objects with optional parameters. The Factory pattern appears in TransactionFactory and DataSourceFactory. The Proxy pattern is used for Mapper interface implementations.
8 Conclusion
The article demonstrates how JDBC can be gradually wrapped into MyBatis, outlines the architectural components, shows the initialization flow, and highlights design patterns that make MyBatis extensible and performant.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
