From JDBC to MyBatis: Evolution, Design, and Optimization of the Persistence Layer
This article explains how JDBC evolves into MyBatis, detailing the seven-step JDBC query process, the motivations for encapsulating JDBC, MyBatis architecture, core components, initialization mechanisms, design patterns, and code examples, while offering optimization strategies for connection handling, SQL management, and result mapping.
1 Introduction
This article mainly explains how JDBC gradually evolves into MyBatis, emphasizing why JDBC should be encapsulated into a persistence framework like MyBatis and discussing areas where MyBatis can be improved.
2 JDBC Query Analysis
We first look at the most familiar and fundamental way of querying a database using JDBC, which generally involves the following seven steps:
Load JDBC driver;
Establish and obtain a database connection;
Create a JDBC Statement object;
Set input parameters for the SQL statement;
Execute the SQL statement and obtain the result set;
Convert the result set into a desired data structure and return it;
Release related resources (close Connection, Statement, ResultSet).
Recommended open‑source Spring Boot project:
https://github.com/javastacks/spring-boot-best-practiceSpecific 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 = ? ";
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 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 Evolution from JDBC to MyBatis
From the seven JDBC steps, we identify which steps can be further encapsulated to reduce developer code.
3.1 Optimize Connection Acquisition and Release
Frequent opening and closing of database connections wastes resources and hurts performance. The solution is to use a connection pool (e.g., DBCP or container‑provided JNDI pool) to reuse connections.
3.2 Centralize SQL Storage
SQL statements are scattered across Java classes, leading to poor readability, maintenance difficulty, and the need to recompile for changes. The solution is to store SQL centrally in configuration files or a database using a key‑value format.
3.3 Parameter Mapping and Dynamic SQL
When parameters are uncertain (e.g., variable‑length query conditions), we need to dynamically generate SQL. This can be achieved by embedding conditional tags (similar to JSTL) in SQL and using a custom parser with placeholders like #variable#.
3.4 Result Mapping and Caching
After executing a query, the ResultSet must be transformed into Java objects (Map, List, Bean, etc.). Additionally, caching the query result (using a composite key of SQL + parameters) can improve performance.
3.5 Eliminate Duplicate SQL
When many SQL statements are similar, duplicate code appears. The solution is to modularize common SQL fragments and reference them, so changes need to be made only once.
4 MyBatis Improvement Points
MyBatis requires explicit SQL for every operation, leading to a large amount of SQL code. For simple single‑table CRUD, we could generate SQL automatically via reflection based on JavaBean‑to‑table mapping.
5 MyBatis Overall Design
MyBatis consists of several layers:
Interface Layer : Provides two ways to interact with the database – traditional MyBatis API and Mapper interfaces.
Data Processing Layer : Builds dynamic SQL from parameters and executes it, returning results as List objects.
Framework Support Layer : Handles transaction management, connection pooling, caching, and SQL configuration methods (XML vs. annotations).
Bootstrap Layer : Configures and starts MyBatis using either XML or Java API.
5.1 Interface Layer – Interaction Methods
Traditional API requires providing a Statement ID and parameters to SqlSession. Mapper interfaces map Java methods to <select|insert|update|delete> nodes, enabling interface‑driven programming.
5.2 Data Processing Layer – Core Components
The core components include: SqlSession: Top‑level API for database sessions. Executor: Generates dynamic SQL, manages cache, and delegates execution. StatementHandler: Prepares PreparedStatement, sets parameters, and executes queries. ParameterHandler: Sets parameters on the PreparedStatement using appropriate TypeHandler s. ResultSetHandler: Converts ResultSet into Java collections or objects. TypeHandler: Handles conversion between Java types and JDBC types. MappedStatement: Encapsulates a single <select|insert|update|delete> node. SqlSource: Generates BoundSql (dynamic SQL) from parameters. Configuration: Holds all MyBatis configuration data.
5.3 Framework Support Layer
Includes transaction management, connection pool management, session‑level caching, and support for both XML and annotation‑based SQL configuration.
5.4 Bootstrap Layer
MyBatis initialization loads configuration into a Configuration object, which is then used to create a SqlSessionFactory. Initialization can be XML‑based or Java‑API‑based.
6 SqlSession Work Process
Creating a SqlSession and executing a statement involves:
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, then delegates to the Executor to perform the query.
6.1 Executor Query Flow
The Executor.query() method:
Generates a BoundSql (dynamic SQL) from the MappedStatement and parameters.
Creates a cache key for the query.
Attempts to retrieve a cached result; if absent, it calls queryFromDatabase to execute the SQL.
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}6.2 StatementHandler
StatementHandlerprepares the JDBC Statement, sets parameters via ParameterHandler, and executes the query.
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E>handleResultSets(ps);
}6.3 ParameterHandler
Sets each parameter on the PreparedStatement using the appropriate TypeHandler.
public void setParameters(PreparedStatement ps) throws SQLException {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping pm = parameterMappings.get(i);
Object value = ... // resolve value from parameter object
TypeHandler typeHandler = pm.getTypeHandler();
JdbcType jdbcType = pm.getJdbcType();
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}6.4 ResultSetHandler
Converts the ResultSet into a list of objects according to the defined ResultMap.
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ResultSet rs = ((PreparedStatement) stmt).getResultSet();
// iterate rows, map columns to objects based on ResultMap
// return List of mapped objects
}7 MyBatis Initialization Mechanism
7.1 What Initialization Does
MyBatis loads all configuration needed at runtime (data sources, environments, mappers, plugins, type handlers, etc.) into a Configuration object, which mirrors the structure of the XML configuration file.
7.2 Building Configuration from XML
The process: SqlSessionFactoryBuilder.build(InputStream) creates an XMLConfigBuilder. XMLConfigBuilder.parse() parses the XML into a Configuration object.
Various sub‑elements ( properties, typeAliases, environments, mappers, etc.) are processed and stored in the Configuration.
The Configuration is used to create a DefaultSqlSessionFactory.
Parsing Environments 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 Java API Initialization
Developers can directly instantiate XMLConfigBuilder and call parse() to obtain a Configuration, then build a SqlSessionFactory:
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);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 is used for creating Executor, StatementHandler, and ParameterHandler implementations.
Conclusion
The article provides a comprehensive walkthrough of how JDBC code can be refactored into MyBatis, covering the evolution steps, core MyBatis components, initialization process, and design patterns, while offering practical code examples and optimization suggestions for building robust backend persistence layers.
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.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.
