Understanding MyBatis Caching Mechanisms and Spring Integration

MyBatis Caching Architecture

MyBatis provides a multi-level caching system to optimize database query performance. The first-level cache operates at the SqlSession scope, meaning it is thread-local and isolated per session. When the same query is executed multiple times within a single session, the database is bypassed after the initial hit.

@Test
public void verifyFirstLevelCacheBehavior() {
    SqlSession session = sessionFactory.openSession();
    UserProfileDao dao = session.getMapper(UserProfileDao.class);

    UserProfile firstCall = dao.fetchById(101);
    UserProfile secondCall = dao.fetchById(101);
    UserProfile thirdCall = dao.fetchById(101);

    // First-level cache ensures the same object reference is returned
    System.out.println("Object identity preserved: " + (firstCall == thirdCall));
}

The second-level cache operates at the Mapper namespace level, allowing data to be shared across different sessions and threads. When a query is executed, MyBatis searches the second-level cache first, then falls back to the first-level cache, and finally queries the database if neither contains the data. Cache hit ratios for the second-level cache are automatically calculated and logged by MyBatis.

Multiple Mapper enterfaces can reference a shared cache space using the <cache-ref> configuration, enabling cross-mapper data sharing.

Pitfall of Second-Level Cache: Using the built-in second-level cache introduces a risk of dirty reads. If a transaction is rolled back, uncommitted modifications might have already been flushed in to the shared cache, making them visible to other threads. For robust, distributed caching with strict consistency controls, it is highly recommended to integrate professional third-party caching solutions (like Redis) at the business service layer rather than relying solely on MyBatis's default second-level cache.

Integrating MyBatis with Spring

The mybatis-spring module seamlessly bridges the two frameworks. One key benefit is exception translation: native MyBatis exceptions are automatically wrapped into Spring's unified DataAccessException hierarchy, allowing for consistent error handling across your Spring application.

Spring XML Configuration

The integration requires setting up a SqlSessionFactoryBean, a MapperScannerConfigurer, and Spring's transaction management. A separate mybatis-config.xml file can be retained solely for MyBatis-specific settings (like underscore-to-camel-case mapping), as the datasource and mapper scanning are now managed by Spring.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath:database.properties" ignore-unresolvable="true" />

    <!-- Database Connection Pool -->
    <bean id="appDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${db.driver}" />
        <property name="url" value="${db.url}" />
        <property name="username" value="${db.user}" />
        <property name="password" value="${db.password}" />
        <property name="maxActive" value="30" />
        <property name="initialSize" value="5" />
        <property name="maxWait" value="60000" />
        <property name="poolPreparedStatements" value="true" />
    </bean>

    <!-- MyBatis Session Factory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="appDataSource" />
        <property name="typeAliasesPackage" value="com.app.domain" />
        <property name="mapperLocations" value="classpath*:mappings/*.xml" />
        <!-- Optional: link to custom MyBatis global settings -->
        <property name="configLocation" value="classpath:mybatis-config.xml" />
    </bean>

    <!-- Mapper Interface Scanning -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.app.repository" />
    </bean>

    <!-- Transaction Management -->
    <bean id="appTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="appDataSource" />
    </bean>

    <tx:annotation-driven transaction-manager="appTransactionManager" />
    <context:component-scan base-package="com.app" />

</beans>

Lean MyBatis Global Configuration

Since datasource and mapper registrations are handled by the Spring beans above, the mybatis-config.xml only needs to contain MyBatis behavioral settings.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
    <typeAliases>
        <package name="com.app.domain"/>
    </typeAliases>
</configuration>

Tags: MyBatis Spring Framework Caching java JDBC

Posted on Wed, 19 Aug 2026 16:18:15 +0000 by abhishekphp6