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.

Top Architect
Top Architect
Top Architect
From JDBC to MyBatis: Evolution, Design, and Optimization of the Persistence Layer

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-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 = ? ";
        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

StatementHandler

prepares 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsSQLMyBatisJDBC
Top Architect
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.