引言:iBatis的历史与核心价值

iBatis(现已演变为MyBatis)是一个优秀的持久层框架,它将SQL语句从Java代码中分离出来,存储在XML或注解中,实现了数据访问逻辑与业务逻辑的解耦。虽然现在MyBatis更为流行,但理解iBatis的核心原理对于掌握现代ORM框架至关重要。

iBatis的核心优势在于:

  • SQL与代码分离:SQL语句独立存储,便于维护和优化
  • 灵活的结果映射:支持复杂对象关系映射
  • 动态SQL能力:根据条件动态生成SQL语句
  • 低侵入性:对现有业务代码影响小

一、基础配置详解

1.1 SqlMapConfig.xml核心配置

iBatis的主配置文件通常命名为SqlMapConfig.xml,它定义了数据源、事务管理器和SQL映射文件的位置。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">

<sqlMapConfig>
    <!-- 1. 属性配置 -->
    <properties resource="jdbc.properties" />
    
    <!-- 2. 全局设置 -->
    <settings 
        cacheModelsEnabled="true"
        enhancementEnabled="true"
        lazyLoadingEnabled="true"
        errorTracingEnabled="true"
        maxRequests="300"
        maxSessions="100"
        maxTransactions="50"
        useStatementNamespaces="false"/>
    
    <!-- 3. 事务管理器 -->
    <transactionManager type="JDBC">
        <dataSource type="SIMPLE">
            <property name="JDBC.Driver" value="${jdbc.driver}"/>
            <property name="JDBC.ConnectionURL" value="${jdbc.url}"/>
            <property name="JDBC.Username" value="${jdbc.username}"/>
            <property name="JDBC.Password" value="${jdbc.password}"/>
            <property name="Pool.MaximumActiveConnections" value="10"/>
            <property name="Pool.MaximumIdleConnections" value="5"/>
            <property name="Pool.MaximumWait" value="60000"/>
        </dataSource>
    </transactionManager>
    
    <!-- 4. SQL映射文件 -->
    <sqlMap resource="com/example/mappers/User.xml"/>
    <sqlMap resource="com/example/mappers/Order.xml"/>
</sqlMapConfig>

关键配置说明:

  • properties:引入外部属性文件,实现配置与代码分离
  • settings:全局参数设置
    • cacheModelsEnabled:启用缓存模型
    • lazyLoadingEnabled:启用延迟加载
    • maxRequests:最大并发请求数
  • transactionManager:事务管理器配置,支持JDBC和JTA两种类型
  • dataSource:数据源配置,SIMPLE类型为iBatis内置连接池

1.2 SQL映射文件基础结构

SQL映射文件定义了Java对象与SQL语句之间的映射关系。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">

<sqlMap namespace="User">
    <!-- 1. 类型别名 -->
    <typeAlias alias="User" type="com.example.model.User"/>
    
    <!-- 2. 结果映射 -->
    <resultMap id="UserResult" class="User">
        <result property="id" column="user_id"/>
        <result property="name" column="user_name"/>
        <result property="email" column="user_email"/>
        <result property="createdTime" column="created_time"/>
    </resultMap>
    
    <!-- 3. 参数映射 -->
    <parameterMap id="UserParam" class="User">
        <parameter property="name"/>
        <parameter property="email"/>
    </parameterMap>
    
    <!-- 4. SQL语句 -->
    <select id="getUserById" resultMap="UserResult">
        SELECT user_id, user_name, user_email, created_time
        FROM users
        WHERE user_id = #id#
    </select>
    
    <insert id="insertUser" parameterMap="UserParam">
        INSERT INTO users (user_name, user_email)
        VALUES (?, ?)
    </insert>
    
    <update id="updateUser" parameterMap="UserParam">
        UPDATE users
        SET user_name = ?, user_email = ?
        WHERE user_id = #id#
    </update>
    
    <delete id="deleteUser">
        DELETE FROM users WHERE user_id = #id#
    </delete>
</sqlMap>

1.3 Java代码调用示例

import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import java.io.Reader;
import java.io.InputStreamReader;
import java.util.List;

public class UserDAO {
    private SqlMapClient sqlMapClient;
    
    public UserDAO() {
        try {
            // 加载配置文件
            Reader reader = new InputStreamReader(
                getClass().getResourceAsStream("/SqlMapConfig.xml"));
            sqlMapClient = SqlMapClientBuilder.buildSqlMapClient(reader);
        } catch (Exception e) {
            throw new RuntimeException("初始化SqlMapClient失败", e);
        }
    }
    
    // 查询用户
    public User getUserById(int id) throws Exception {
        return (User) sqlMapClient.queryForObject("User.getUserById", id);
    }
    
    // 插入用户
    public void insertUser(User user) throws Exception {
        sqlMapClient.insert("User.insertUser", user);
    }
    
    // 更新用户
    public void updateUser(User user) throws Exception {
        sqlMapClient.update("User.updateUser", user);
    }
    
    // 删除用户
    public void deleteUser(int id) throws Exception {
        sqlMapClient.delete("User.deleteUser", id);
    }
    
    // 查询列表
    public List<User> getAllUsers() throws Exception {
        return sqlMapClient.queryForList("User.getAllUsers", null);
    }
}

二、核心映射技巧

2.1 结果映射(ResultMap)深度应用

2.1.1 基础结果映射

<resultMap id="UserResult" class="User">
    <result property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
</resultMap>

<select id="getUserById" resultMap="UserResult">
    SELECT user_id, user_name, user_email FROM users WHERE user_id = #id#
</select>

2.1.2 复杂对象映射(关联映射)

<!-- 用户类包含订单列表 -->
<resultMap id="UserWithOrdersResult" class="User">
    <result property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
    
    <!-- 一对多关联 -->
    <result property="orders" column="user_id" 
            select="Order.getOrdersByUserId"/>
</resultMap>

<select id="getUserWithOrders" resultMap="UserWithOrdersResult">
    SELECT user_id, user_name, user_email FROM users WHERE user_id = #id#
</select>

2.1.3 嵌套结果映射

<!-- 订单类包含用户信息 -->
<resultMap id="OrderWithUserResult" class="Order">
    <result property="id" column="order_id"/>
    <result property="amount" column="order_amount"/>
    <result property="orderTime" column="order_time"/>
    
    <!-- 嵌套用户信息 -->
    <result property="user.id" column="user_id"/>
    <result property="user.name" column="user_name"/>
    <result property="user.email" column="user_email"/>
</resultMap>

<select id="getOrderWithUser" resultMap="OrderWithUserResult">
    SELECT 
        o.order_id, o.order_amount, o.order_time,
        u.user_id, u.user_name, u.user_email
    FROM orders o
    INNER JOIN users u ON o.user_id = u.user_id
    WHERE o.order_id = #orderId#
</select>

2.2 参数映射(ParameterMap)高级用法

2.2.1 基础参数映射

<parameterMap id="UserParam" class="User">
    <parameter property="name"/>
    <parameter property="email"/>
</parameterMap>

<insert id="insertUser" parameterMap="UserParam">
    INSERT INTO users (user_name, user_email) VALUES (?, ?)
</insert>

2.2.2 带JDBC类型的参数映射

<parameterMap id="AdvancedParam" class="java.util.Map">
    <parameter property="name" jdbcType="VARCHAR"/>
    <parameter property="age" jdbcType="INTEGER"/>
    <parameter property="salary" jdbcType="DECIMAL"/>
    <parameter property="birthDate" jdbcType="DATE"/>
</parameterMap>

<insert id="insertWithTypes" parameterMap="AdvancedParam">
    INSERT INTO employees (name, age, salary, birth_date)
    VALUES (?, ?, ?, ?)
</insert>

2.2.3 存储过程参数映射

<parameterMap id="ProcParam" class="java.util.Map">
    <parameter property="inParam" mode="IN" jdbcType="VARCHAR"/>
    <parameter property="outParam" mode="OUT" jdbcType="INTEGER"/>
</parameterMap>

<procedure id="callProc" parameterMap="ProcParam">
    {call my_proc(?, ?)}
</procedure>

2.3 动态SQL技巧

2.3.1 条件查询动态SQL

<select id="searchUsers" resultMap="UserResult">
    SELECT user_id, user_name, user_email FROM users
    <dynamic prepend="WHERE">
        <isNotNull prepend="AND" property="name">
            user_name LIKE '%$name$%'
        </isNotNull>
        <isNotNull prepend="AND" property="email">
            user_email LIKE '%$email$%'
        </isNotNull>
        <isNotNull prepend="AND" property="minAge">
            age >= #minAge#
        </isNotNull>
        <isNotNull prepend="AND" property="maxAge">
            age <= #maxAge#
        </isNotNull>
    </dynamic>
    <dynamic prepend="ORDER BY">
        <isNotNull prepend="," property="orderBy">
            $orderBy$
        </isNotNull>
    </dynamic>
</select>

2.3.2 批量操作动态SQL

<!-- 批量插入 -->
<insert id="batchInsert" parameterMap="UserParam">
    INSERT INTO users (user_name, user_email) VALUES
    <dynamic prepend="">
        <iterate property="userList" conjunction=",">
            (#userList[].name#, #userList[].email#)
        </iterate>
    </dynamic>
</insert>

<!-- 批量更新 -->
<update id="batchUpdate">
    <dynamic prepend="">
        <iterate property="userList" conjunction=";">
            UPDATE users
            SET user_name = #userList[].name#, 
                user_email = #userList[].email#
            WHERE user_id = #userList[].id#
        </iterate>
    </dynamic>
</update>

2.3.3 复杂条件动态SQL

<select id="complexSearch" resultMap="UserResult">
    SELECT user_id, user_name, user_email FROM users
    <dynamic prepend="WHERE">
        <isNotNull prepend="AND" property="searchType">
            <isEqual property="searchType" compareValue="NAME">
                user_name LIKE '%$keyword$%'
            </isEqual>
            <isEqual property="searchType" compareValue="EMAIL">
                user_email LIKE '%$keyword$%'
            </isEqual>
            <isEqual property="searchType" compareValue="BOTH">
                (user_name LIKE '%$keyword$%' OR user_email LIKE '%$keyword$%')
            </isEqual>
        </isNotNull>
        
        <isNotNull prepend="AND" property="statusList">
            status IN
            <dynamic prepend="(">
                <iterate property="statusList" conjunction=",">
                    #statusList[]#
                </iterate>
            </dynamic>
        </isNotNull>
    </dynamic>
</select>

2.4 缓存机制

2.4.1 缓存模型配置

<!-- 在SqlMapConfig.xml中启用缓存 -->
<sqlMapConfig>
    <settings cacheModelsEnabled="true"/>
</sqlMapConfig>

<!-- 在SQL映射文件中定义缓存模型 -->
<cacheModel id="userCache" type="LRU" readOnly="true" serialize="false">
    <property name="size" value="100"/>
    <property name="cacheSize" value="1000"/>
</cacheModel>

<!-- 应用缓存到查询 -->
<select id="getUserById" resultMap="UserResult" cacheModel="userCache">
    SELECT user_id, user_name, user_email FROM users WHERE user_id = #id#
</select>

2.4.2 缓存类型详解

<!-- LRU缓存:最近最少使用 -->
<cacheModel id="lruCache" type="LRU" readOnly="true">
    <property name="size" value="100"/>
</cacheModel>

<!-- FIFO缓存:先进先出 -->
<cacheModel id="fifoCache" type="FIFO" readOnly="true">
    <property name="size" value="100"/>
</cacheModel>

<!-- 缓存引用 -->
<cacheModel id="sharedCache" type="PERPETUAL" readOnly="true">
    <property name="size" value="200"/>
</cacheModel>

<cacheModel id="userCache" type="LRU" readOnly="true">
    <property name="reference" value="sharedCache"/>
</cacheModel>

三、高级优化技巧

3.1 性能优化策略

3.1.1 延迟加载优化

<!-- 在SqlMapConfig.xml中启用延迟加载 -->
<settings lazyLoadingEnabled="true"/>

<!-- 定义延迟加载的关联 -->
<resultMap id="UserWithLazyOrders" class="User">
    <result property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="orders" column="user_id" 
            select="Order.getOrdersByUserId" 
            lazyLoad="true"/>
</resultMap>

<!-- 订单查询 -->
<select id="getOrdersByUserId" resultMap="OrderResult">
    SELECT order_id, order_amount, order_time
    FROM orders
    WHERE user_id = #user_id#
</select>

3.1.2 批量操作优化

// 传统方式:N+1次数据库操作
public void insertUsersTraditional(List<User> users) throws Exception {
    for (User user : users) {
        sqlMapClient.insert("User.insertUser", user);
    }
}

// 优化方式:批量插入
public void insertUsersBatch(List<User> users) throws Exception {
    try {
        sqlMapClient.startBatch();
        for (User user : users) {
            sqlMapClient.insert("User.insertUser", user);
        }
        sqlMapClient.executeBatch();
    } catch (Exception e) {
        sqlMapClient.endTransaction();
        throw e;
    }
}

// 优化方式:使用动态SQL批量插入
public void insertUsersDynamicBatch(List<User> users) throws Exception {
    Map<String, Object> params = new HashMap<>();
    params.put("userList", users);
    sqlMapClient.insert("User.batchInsert", params);
}

3.1.3 分页查询优化

<!-- 通用分页查询 -->
<select id="getUsersByPage" resultMap="UserResult">
    SELECT user_id, user_name, user_email
    FROM users
    WHERE 1=1
    <dynamic prepend="AND">
        <isNotNull prepend="AND" property="name">
            user_name LIKE '%$name$%'
        </isNotNull>
    </dynamic>
    ORDER BY user_id
    LIMIT #startRow#, #pageSize#
</select>

<!-- 获取总数 -->
<select id="getUsersCount" resultClass="java.lang.Integer">
    SELECT COUNT(*) FROM users
    WHERE 1=1
    <dynamic prepend="AND">
        <isNotNull prepend="AND" property="name">
            user_name LIKE '%$name$%'
        </isNotNull>
    </dynamic>
</select>
// 分页查询实现
public class PageResult<T> {
    private List<T> data;
    private int total;
    private int pageSize;
    private int currentPage;
    
    // getters and setters
}

public PageResult<User> getUsersByPage(String name, int page, int pageSize) throws Exception {
    Map<String, Object> params = new HashMap<>();
    params.put("name", name);
    params.put("startRow", (page - 1) * pageSize);
    params.put("pageSize", pageSize);
    
    List<User> users = sqlMapClient.queryForList("User.getUsersByPage", params);
    int total = (Integer) sqlMapClient.queryForObject("User.getUsersCount", params);
    
    PageResult<User> result = new PageResult<>();
    result.setData(users);
    result.setTotal(total);
    result.setPageSize(pageSize);
    result.setCurrentPage(page);
    
    return result;
}

3.2 事务管理优化

3.2.1 事务边界控制

// 手动事务控制
public void executeTransaction() throws Exception {
    try {
        // 开始事务
        sqlMapClient.startTransaction();
        
        // 执行多个操作
        sqlMapClient.insert("User.insertUser", user1);
        sqlMapClient.insert("Order.insertOrder", order1);
        sqlMapClient.update("Inventory.updateStock", inventory);
        
        // 提交事务
        sqlMapClient.commitTransaction();
    } catch (Exception e) {
        // 回滚事务
        sqlMapClient.endTransaction();
        throw e;
    } finally {
        sqlMapClient.endTransaction();
    }
}

// 批量事务优化
public void executeBatchTransaction(List<User> users) throws Exception {
    try {
        sqlMapClient.startTransaction();
        sqlMapClient.startBatch();
        
        for (User user : users) {
            sqlMapClient.insert("User.insertUser", user);
        }
        
        sqlMapClient.executeBatch();
        sqlMapClient.commitTransaction();
    } finally {
        sqlMapClient.endTransaction();
    }
}

3.2.2 连接池优化配置

<transactionManager type="JDBC">
    <dataSource type="SIMPLE">
        <property name="JDBC.Driver" value="${jdbc.driver}"/>
        <property name="JDBC.ConnectionURL" value="${jdbc.url}"/>
        <property name="JDBC.Username" value="${jdbc.username}"/>
        <property name="JDBC.Password" value="${jdbc.password}"/>
        
        <!-- 连接池核心参数 -->
        <property name="Pool.MaximumActiveConnections" value="20"/>    <!-- 最大活跃连接 -->
        <property name="Pool.MaximumIdleConnections" value="10"/>      <!-- 最大空闲连接 -->
        <property name="Pool.MaximumWait" value="60000"/>              <!-- 最大等待时间(毫秒) -->
        <property name="Pool.PingQuery" value="SELECT 1"/>             <!-- 连接检测SQL -->
        <property name="Pool.PingEnabled" value="true"/>               <!-- 启用连接检测 -->
        <property name="Pool.PingConnectionsOlderThan" value="30000"/> <!-- 超时连接检测 -->
        <property name="Pool.PingConnectionsNotUsedFor" value="60000"/> <!-- 闲置连接检测 -->
    </dataSource>
</transactionManager>

3.3 SQL优化技巧

3.3.1 预编译SQL与参数绑定

<!-- 错误的写法:字符串拼接(存在SQL注入风险) -->
<select id="getUserByName" resultMap="UserResult">
    SELECT * FROM users WHERE user_name = '$name$'  <!-- 危险! -->
</select>

<!-- 正确的写法:参数绑定 -->
<select id="getUserByName" resultMap="UserResult">
    SELECT * FROM users WHERE user_name = #name#
</select>

<!-- 动态表名和列名(使用$符号) -->
<select id="getUsersByDynamicTable" resultMap="UserResult">
    SELECT * FROM $tableName$ WHERE $columnName$ = #value#
</select>

3.3.2 索引优化提示

<!-- 使用数据库特定的索引提示 -->
<select id="getUsersWithHint" resultMap="UserResult">
    SELECT /*+ INDEX(users idx_user_name) */ user_id, user_name, user_email
    FROM users
    WHERE user_name = #name#
</select>

<!-- MySQL索引提示 -->
<select id="getUsersWithMySQLHint" resultMap="UserResult">
    SELECT user_id, user_name, user_email
    FROM users USE INDEX (idx_user_name)
    WHERE user_name = #name#
</select>

3.3.3 避免N+1查询问题

<!-- 问题:N+1查询 -->
<resultMap id="UserWithOrdersN1" class="User">
    <result property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="orders" column="user_id" 
            select="Order.getOrdersByUserId"/>
</resultMap>

<!-- 解决方案:单次JOIN查询 -->
<resultMap id="UserWithOrdersJoin" class="User">
    <result property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
    
    <!-- 嵌套结果 -->
    <result property="orders" column="user_id" 
            select="Order.getOrdersByUserIdJoin"/>
</resultMap>

<select id="getUserWithOrdersJoin" resultMap="UserWithOrdersJoin">
    SELECT u.user_id, u.user_name, u.user_email
    FROM users u
    WHERE u.user_id = #id#
</select>

<!-- 关联订单查询(使用JOIN避免N+1) -->
<select id="getOrdersByUserIdJoin" resultMap="OrderResult">
    SELECT o.order_id, o.order_amount, o.order_time
    FROM orders o
    INNER JOIN users u ON o.user_id = u.user_id
    WHERE u.user_id = #user_id#
</select>

3.4 监控与调试

3.4.1 SQL日志配置

<!-- 在SqlMapConfig.xml中启用日志 -->
<settings errorTracingEnabled="true"/>

<!-- 在log4j.properties中配置 -->
log4j.logger.com.ibatis=DEBUG
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG

3.4.2 性能监控实现

// 自定义监控拦截器
public class PerformanceMonitor {
    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitor.class);
    
    public static void monitorQuery(String statementId, long startTime) {
        long duration = System.currentTimeMillis() - startTime;
        if (duration > 1000) { // 超过1秒的查询
            logger.warn("Slow query detected: {} took {}ms", statementId, duration);
        }
    }
}

// 在DAO层使用
public User getUserByIdWithMonitor(int id) throws Exception {
    long start = System.currentTimeMillis();
    try {
        return (User) sqlMapClient.queryForObject("User.getUserById", id);
    } finally {
        PerformanceMonitor.monitorQuery("User.getUserById", start);
    }
}

四、最佳实践与常见问题

4.1 命名规范

<!-- 推荐的命名规范 -->
<sqlMap namespace="User">
    <!-- 结果映射:ResultMap + 描述 -->
    <resultMap id="UserBaseResult" class="User">
        <!-- 属性映射:属性名 = 列名 -->
        <result property="id" column="user_id"/>
    </resultMap>
    
    <!-- 查询语句:操作 + 实体 + 条件 -->
    <select id="getUserById" resultMap="UserBaseResult">
        <!-- SQL语句:关键词大写,表名和列名小写 -->
        SELECT user_id, user_name FROM users WHERE user_id = #id#
    </select>
    
    <!-- 插入语句 -->
    <insert id="insertUser" parameterMap="UserParam">
        INSERT INTO users (user_name, user_email) VALUES (?, ?)
    </insert>
</sqlMap>

4.2 异常处理策略

// DAO层异常处理
public class UserDAO {
    private SqlMapClient sqlMapClient;
    
    public User getUserById(int id) throws UserNotFoundException {
        try {
            User user = (User) sqlMapClient.queryForObject("User.getUserById", id);
            if (user == null) {
                throw new UserNotFoundException("用户不存在: " + id);
            }
            return user;
        } catch (SQLException e) {
            throw new DataAccessException("数据库访问异常", e);
        } catch (Exception e) {
            throw new DataAccessException("系统异常", e);
        }
    }
    
    // 事务性操作的异常处理
    public void updateUserWithTransaction(User user) throws DataAccessException {
        try {
            sqlMapClient.startTransaction();
            
            // 检查用户是否存在
            User existing = (User) sqlMapClient.queryForObject("User.getUserById", user.getId());
            if (existing == null) {
                throw new UserNotFoundException("用户不存在");
            }
            
            // 更新用户
            sqlMapClient.update("User.updateUser", user);
            
            // 记录日志
            sqlMapClient.insert("UserLog.insertLog", 
                new UserLog(user.getId(), "UPDATE", new Date()));
            
            sqlMapClient.commitTransaction();
        } catch (UserNotFoundException e) {
            throw e;
        } catch (Exception e) {
            try {
                sqlMapClient.endTransaction();
            } catch (Exception ex) {
                // 记录回滚异常
                logger.error("事务回滚失败", ex);
            }
            throw new DataAccessException("更新用户失败", e);
        } finally {
            try {
                sqlMapClient.endTransaction();
            } catch (Exception e) {
                logger.error("关闭事务失败", e);
            }
        }
    }
}

4.3 配置管理最佳实践

# jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=secret

# 生产环境配置
jdbc.pool.min=5
jdbc.pool.max=20
jdbc.pool.timeout=30000

# 开发环境配置(用于本地开发)
jdbc.dev.driver=com.mysql.jdbc.Driver
jdbc.dev.url=jdbc:mysql://localhost:3306/mydb_dev
jdbc.dev.username=dev_user
jdbc.dev.password=dev_pass
<!-- 环境隔离配置 -->
<sqlMapConfig>
    <!-- 开发环境 -->
    <properties resource="jdbc-dev.properties" />
    
    <!-- 生产环境(注释掉开发配置,启用生产配置) -->
    <!-- <properties resource="jdbc-prod.properties" /> -->
    
    <transactionManager type="JDBC">
        <dataSource type="SIMPLE">
            <property name="JDBC.Driver" value="${jdbc.driver}"/>
            <property name="JDBC.ConnectionURL" value="${jdbc.url}"/>
            <property name="JDBC.Username" value="${jdbc.username}"/>
            <property name="JDBC.Password" value="${jdbc.password}"/>
        </dataSource>
    </transactionManager>
</sqlMapConfig>

4.4 常见问题解决方案

4.4.1 N+1查询问题

问题描述:查询主对象时,每关联一个子对象就会产生一次额外查询。

解决方案:

  1. 使用JOIN查询替代嵌套查询
  2. 使用批量查询优化
<!-- 批量查询优化 -->
<select id="getOrdersByUserIds" resultMap="OrderResult">
    SELECT user_id, order_id, order_amount
    FROM orders
    WHERE user_id IN
    <dynamic prepend="(">
        <iterate property="userIds" conjunction=",">
            #userIds[]#
        </iterate>
    </dynamic>
</select>

4.4.2 大数据量查询内存溢出

问题描述:查询返回大量数据导致内存溢出。

解决方案:

  1. 使用分页查询
  2. 使用流式查询(需要数据库驱动支持)
// 分页查询
public List<User> getUsersByPage(int page, int pageSize) throws Exception {
    Map<String, Object> params = new HashMap<>();
    params.put("startRow", (page - 1) * pageSize);
    params.put("pageSize", pageSize);
    return sqlMapClient.queryForList("User.getUsersByPage", params);
}

// 流式查询(伪代码,需要特定数据库驱动支持)
public void processLargeDataset(Processor processor) throws Exception {
    SqlMapSession session = sqlMapClient.openSession();
    try {
        ResultSet rs = session.executeQuery("User.getAllUsers", null);
        while (rs.next()) {
            User user = mapResultSetToUser(rs);
            processor.process(user);
        }
    } finally {
        session.close();
    }
}

4.4.3 数据库连接泄漏

问题描述:未正确关闭连接导致连接池耗尽。

解决方案:

  1. 使用try-finally确保关闭
  2. 使用连接池监控
// 正确的资源管理
public User getUserById(int id) throws Exception {
    SqlMapSession session = null;
    try {
        session = sqlMapClient.openSession();
        return (User) session.queryForObject("User.getUserById", id);
    } finally {
        if (session != null) {
            session.close();
        }
    }
}

// 使用try-with-resources(Java 7+)
public User getUserById(int id) throws Exception {
    try (SqlMapSession session = sqlMapClient.openSession()) {
        return (User) session.queryForObject("User.getUserById", id);
    }
}

五、从iBatis到MyBatis的演进

5.1 主要变化

  1. 配置文件:从SqlMapConfig.xml演变为mybatis-config.xml
  2. SQL映射:从<sqlMap>演变为<mapper>接口
  3. 注解支持:MyBatis增加了丰富的注解支持
  4. 动态SQL:MyBatis提供了更强大的动态SQL标签

5.2 迁移示例

// iBatis方式
public class UserDAO {
    private SqlMapClient sqlMapClient;
    
    public User getUserById(int id) throws Exception {
        return (User) sqlMapClient.queryForObject("User.getUserById", id);
    }
}

// MyBatis方式
public interface UserMapper {
    @Select("SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}")
    User getUserById(@Param("id") int id);
}

// 或者XML方式
public interface UserMapper {
    User getUserById(int id);
}

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    <select id="getUserById" resultType="User">
        SELECT user_id, user_name, user_email FROM users WHERE user_id = #{id}
    </select>
</mapper>

六、总结

iBatis作为经典的持久层框架,其核心思想是将SQL与代码分离,通过灵活的映射机制实现数据访问。掌握iBatis需要理解:

  1. 配置管理:主配置文件和映射文件的正确配置
  2. 映射技巧:结果映射、参数映射和动态SQL
  3. 性能优化:缓存、批量操作、分页查询
  4. 最佳实践:命名规范、异常处理、配置管理

虽然现在MyBatis更为流行,但iBatis的设计理念和核心技巧仍然具有重要的参考价值。通过深入理解iBatis,可以更好地掌握现代ORM框架的精髓,提升数据访问层的开发效率和系统性能。

在实际项目中,建议:

  • 优先使用MyBatis(iBatis的继任者)
  • 遵循最佳实践规范
  • 充分利用缓存和批量操作
  • 建立完善的监控和日志体系
  • 定期进行性能分析和优化

通过这些技巧和实践,可以构建出高效、稳定、可维护的数据访问层。