From JDBC to MyBatis: Why Java Persistence Evolved and How to Optimize It

This article walks through the step‑by‑step JDBC query process, explains why wrapping JDBC in a framework like MyBatis is beneficial, analyzes MyBatis’s core components and execution flow, and presents five practical optimizations—from connection pooling to dynamic SQL and result caching—to improve Java backend performance.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
From JDBC to MyBatis: Why Java Persistence Evolved and How to Optimize It

1 Introduction

This article explains how JDBC evolves into MyBatis, why JDBC should be encapsulated into a persistence framework, and points out areas where MyBatis itself can be improved.

2 JDBC Query Process

The basic JDBC query involves seven steps:

Load JDBC driver;

Obtain a database connection;

Create a Statement object;

Set SQL parameters;

Execute the SQL and get a ResultSet;

Transform the ResultSet into a desired structure;

Release resources (close Connection, Statement, ResultSet).

Example 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{
        Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
        String url = "jdbc:oracle:thin:@localhost:1521:ORACLEDB";
        String user = "trainer";
        String password = "trainer";
        connection = DriverManager.getConnection(url,user,password);
        String sql = "select * from userinfo where user_id = ? ";
        stmt = connection.prepareStatement(sql);
        stmt.setString(1, "zhangsan");
        rs = stmt.executeQuery();
        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; } }catch(SQLException e){ e.printStackTrace(); }
        try{ if(stmt!=null){ stmt.close(); stmt=null; } }catch(SQLException e){ e.printStackTrace(); }
        try{ if(connection!=null){ connection.close(); connection=null; } }catch(SQLException e){ e.printStackTrace(); }
    }
    return resultList;
}

3 From JDBC to MyBatis

Among the seven JDBC steps, many can be encapsulated to reduce boilerplate code.

3.1 Optimize Connection Acquisition and Release

Frequent opening/closing of connections wastes resources and hurts performance. Using a connection pool (e.g., DBCP or container‑provided JNDI) reuses existing connections and shortens latency.

By abstracting the pool behind DataSource , the application can switch implementations without code changes.

3.2 Centralize SQL Storage

Scattering SQL across Java classes leads to poor readability, difficult maintenance, and extra compile‑time work. Storing SQL in configuration files or a database (key‑value) improves manageability and allows direct execution in DB clients.

Loading SQL by key also raises the question of how to cache and retrieve the statements efficiently.

3.3 Parameter Mapping and Dynamic SQL

When the number of parameters varies, static placeholders are insufficient. Introducing a simple templating syntax (e.g., #param#) and an SQL parser enables dynamic statement generation based on a Map of parameters.

3.4 Result Mapping and Caching

After execution, the ResultSet must be transformed into Java objects (beans, maps, lists). MyBatis also supports session‑level caching to avoid repeated queries for identical parameter sets.

3.5 Eliminate Duplicate SQL

When many statements share fragments, extracting common parts into reusable SQL blocks reduces duplication and eases future modifications.

4 MyBatis Shortcomings

MyBatis requires explicit SQL for every operation, which can become overwhelming for large applications.

Potential improvement: generate simple CRUD SQL automatically from Java bean metadata (e.g., map UserInfo to USER_INFO table) using reflection.

5 MyBatis Overall Design

MyBatis architecture diagram
MyBatis architecture diagram

5.1 Interface Layer

MyBatis offers two interaction modes:

Traditional API where the developer passes a statement ID and parameters to SqlSession;

Mapper interface where each method corresponds to a mapped statement, enabling type‑safe, annotation‑driven configuration.

5.1.1 Traditional API

Creates a SqlSession, calls selectList, insert, etc., using statement IDs defined in XML.

5.1.2 Mapper Interface

MyBatis generates a dynamic proxy for the interface; method names map to XML <select|insert|update|delete> nodes, allowing clean, interface‑driven code.

5.2 Data Processing Layer

Core responsibilities:

Build dynamic SQL from parameters;

Execute SQL and wrap the ResultSet into a List.

5.2.1 Parameter Mapping & Dynamic SQL

Uses OGNL expressions to construct SQL at runtime, providing strong flexibility.

5.2.2 Result Set Handling

Converts 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

Includes transaction management, connection‑pool handling, caching, and configuration loading (XML vs. Java API).

5.4 Bootstrap Layer

Two ways to start MyBatis:

XML configuration file;

Programmatic Java API.

5.5 Core Components and Relationships

Key classes:

SqlSession : top‑level API for database interaction;

Executor : creates BoundSql, manages cache, and delegates to StatementHandler;

StatementHandler : prepares PreparedStatement, sets parameters via ParameterHandler, and executes queries;

ParameterHandler : maps Java values to JDBC parameters;

ResultSetHandler : transforms ResultSet into Java objects;

TypeHandler : converts between Java types and JDBC types;

MappedStatement : encapsulates a single mapped SQL node;

SqlSource : generates BoundSql from parameters;

Configuration : holds all parsed configuration data.

6 SqlSession Workflow

Typical usage:

SqlSession sqlSession = factory.openSession();
List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary", params);
SqlSession.selectList

looks up the MappedStatement by ID, creates a BoundSql, obtains a cache key, and delegates to the Executor to perform the query.

7 MyBatis Initialization

7.1 What Initialization Does

It loads all configuration information (environments, mappers, type handlers, plugins, etc.) into a Configuration object.

7.2 Building Configuration from XML

The process: SqlSessionFactoryBuilder.build(InputStream) creates an XMLConfigBuilder; XMLConfigBuilder.parse() reads the XML, creates a Document, and wraps it in an XPathParser;

The parser extracts nodes such as properties, typeAliases, plugins, environments, mappers, etc.;

Each node is parsed and the corresponding objects are stored in the Configuration instance;

The fully populated Configuration is returned and used to construct a DefaultSqlSessionFactory.

7.3 Manual Java API Construction

Developers can instantiate XMLConfigBuilder directly, call parse() to obtain a Configuration, and then build a SqlSessionFactory with new SqlSessionFactoryBuilder().build(configuration).

7.4 Design Patterns Used

7.4.1 Builder Pattern – SqlSessionFactory

Multiple overloaded build methods allow different parameter combinations; the builder isolates construction logic from the final SqlSessionFactory object.

7.4.2 Builder Pattern – Environment

The inner static Environment.Builder assembles the environment’s ID, TransactionFactory, and DataSource before creating an immutable Environment instance.

8 Summary

The article demonstrates the transition from raw JDBC to the MyBatis framework, highlights practical performance optimizations, explains MyBatis’s internal architecture and execution flow, and shows how the framework is initialized using XML or Java API, employing Builder and Factory patterns to keep configuration flexible and extensible.

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 PatternsMyBatisJDBCSQL OptimizationJava backendConnection Pooling
Java High-Performance Architecture
Written by

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.

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.