Mybatis学习笔记(一)

发布于:2025-08-15 ⋅ 阅读:(15) ⋅ 点赞:(0)

这是一份全面的Mybatis和MybatisPlus开发笔记,涵盖从基础使用到进阶特性的所有内容,适合日常开发参考和深入学习。

Mybatis核心基础

Mybatis简介与架构

什么是Mybatis

简要描述:MyBatis是一个优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。

核心概念

  • 持久层框架:专门处理数据持久化的框架
  • 半自动ORM:相比Hibernate等全自动ORM,MyBatis需要手写SQL,提供更好的SQL控制能力
  • SQL映射:通过XML或注解将SQL语句与Java方法进行映射

与其他框架的区别

  • vs JDBC:减少样板代码,提供更好的参数映射和结果集处理
  • vs Hibernate:更灵活的SQL控制,更适合复杂查询和性能优化
  • vs Spring Data JPA:更接近原生SQL,学习成本更低

Mybatis整体架构

核心架构层次

应用层 (Application)
    ↓
API接口层 (SqlSession)
    ↓
数据处理层 (Executor, StatementHandler, ParameterHandler, ResultSetHandler)
    ↓
基础支撑层 (Configuration, MappedStatement, Cache)
    ↓
数据库层 (Database)

架构组件说明

  • Configuration:全局配置信息,包含所有配置项
  • SqlSessionFactory:SqlSession工厂,负责创建SqlSession
  • SqlSession:执行SQL的会话,提供操作数据库的API
  • Executor:执行器,负责SQL的执行和缓存维护
  • MappedStatement:映射语句,包含SQL信息和映射规则

核心组件详解

SqlSessionFactory

简要描述:SqlSessionFactory是MyBatis的核心工厂类,负责创建SqlSession实例。它是线程安全的,在应用运行期间应该只创建一次。

核心概念

  • 工厂模式:使用工厂模式创建SqlSession
  • 单例模式:整个应用中通常只需要一个SqlSessionFactory实例
  • 线程安全:可以被多个线程同时访问

创建方式

// 方式1:通过XML配置文件创建
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

// 方式2:通过Java代码配置创建
DataSource dataSource = getDataSource(); // 获取数据源
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(UserMapper.class); // 添加Mapper
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

最佳实践

  • 使用单例模式管理SqlSessionFactory
  • 在Spring环境中通过依赖注入管理
  • 避免频繁创建和销毁

SqlSession

简要描述:SqlSession是MyBatis工作的主要顶层API,它提供了执行SQL命令、获取映射器和管理事务的方法。

核心概念

  • 会话概念:代表一次数据库会话
  • 非线程安全:每个线程都应该有自己的SqlSession实例
  • 事务管理:负责事务的提交和回滚

基本使用

// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    // 方式1:直接执行SQL
    User user = sqlSession.selectOne("com.example.UserMapper.selectUser", 1);
    
    // 方式2:通过Mapper接口
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user2 = mapper.selectUser(1);
    
    // 提交事务
    sqlSession.commit();
} finally {
    // 关闭会话
    sqlSession.close();
}

重要方法说明

// 查询方法
T selectOne(String statement, Object parameter);           // 查询单个对象
List<E> selectList(String statement, Object parameter);   // 查询列表
Map<K,V> selectMap(String statement, Object parameter, String mapKey); // 查询Map

// 增删改方法
int insert(String statement, Object parameter);   // 插入
int update(String statement, Object parameter);   // 更新
int delete(String statement, Object parameter);   // 删除

// 事务管理
void commit();          // 提交事务
void rollback();        // 回滚事务
void close();           // 关闭会话

使用注意事项

  • SqlSession不是线程安全的,不能在多线程间共享
  • 使用完毕后必须关闭,建议使用try-with-resources
  • 在Spring环境中通过SqlSessionTemplate管理

Mapper接口

简要描述:Mapper接口是MyBatis的核心特性之一,它允许你定义接口方法来映射SQL语句,无需编写实现类。

核心概念

  • 接口映射:通过接口方法名映射到SQL语句
  • 动态代理:MyBatis使用动态代理技术生成接口实现
  • 类型安全:提供编译时类型检查

定义Mapper接口

public interface UserMapper {
    // 查询单个用户
    User selectUser(Long id);
    
    // 查询用户列表
    List<User> selectUsers(@Param("name") String name, @Param("age") Integer age);
    
    // 插入用户
    int insertUser(User user);
    
    // 更新用户
    int updateUser(User user);
    
    // 删除用户
    int deleteUser(Long id);
    
    // 复杂查询
    List<User> selectUsersByCondition(@Param("condition") UserCondition condition);
}

对应的XML映射

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 查询单个用户 -->
    <select id="selectUser" parameterType="long" resultType="User">
        SELECT id, name, age, email 
        FROM user 
        WHERE id = #{id}
    </select>
    
    <!-- 查询用户列表 -->
    <select id="selectUsers" resultType="User">
        SELECT id, name, age, email 
        FROM user 
        WHERE 1=1
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </select>
    
    <!-- 插入用户 -->
    <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (name, age, email) 
        VALUES (#{name}, #{age}, #{email})
    </insert>
    
</mapper>

注解方式映射

public interface UserMapper {
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectUser(Long id);
    
    @Insert("INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertUser(User user);
    
    @Update("UPDATE user SET name=#{name}, age=#{age} WHERE id=#{id}")
    int updateUser(User user);
    
    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteUser(Long id);
}

Executor执行器

简要描述:Executor是MyBatis的核心执行器,负责SQL语句的执行、参数设置、结果集处理和缓存管理。

核心概念

  • 执行器类型:Simple、Reuse、Batch三种类型
  • 缓存管理:负责一级缓存的维护
  • SQL执行:协调StatementHandler、ParameterHandler、ResultSetHandler工作

执行器类型详解

// SimpleExecutor:简单执行器(默认)
// 每次执行都会创建新的Statement
public class SimpleExecutor extends BaseExecutor {
    // 特点:简单直接,每次都创建新的Statement
    // 适用:一般的CRUD操作
}

// ReuseExecutor:重用执行器
// 重用Statement,减少Statement创建开销
public class ReuseExecutor extends BaseExecutor {
    // 特点:重用相同SQL的Statement
    // 适用:有重复SQL执行的场景
}

// BatchExecutor:批量执行器
// 支持批量操作,提高批量插入/更新性能
public class BatchExecutor extends BaseExecutor {
    // 特点:支持JDBC批量操作
    // 适用:大量数据的批量操作
}

配置执行器类型

<!-- 在mybatis-config.xml中配置 -->
<configuration>
    <settings>
        <!-- SIMPLE, REUSE, BATCH -->
        <setting name="defaultExecutorType" value="SIMPLE"/>
    </settings>
</configuration>

执行流程

// Executor执行SQL的基本流程
public <E> List<E> query(MappedStatement ms, Object parameter, 
                        RowBounds rowBounds, ResultHandler resultHandler) {
    // 1. 获取BoundSql(包含SQL和参数信息)
    BoundSql boundSql = ms.getBoundSql(parameter);
    
    // 2. 创建缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    
    // 3. 查询(先查缓存,再查数据库)
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

Mybatis 核心配置

全局配置文件详解

简要描述:MyBatis的全局配置文件(通常命名为mybatis-config.xml)包含了影响MyBatis行为的设置和属性信息。

核心概念

  • 全局配置:影响整个MyBatis实例的配置
  • 层次结构:配置元素有严格的顺序要求
  • 环境配置:支持多环境配置(开发、测试、生产)

完整配置文件结构

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 1. 属性配置 -->
    <properties resource="database.properties">
        <property name="username" value="dev_user"/>
        <property name="password" value="dev_password"/>
    </properties>
    
    <!-- 2. 设置配置 -->
    <settings>
        <!-- 开启驼峰命名转换 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 设置执行器类型 -->
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <!-- 设置超时时间 -->
        <setting name="defaultStatementTimeout" value="25"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
        <!-- 日志实现 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
    <!-- 3. 类型别名 -->
    <typeAliases>
        <!-- 单个别名 -->
        <typeAlias alias="User" type="com.example.entity.User"/>
        <!-- 包扫描 -->
        <package name="com.example.entity"/>
    </typeAliases>
    
    <!-- 4. 类型处理器 -->
    <typeHandlers>
        <typeHandler handler="com.example.handler.MyTypeHandler"/>
        <package name="com.example.handler"/>
    </typeHandlers>
    
    <!-- 5. 插件配置 -->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>
    
    <!-- 6. 环境配置 -->
    <environments default="development">
        <!-- 开发环境 -->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        
        <!-- 生产环境 -->
        <environment id="production">
            <transactionManager type="MANAGED"/>
            <dataSource type="JNDI">
                <property name="data_source" value="java:comp/env/jdbc/MyDataSource"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 7. 数据库厂商标识 -->
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql"/>
        <property name="Oracle" value="oracle"/>
        <property name="PostgreSQL" value="postgresql"/>
    </databaseIdProvider>
    
    <!-- 8. 映射器配置 -->
    <mappers>
        <!-- 单个映射文件 -->
        <mapper resource="com/example/mapper/UserMapper.xml"/>
        <!-- 接口映射 -->
        <mapper class="com.example.mapper.UserMapper"/>
        <!-- 包扫描 -->
        <package name="com.example.mapper"/>
    </mappers>
</configuration>

重要设置详解

<settings>
    <!-- 自动映射策略 -->
    <setting name="autoMappingBehavior" value="PARTIAL"/>  <!-- NONE, PARTIAL, FULL -->
    
    <!-- 自动映射未知列行为 -->
    <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>  <!-- NONE, WARNING, FAILING -->
    
    <!-- 缓存配置 -->
    <setting name="cacheEnabled" value="true"/>  <!-- 二级缓存开关 -->
    <setting name="localCacheScope" value="SESSION"/>  <!-- SESSION, STATEMENT -->
    
    <!-- 延迟加载配置 -->
    <setting name="lazyLoadingEnabled" value="false"/>  <!-- 延迟加载开关 -->
    <setting name="aggressiveLazyLoading" value="false"/>  <!-- 积极延迟加载 -->
    
    <!-- 执行器配置 -->
    <setting name="defaultExecutorType" value="SIMPLE"/>  <!-- SIMPLE, REUSE, BATCH -->
    
    <!-- 超时配置 -->
    <setting name="defaultStatementTimeout" value="25"/>  <!-- SQL执行超时时间 -->
    <setting name="defaultFetchSize" value="100"/>  <!-- 驱动结果集获取数量 -->
    
    <!-- 返回值配置 -->
    <setting name="returnInstanceForEmptyRow" value="false"/>  <!-- 空行返回实例 -->
    
    <!-- 日志配置 -->
    <setting name="logImpl" value="SLF4J"/>  <!-- SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING -->
    
    <!-- 命名转换 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>  <!-- 下划线转驼峰 -->
    
    <!-- 多结果集处理 -->
    <setting name="multipleResultSetsEnabled" value="true"/>  <!-- 多结果集支持 -->
    
    <!-- 空值处理 -->
    <setting name="callSettersOnNulls" value="false"/>  <!-- null值调用setter -->
    <setting name="jdbcTypeForNull" value="OTHER"/>  <!-- null值的JDBC类型 -->
</settings>

映射文件配置

简要描述:映射文件是MyBatis的核心,定义了SQL语句与Java方法的映射关系。每个映射文件对应一个Mapper接口。

核心概念

  • 命名空间:映射文件的唯一标识,通常对应Mapper接口的全限定名
  • SQL映射:定义具体的SQL语句和参数映射
  • 结果映射:定义查询结果与Java对象的映射关系

映射文件基本结构

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 1. 缓存配置 -->
    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
    
    <!-- 2. 缓存引用 -->
    <cache-ref namespace="com.example.mapper.CommonMapper"/>
    
    <!-- 3. 结果映射 -->
    <resultMap id="userResultMap" type="User">
        <id property="id" column="user_id"/>
        <result property="name" column="user_name"/>
        <result property="age" column="user_age"/>
        <result property="email" column="user_email"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    </resultMap>
    
    <!-- 4. SQL片段 -->
    <sql id="userColumns">
        user_id, user_name, user_age, user_email, create_time
    </sql>
    
    <!-- 5. 参数映射 -->
    <parameterMap id="userParameterMap" type="User">
        <parameter property="id" jdbcType="BIGINT"/>
        <parameter property="name" jdbcType="VARCHAR"/>
        <parameter property="age" jdbcType="INTEGER"/>
        <parameter property="email" jdbcType="VARCHAR"/>
    </parameterMap>
    
    <!-- 6. 查询语句 -->
    <select id="selectUser" parameterType="long" resultMap="userResultMap">
        SELECT <include refid="userColumns"/>
        FROM user 
        WHERE user_id = #{id}
    </select>
    
    <!-- 7. 插入语句 -->
    <insert id="insertUser" parameterType="User" 
            useGeneratedKeys="true" keyProperty="id" keyColumn="user_id">
        INSERT INTO user (user_name, user_age, user_email, create_time)
        VALUES (#{name}, #{age}, #{email}, #{createTime})
    </insert>
    
    <!-- 8. 更新语句 -->
    <update id="updateUser" parameterType="User">
        UPDATE user 
        SET user_name = #{name},
            user_age = #{age},
            user_email = #{email}
        WHERE user_id = #{id}
    </update>
    
    <!-- 9. 删除语句 -->
    <delete id="deleteUser" parameterType="long">
        DELETE FROM user WHERE user_id = #{id}
    </delete>
    
</mapper>

数据源配置

简要描述:数据源配置是MyBatis连接数据库的核心配置,支持多种数据源类型和连接池配置。

核心概念

  • Environment环境:包含事务管理器和数据源的配置环境
  • DataSource数据源:提供数据库连接的组件
  • TransactionManager事务管理器:管理数据库事务的组件
  • 连接池:管理数据库连接的池化技术

基本数据源配置

<!-- mybatis-config.xml中的数据源配置 -->
<environments default="development">
    <!-- 开发环境 -->
    <environment id="development">
        <!-- 事务管理器:JDBC或MANAGED -->
        <transactionManager type="JDBC"/>
        <!-- 数据源类型:POOLED、UNPOOLED、JNDI -->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8"/>
            <property name="username" value="root"/>
            <property name="password" value="password"/>
            <!-- 连接池配置 -->
            <property name="poolMaximumActiveConnections" value="20"/>  <!-- 最大活跃连接数 -->
            <property name="poolMaximumIdleConnections" value="5"/>   <!-- 最大空闲连接数 -->
            <property name="poolMaximumCheckoutTime" value="20000"/> <!-- 最大检出时间 -->
            <property name="poolTimeToWait" value="20000"/>          <!-- 等待时间 -->
        </dataSource>
    </environment>
    
    <!-- 生产环境 -->
    <environment id="production">
        <transactionManager type="JDBC"/>
        <dataSource type="JNDI">
            <property name="data_source" value="java:comp/env/jdbc/mybatis"/>
        </dataSource>
    </environment>
</environments>

外部配置文件

<!-- 引用外部配置文件 -->
<properties resource="database.properties">
    <!-- 可以在这里定义默认值 -->
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
</properties>

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>
# database.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username=root
password=password

多数据源配置

<!-- 多数据源环境配置 -->
<environments default="master">
    <!-- 主数据库 -->
    <environment id="master">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://master:3306/mybatis_demo"/>
            <property name="username" value="root"/>
            <property name="password" value="password"/>
        </dataSource>
    </environment>
    
    <!-- 从数据库 -->
    <environment id="slave">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://slave:3306/mybatis_demo"/>
            <property name="username" value="readonly"/>
            <property name="password" value="password"/>
        </dataSource>
    </environment>
</environments>

类型处理器

简要描述:类型处理器(TypeHandler)负责Java类型与JDBC类型之间的转换,MyBatis提供了丰富的内置类型处理器,也支持自定义类型处理器。

核心概念

  • 类型转换:Java类型与数据库类型之间的双向转换
  • 内置处理器:MyBatis提供的常用类型处理器
  • 自定义处理器:针对特殊需求开发的类型处理器
  • 泛型处理:支持泛型的类型处理器

内置类型处理器

// MyBatis内置的常用类型处理器
public class TypeHandlerRegistry {
    // 基本类型处理器
    register(Boolean.class, new BooleanTypeHandler());
    register(Integer.class, new IntegerTypeHandler());
    register(Long.class, new LongTypeHandler());
    register(String.class, new StringTypeHandler());
    
    // 日期时间处理器
    register(Date.class, new DateTypeHandler());
    register(Timestamp.class, new DateTypeHandler());
    register(LocalDate.class, new LocalDateTypeHandler());
    register(LocalDateTime.class, new LocalDateTimeTypeHandler());
    
    // 大对象处理器
    register(byte[].class, new ByteArrayTypeHandler());
    register(Blob.class, new BlobTypeHandler());
    register(Clob.class, new ClobTypeHandler());
}

自定义类型处理器

// 枚举类型处理器示例
public enum UserStatus {
    ACTIVE(1, "激活"),
    INACTIVE(0, "未激活"),
    DELETED(-1, "已删除");
    
    private final int code;
    private final String description;
    
    UserStatus(int code, String description) {
        this.code = code;
        this.description = description;
    }
    
    // getter方法
    public int getCode() { return code; }
    public String getDescription() { return description; }
    
    // 根据code获取枚举
    public static UserStatus fromCode(int code) {
        for (UserStatus status : values()) {
            if (status.code == code) {
                return status;
            }
        }
        throw new IllegalArgumentException("Unknown code: " + code);
    }
}

// 自定义枚举类型处理器
@MappedTypes(UserStatus.class)
@MappedJdbcTypes(JdbcType.INTEGER)
public class UserStatusTypeHandler implements TypeHandler<UserStatus> {
    
    @Override
    public void setParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            ps.setNull(i, Types.INTEGER);
        } else {
            ps.setInt(i, parameter.getCode());
        }
    }
    
    @Override
    public UserStatus getResult(ResultSet rs, String columnName) throws SQLException {
        int code = rs.getInt(columnName);
        return rs.wasNull() ? null : UserStatus.fromCode(code);
    }
    
    @Override
    public UserStatus getResult(ResultSet rs, int columnIndex) throws SQLException {
        int code = rs.getInt(columnIndex);
        return rs.wasNull() ? null : UserStatus.fromCode(code);
    }
    
    @Override
    public UserStatus getResult(CallableStatement cs, int columnIndex) throws SQLException {
        int code = cs.getInt(columnIndex);
        return cs.wasNull() ? null : UserStatus.fromCode(code);
    }
}

JSON类型处理器

// JSON类型处理器,用于处理JSON字段
@MappedTypes({Object.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class JsonTypeHandler<T> implements TypeHandler<T> {
    
    private static final ObjectMapper objectMapper = new ObjectMapper();
    private Class<T> type;
    
    public JsonTypeHandler(Class<T> type) {
        this.type = type;
    }
    
    @Override
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        if (parameter == null) {
            ps.setNull(i, Types.VARCHAR);
        } else {
            try {
                ps.setString(i, objectMapper.writeValueAsString(parameter));
            } catch (JsonProcessingException e) {
                throw new SQLException("Error converting object to JSON", e);
            }
        }
    }
    
    @Override
    public T getResult(ResultSet rs, String columnName) throws SQLException {
        String json = rs.getString(columnName);
        return parseJson(json);
    }
    
    @Override
    public T getResult(ResultSet rs, int columnIndex) throws SQLException {
        String json = rs.getString(columnIndex);
        return parseJson(json);
    }
    
    @Override
    public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String json = cs.getString(columnIndex);
        return parseJson(json);
    }
    
    private T parseJson(String json) throws SQLException {
        if (json == null || json.trim().isEmpty()) {
            return null;
        }
        try {
            return objectMapper.readValue(json, type);
        } catch (JsonProcessingException e) {
            throw new SQLException("Error parsing JSON", e);
        }
    }
}

类型处理器注册

<!-- 在mybatis-config.xml中注册类型处理器 -->
<typeHandlers>
    <!-- 单个注册 -->
    <typeHandler handler="com.example.handler.UserStatusTypeHandler"/>
    
    <!-- 包扫描注册 -->
    <package name="com.example.handler"/>
    
    <!-- 指定Java类型和JDBC类型 -->
    <typeHandler javaType="com.example.entity.UserProfile" 
                 jdbcType="VARCHAR" 
                 handler="com.example.handler.JsonTypeHandler"/>
</typeHandlers>

插件配置

简要描述:MyBatis插件机制基于拦截器模式,允许在SQL执行的关键点进行拦截和自定义处理,常用于分页、性能监控、数据权限等场景。

核心概念

  • 拦截器:实现Interceptor接口的插件组件
  • 拦截点:可以被拦截的四个核心对象
  • 方法签名:指定要拦截的具体方法
  • 插件链:多个插件按顺序执行的链式结构

可拦截的四大对象

// 1. Executor - 执行器,负责SQL的执行
@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})

// 2. StatementHandler - 语句处理器,负责SQL语句的预处理
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
    @Signature(type = StatementHandler.class, method = "parameterize", args = {Statement.class})
})

// 3. ParameterHandler - 参数处理器,负责参数的设置
@Intercepts({
    @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
})

// 4. ResultSetHandler - 结果集处理器,负责结果集的处理
@Intercepts({
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})

SQL性能监控插件

// SQL执行时间监控插件
@Intercepts({
    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
    @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class SqlPerformanceInterceptor implements Interceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(SqlPerformanceInterceptor.class);
    private long slowSqlThreshold = 1000; // 慢SQL阈值,单位毫秒
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            // 执行原方法
            Object result = invocation.proceed();
            
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            // 获取SQL语句
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            
            // 记录SQL执行信息
            if (executionTime > slowSqlThreshold) {
                logger.warn("Slow SQL detected: {} ms, SQL: {}", executionTime, sql);
            } else {
                logger.info("SQL executed: {} ms, SQL: {}", executionTime, sql);
            }
            
            return result;
        } catch (Exception e) {
            logger.error("SQL execution failed", e);
            throw e;
        }
    }
    
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    
    @Override
    public void setProperties(Properties properties) {
        String threshold = properties.getProperty("slowSqlThreshold");
        if (threshold != null) {
            this.slowSqlThreshold = Long.parseLong(threshold);
        }
    }
}

分页插件

// 简单的分页插件实现
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class PageInterceptor implements Interceptor {
    
    private static final ThreadLocal<Page> PAGE_THREAD_LOCAL = new ThreadLocal<>();
    
    public static void startPage(int pageNum, int pageSize) {
        PAGE_THREAD_LOCAL.set(new Page(pageNum, pageSize));
    }
    
    public static void clearPage() {
        PAGE_THREAD_LOCAL.remove();
    }
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Page page = PAGE_THREAD_LOCAL.get();
        if (page == null) {
            return invocation.proceed();
        }
        
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String originalSql = boundSql.getSql();
        
        // 构建分页SQL
        String pageSql = buildPageSql(originalSql, page);
        
        // 通过反射修改SQL
        Field sqlField = BoundSql.class.getDeclaredField("sql");
        sqlField.setAccessible(true);
        sqlField.set(boundSql, pageSql);
        
        return invocation.proceed();
    }
    
    private String buildPageSql(String originalSql, Page page) {
        int offset = (page.getPageNum() - 1) * page.getPageSize();
        return originalSql + " LIMIT " + offset + ", " + page.getPageSize();
    }
    
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    
    @Override
    public void setProperties(Properties properties) {
        // 可以从配置中读取参数
    }
    
    // 分页参数类
    public static class Page {
        private int pageNum;
        private int pageSize;
        
        public Page(int pageNum, int pageSize) {
            this.pageNum = pageNum;
            this.pageSize = pageSize;
        }
        
        // getter方法
        public int getPageNum() { return pageNum; }
        public int getPageSize() { return pageSize; }
    }
}

插件注册配置

<!-- 在mybatis-config.xml中注册插件 -->
<plugins>
    <!-- SQL性能监控插件 -->
    <plugin interceptor="com.example.plugin.SqlPerformanceInterceptor">
        <property name="slowSqlThreshold" value="2000"/>
    </plugin>
    
    <!-- 分页插件 -->
    <plugin interceptor="com.example.plugin.PageInterceptor"/>
    
    <!-- 数据权限插件 -->
    <plugin interceptor="com.example.plugin.DataPermissionInterceptor">
        <property name="enable" value="true"/>
    </plugin>
</plugins>

映射器与SQL映射

简要描述:映射器与SQL映射是MyBatis的核心功能,通过Mapper接口和映射文件将Java方法与SQL语句进行绑定,支持XML配置和注解两种方式。

核心概念

  • Mapper接口:定义数据访问方法的Java接口
  • 映射文件:包含SQL语句和映射规则的XML文件
  • 注解映射:通过注解直接在接口方法上定义SQL
  • 参数映射:Java参数与SQL参数之间的映射关系

Mapper接口开发

简要描述:Mapper接口是MyBatis中定义数据访问方法的核心组件,通过接口方法与SQL语句进行映射,支持各种参数类型和返回类型。

核心概念

  • 接口代理:MyBatis通过动态代理生成接口实现
  • 方法映射:接口方法与SQL语句的一对一映射
  • 参数绑定:方法参数与SQL参数的绑定规则
  • 返回类型:支持多种返回类型的自动映射

基本Mapper接口

// 用户数据访问接口
public interface UserMapper {
    
    // 根据ID查询用户
    User selectById(Long id);
    
    // 查询所有用户
    List<User> selectAll();
    
    // 根据条件查询用户
    List<User> selectByCondition(UserQuery query);
    
    // 插入用户
    int insert(User user);
    
    // 更新用户
    int update(User user);
    
    // 删除用户
    int deleteById(Long id);
    
    // 批量插入
    int batchInsert(List<User> users);
    
    // 统计用户数量
    int count();
    
    // 检查用户是否存在
    boolean exists(Long id);
}

参数处理方式

public interface UserMapper {
    
    // 1. 单个参数(基本类型)
    User selectById(Long id);
    User selectByName(String name);
    
    // 2. 多个参数(使用@Param注解)
    List<User> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
    
    // 3. 对象参数
    List<User> selectByCondition(UserQuery query);
    
    // 4. Map参数
    List<User> selectByMap(Map<String, Object> params);
    
    // 5. 集合参数
    List<User> selectByIds(@Param("ids") List<Long> ids);
    
    // 6. 混合参数
    List<User> selectByConditionAndIds(@Param("query") UserQuery query, @Param("ids") List<Long> ids);
}

返回类型处理

public interface UserMapper {
    
    // 1. 单个对象
    User selectById(Long id);
    
    // 2. 集合
    List<User> selectAll();
    
    // 3. 基本类型
    int count();
    long countByStatus(Integer status);
    String getNameById(Long id);
    
    // 4. Map类型
    Map<String, Object> selectAsMap(Long id);
    
    // 5. Map集合(以某个字段为key)
    @MapKey("id")
    Map<Long, User> selectAllAsMap();
    
    // 6. Optional类型(MyBatis 3.5+)
    Optional<User> selectOptionalById(Long id);
    
    // 7. 游标(大数据量处理)
    Cursor<User> selectCursor();
    
    // 8. 自定义结果处理器
    void selectWithHandler(ResultHandler<User> handler);
}

Mapper接口注册

<!-- 在mybatis-config.xml中注册Mapper -->
<mappers>
    <!-- 1. 单个注册 -->
    <mapper resource="com/example/mapper/UserMapper.xml"/>
    <mapper class="com.example.mapper.UserMapper"/>
    <mapper url="file:///var/mappers/UserMapper.xml"/>
    
    <!-- 2. 包扫描注册 -->
    <package name="com.example.mapper"/>
</mappers>
// 在Java配置中注册
Configuration configuration = new Configuration();
configuration.addMapper(UserMapper.class);

XML映射文件

简要描述:XML映射文件是MyBatis中定义SQL语句和映射规则的核心配置文件,提供了丰富的标签和功能来处理复杂的SQL映射需求。

核心概念

  • 命名空间:映射文件的唯一标识,通常对应Mapper接口
  • SQL语句标签:select、insert、update、delete等
  • 参数映射:parameterType和参数占位符
  • 结果映射:resultType和resultMap

基本映射文件结构

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace必须与Mapper接口全限定名一致 -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 结果映射定义 -->
    <resultMap id="userResultMap" type="User">
        <id property="id" column="id"/>
        <result property="name" column="user_name"/>
        <result property="age" column="age"/>
        <result property="email" column="email"/>
        <result property="createTime" column="create_time"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>
    
    <!-- SQL片段定义 -->
    <sql id="userColumns">
        id, user_name, age, email, create_time, update_time
    </sql>
    
    <!-- 查询语句 -->
    <select id="selectById" parameterType="long" resultMap="userResultMap">
        SELECT <include refid="userColumns"/>
        FROM user 
        WHERE id = #{id}
    </select>
    
    <!-- 插入语句 -->
    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (user_name, age, email, create_time)
        VALUES (#{name}, #{age}, #{email}, #{createTime})
    </insert>
    
    <!-- 更新语句 -->
    <update id="update" parameterType="User">
        UPDATE user 
        SET user_name = #{name}, 
            age = #{age}, 
            email = #{email}, 
            update_time = #{updateTime}
        WHERE id = #{id}
    </update>
    
    <!-- 删除语句 -->
    <delete id="deleteById" parameterType="long">
        DELETE FROM user WHERE id = #{id}
    </delete>
    
</mapper>

复杂查询映射

<!-- 条件查询 -->
<select id="selectByCondition" parameterType="UserQuery" resultMap="userResultMap">
    SELECT <include refid="userColumns"/>
    FROM user
    <where>
        <if test="name != null and name != ''">
            AND user_name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age <= #{maxAge}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

<!-- 批量查询 -->
<select id="selectByIds" resultMap="userResultMap">
    SELECT <include refid="userColumns"/>
    FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

<!-- 分页查询 -->
<select id="selectPage" parameterType="map" resultMap="userResultMap">
    SELECT <include refid="userColumns"/>
    FROM user
    <where>
        <if test="query.name != null and query.name != ''">
            AND user_name LIKE CONCAT('%', #{query.name}, '%')
        </if>
    </where>
    ORDER BY create_time DESC
    LIMIT #{offset}, #{limit}
</select>

关联查询映射

<!-- 一对一关联查询 -->
<resultMap id="userWithProfileResultMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="user_name"/>
    <result property="age" column="age"/>
    <result property="email" column="email"/>
    <!-- 一对一关联 -->
    <association property="profile" javaType="UserProfile">
        <id property="id" column="profile_id"/>
        <result property="avatar" column="avatar"/>
        <result property="bio" column="bio"/>
        <result property="website" column="website"/>
    </association>
</resultMap>

<select id="selectWithProfile" parameterType="long" resultMap="userWithProfileResultMap">
    SELECT u.id, u.user_name, u.age, u.email,
           p.id as profile_id, p.avatar, p.bio, p.website
    FROM user u
    LEFT JOIN user_profile p ON u.id = p.user_id
    WHERE u.id = #{id}
</select>

<!-- 一对多关联查询 -->
<resultMap id="userWithOrdersResultMap" type="User">
    <id property="id" column="id"/>
    <result property="name" column="user_name"/>
    <result property="age" column="age"/>
    <result property="email" column="email"/>
    <!-- 一对多关联 -->
    <collection property="orders" ofType="Order">
        <id property="id" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="amount" column="amount"/>
        <result property="status" column="order_status"/>
        <result property="createTime" column="order_create_time"/>
    </collection>
</resultMap>

<select id="selectWithOrders" parameterType="long" resultMap="userWithOrdersResultMap">
    SELECT u.id, u.user_name, u.age, u.email,
           o.id as order_id, o.order_no, o.amount, o.status as order_status, o.create_time as order_create_time
    FROM user u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

注解映射

简要描述:注解映射是MyBatis提供的另一种SQL映射方式,通过在Mapper接口方法上使用注解直接定义SQL语句,适用于简单的SQL操作。

核心概念

  • SQL注解:@Select、@Insert、@Update、@Delete
  • 参数注解:@Param、@Options等
  • 结果注解:@Results、@Result等
  • 动态SQL:@SelectProvider、@InsertProvider等

基本注解使用

public interface UserMapper {
    
    // 查询注解
    @Select("SELECT id, user_name as name, age, email, create_time FROM user WHERE id = #{id}")
    User selectById(Long id);
    
    // 插入注解
    @Insert("INSERT INTO user(user_name, age, email, create_time) VALUES(#{name}, #{age}, #{email}, #{createTime})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
    
    // 更新注解
    @Update("UPDATE user SET user_name = #{name}, age = #{age}, email = #{email} WHERE id = #{id}")
    int update(User user);
    
    // 删除注解
    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteById(Long id);
    
    // 多参数查询
    @Select("SELECT id, user_name as name, age, email FROM user WHERE user_name = #{name} AND age = #{age}")
    List<User> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
    
    // 统计查询
    @Select("SELECT COUNT(*) FROM user WHERE status = #{status}")
    int countByStatus(@Param("status") Integer status);
}

复杂结果映射注解

public interface UserMapper {
    
    // 使用@Results注解定义结果映射
    @Select("SELECT id, user_name, age, email, create_time, update_time FROM user WHERE id = #{id}")
    @Results(id = "userResultMap", value = {
        @Result(property = "id", column = "id", id = true),
        @Result(property = "name", column = "user_name"),
        @Result(property = "age", column = "age"),
        @Result(property = "email", column = "email"),
        @Result(property = "createTime", column = "create_time"),
        @Result(property = "updateTime", column = "update_time")
    })
    User selectByIdWithMapping(Long id);
    
    // 复用结果映射
    @Select("SELECT id, user_name, age, email, create_time, update_time FROM user")
    @ResultMap("userResultMap")
    List<User> selectAll();
    
    // 一对一关联映射
    @Select("SELECT u.id, u.user_name, u.age, u.email, p.avatar, p.bio " +
            "FROM user u LEFT JOIN user_profile p ON u.id = p.user_id WHERE u.id = #{id}")
    @Results({
        @Result(property = "id", column = "id", id = true),
        @Result(property = "name", column = "user_name"),
        @Result(property = "age", column = "age"),
        @Result(property = "email", column = "email"),
        @Result(property = "profile", column = "id", 
                one = @One(select = "selectProfileByUserId"))
    })
    User selectWithProfile(Long id);
    
    @Select("SELECT id, avatar, bio, website FROM user_profile WHERE user_id = #{userId}")
    UserProfile selectProfileByUserId(Long userId);
    
    // 一对多关联映射
    @Select("SELECT id, user_name, age, email FROM user WHERE id = #{id}")
    @Results({
        @Result(property = "id", column = "id", id = true),
        @Result(property = "name", column = "user_name"),
        @Result(property = "age", column = "age"),
        @Result(property = "email", column = "email"),
        @Result(property = "orders", column = "id",
                many = @Many(select = "selectOrdersByUserId"))
    })
    User selectWithOrders(Long id);
    
    @Select("SELECT id, order_no, amount, status, create_time FROM orders WHERE user_id = #{userId}")
    List<Order> selectOrdersByUserId(Long userId);
}

动态SQL注解

public interface UserMapper {
    
    // 使用Provider类提供动态SQL
    @SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
    List<User> selectByCondition(UserQuery query);
    
    @InsertProvider(type = UserSqlProvider.class, method = "batchInsert")
    int batchInsert(@Param("users") List<User> users);
    
    @UpdateProvider(type = UserSqlProvider.class, method = "updateSelective")
    int updateSelective(User user);
}

// SQL提供者类
public class UserSqlProvider {
    
    public String selectByCondition(UserQuery query) {
        SQL sql = new SQL();
        sql.SELECT("id, user_name as name, age, email, create_time");
        sql.FROM("user");
        
        if (query.getName() != null && !query.getName().isEmpty()) {
            sql.WHERE("user_name LIKE CONCAT('%', #{name}, '%')");
        }
        if (query.getMinAge() != null) {
            sql.WHERE("age >= #{minAge}");
        }
        if (query.getMaxAge() != null) {
            sql.WHERE("age <= #{maxAge}");
        }
        if (query.getEmail() != null && !query.getEmail().isEmpty()) {
            sql.WHERE("email = #{email}");
        }
        
        sql.ORDER_BY("create_time DESC");
        return sql.toString();
    }
    
    public String batchInsert(@Param("users") List<User> users) {
        StringBuilder sql = new StringBuilder();
        sql.append("INSERT INTO user (user_name, age, email, create_time) VALUES ");
        
        for (int i = 0; i < users.size(); i++) {
            if (i > 0) {
                sql.append(", ");
            }
            sql.append("(#{users[").append(i).append("].name}, ")
               .append("#{users[").append(i).append("].age}, ")
               .append("#{users[").append(i).append("].email}, ")
               .append("#{users[").append(i).append("].createTime})");
        }
        
        return sql.toString();
    }
    
    public String updateSelective(User user) {
        SQL sql = new SQL();
        sql.UPDATE("user");
        
        if (user.getName() != null) {
            sql.SET("user_name = #{name}");
        }
        if (user.getAge() != null) {
            sql.SET("age = #{age}");
        }
        if (user.getEmail() != null) {
            sql.SET("email = #{email}");
        }
        
        sql.SET("update_time = NOW()");
        sql.WHERE("id = #{id}");
        
        return sql.toString();
    }
}

参数映射详解

简要描述:参数映射是MyBatis中处理Java方法参数与SQL参数绑定的核心机制,支持多种参数类型和绑定方式。

核心概念

  • 参数绑定:Java参数与SQL占位符的绑定关系
  • 参数类型:基本类型、对象类型、集合类型等
  • 参数注解:@Param注解指定参数名称
  • 参数处理:MyBatis内部的参数处理机制

单个参数处理

public interface UserMapper {
    
    // 基本类型参数
    User selectById(Long id);                    // 参数名可以是任意值
    User selectByName(String name);              // 在SQL中使用#{任意名称}
    List<User> selectByAge(Integer age);
    
    // 包装类型参数
    List<User> selectByStatus(Integer status);   // 支持null值检查
    User selectByEmail(String email);
}
<!-- 单个参数的XML映射 -->
<select id="selectById" parameterType="long" resultType="User">
    SELECT * FROM user WHERE id = #{id}        <!-- 参数名可以任意 -->
</select>

<select id="selectByName" parameterType="string" resultType="User">
    SELECT * FROM user WHERE user_name = #{name}  <!-- 或者#{value}、#{_parameter} -->
</select>

<!-- 参数为null的处理 -->
<select id="selectByStatus" parameterType="integer" resultType="User">
    SELECT * FROM user 
    <where>
        <if test="_parameter != null">
            status = #{status}
        </if>
    </where>
</select>

多个参数处理

public interface UserMapper {
    
    // 使用@Param注解(推荐方式)
    List<User> selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);
    
    // 不使用@Param注解(不推荐)
    List<User> selectByNameAndAgeOld(String name, Integer age);
    
    // 混合参数类型
    List<User> selectByCondition(@Param("name") String name, 
                                @Param("minAge") Integer minAge,
                                @Param("maxAge") Integer maxAge,
                                @Param("status") Integer status);
}
<!-- 使用@Param注解的映射 -->
<select id="selectByNameAndAge" resultType="User">
    SELECT * FROM user 
    WHERE user_name = #{name} AND age = #{age}
</select>

<!-- 不使用@Param注解的映射(使用arg0, arg1或param1, param2) -->
<select id="selectByNameAndAgeOld" resultType="User">
    SELECT * FROM user 
    WHERE user_name = #{arg0} AND age = #{arg1}
    <!-- 或者使用 #{param1} 和 #{param2} -->
</select>

<!-- 复杂条件查询 -->
<select id="selectByCondition" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            AND user_name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age <= #{maxAge}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</select>

对象参数处理

// 查询条件对象
public class UserQuery {
    private String name;
    private Integer minAge;
    private Integer maxAge;
    private String email;
    private Integer status;
    private Date startDate;
    private Date endDate;
    
    // getter和setter方法
}

public interface UserMapper {
    
    // 对象参数
    List<User> selectByQuery(UserQuery query);
    
    // 对象参数插入
    int insert(User user);
    
    // 对象参数更新
    int update(User user);
    
    // 嵌套对象参数
    List<User> selectByUserAndProfile(@Param("user") User user, @Param("profile") UserProfile profile);
}
<!-- 对象参数映射 -->
<select id="selectByQuery" parameterType="UserQuery" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            AND user_name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="minAge != null">
            AND age >= #{minAge}
        </if>
        <if test="maxAge != null">
            AND age <= #{maxAge}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="startDate != null">
            AND create_time >= #{startDate}
        </if>
        <if test="endDate != null">
            AND create_time <= #{endDate}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

<!-- 对象参数插入 -->
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user (user_name, age, email, status, create_time)
    VALUES (#{name}, #{age}, #{email}, #{status}, #{createTime})
</insert>

<!-- 嵌套对象参数 -->
<select id="selectByUserAndProfile" resultType="User">
    SELECT * FROM user u
    LEFT JOIN user_profile p ON u.id = p.user_id
    <where>
        <if test="user.name != null and user.name != ''">
            AND u.user_name = #{user.name}
        </if>
        <if test="user.age != null">
            AND u.age = #{user.age}
        </if>
        <if test="profile.avatar != null and profile.avatar != ''">
            AND p.avatar = #{profile.avatar}
        </if>
    </where>
</select>

Map参数处理

public interface UserMapper {
    
    // Map参数
    List<User> selectByMap(Map<String, Object> params);
    
    // 分页查询Map参数
    List<User> selectPage(Map<String, Object> params);
    
    // 复杂查询Map参数
    List<User> selectByComplexMap(Map<String, Object> conditions);
}
<!-- Map参数映射 -->
<select id="selectByMap" parameterType="map" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            AND user_name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</select>

<!-- 分页查询 -->
<select id="selectPage" parameterType="map" resultType="User">
    SELECT * FROM user
    <where>
        <if test="query != null">
            <if test="query.name != null and query.name != ''">
                AND user_name LIKE CONCAT('%', #{query.name}, '%')
            </if>
            <if test="query.status != null">
                AND status = #{query.status}
            </if>
        </if>
    </where>
    ORDER BY create_time DESC
    LIMIT #{offset}, #{limit}
</select>

集合参数处理

public interface UserMapper {
    
    // List参数
    List<User> selectByIds(@Param("ids") List<Long> ids);
    
    // Array参数
    List<User> selectByIdArray(@Param("ids") Long[] ids);
    
    // 批量插入
    int batchInsert(@Param("users") List<User> users);
    
    // 批量更新
    int batchUpdate(@Param("users") List<User> users);
    
    // Set参数
    List<User> selectByStatusSet(@Param("statusSet") Set<Integer> statusSet);
}
<!-- List参数映射 -->
<select id="selectByIds" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

<!-- Array参数映射 -->
<select id="selectByIdArray" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsert">
    INSERT INTO user (user_name, age, email, create_time)
    VALUES
    <foreach collection="users" item="user" separator=",">
        (#{user.name}, #{user.age}, #{user.email}, #{user.createTime})
    </foreach>
</insert>

<!-- 批量更新(MySQL特有语法) -->
<update id="batchUpdate">
    <foreach collection="users" item="user" separator=";">
        UPDATE user 
        SET user_name = #{user.name}, age = #{user.age}, email = #{user.email}
        WHERE id = #{user.id}
    </foreach>
</update>

<!-- Set参数映射 -->
<select id="selectByStatusSet" resultType="User">
    SELECT * FROM user
    <where>
        <if test="statusSet != null and statusSet.size() > 0">
            status IN
            <foreach collection="statusSet" item="status" open="(" close=")" separator=",">
                #{status}
            </foreach>
        </if>
    </where>
</select>

参数类型转换

public interface UserMapper {
    
    // 日期参数处理
    List<User> selectByDateRange(@Param("startDate") Date startDate, 
                                @Param("endDate") Date endDate);
    
    // 枚举参数处理
    List<User> selectByUserType(@Param("userType") UserType userType);
    
    // JSON参数处理
    List<User> selectByJsonCondition(@Param("jsonData") String jsonData);
    
    // 自定义类型参数
    List<User> selectByCustomType(@Param("customParam") CustomType customParam);
}
<!-- 日期参数映射 -->
<select id="selectByDateRange" resultType="User">
    SELECT * FROM user
    <where>
        <if test="startDate != null">
            AND create_time >= #{startDate, jdbcType=TIMESTAMP}
        </if>
        <if test="endDate != null">
            AND create_time <= #{endDate, jdbcType=TIMESTAMP}
        </if>
    </where>
</select>

<!-- 枚举参数映射 -->
<select id="selectByUserType" resultType="User">
    SELECT * FROM user
    WHERE user_type = #{userType, jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
</select>

<!-- JSON参数映射 -->
<select id="selectByJsonCondition" resultType="User">
    SELECT * FROM user
    WHERE JSON_EXTRACT(extra_data, '$.condition') = #{jsonData, jdbcType=VARCHAR}
</select>

参数验证和处理

public interface UserMapper {
    
    // 参数验证示例
    List<User> selectWithValidation(@Param("name") String name, 
                                   @Param("age") Integer age);
}
<!-- 参数验证和处理 -->
<select id="selectWithValidation" resultType="User">
    SELECT * FROM user
    <where>
        <!-- 字符串参数验证 -->
        <if test="name != null and name != '' and name.length() > 0">
            AND user_name = #{name}
        </if>
        
        <!-- 数值参数验证 -->
        <if test="age != null and age > 0 and age < 150">
            AND age = #{age}
        </if>
        
        <!-- 防止SQL注入的参数处理 -->
        <if test="name != null">
            AND user_name = #{name, jdbcType=VARCHAR}
        </if>
    </where>
</select>

特殊参数处理

public interface UserMapper {
    
    // RowBounds分页参数(MyBatis内置)
    List<User> selectWithRowBounds(RowBounds rowBounds);
    
    // ResultHandler结果处理器
    void selectWithHandler(ResultHandler<User> handler);
    
    // 混合特殊参数
    void selectWithMixedParams(@Param("name") String name, 
                              RowBounds rowBounds, 
                              ResultHandler<User> handler);
}
<!-- RowBounds不需要在XML中声明,MyBatis自动处理 -->
<select id="selectWithRowBounds" resultType="User">
    SELECT * FROM user ORDER BY create_time DESC
</select>

<!-- ResultHandler也不需要在XML中声明 -->
<select id="selectWithHandler" resultType="User">
    SELECT * FROM user ORDER BY create_time DESC
</select>

<!-- 混合参数处理 -->
<select id="selectWithMixedParams" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            user_name LIKE CONCAT('%', #{name}, '%')
        </if>
    </where>
    ORDER BY create_time DESC
</select>

结果映射详解

简要描述:结果映射是MyBatis将查询结果转换为Java对象的核心机制,支持简单映射和复杂的关联映射。

核心概念

  • 自动映射:根据列名和属性名自动映射
  • 手动映射:通过resultMap精确控制映射关系
  • 关联映射:处理对象间的关联关系
  • 延迟加载:按需加载关联对象

resultType

简要描述:resultType是最简单的结果映射方式,适用于简单的数据类型和JavaBean,MyBatis会自动进行映射。

核心概念

  • 自动映射:根据列名和属性名进行自动映射
  • 类型转换:自动进行基本的类型转换
  • 命名规则:支持下划线转驼峰命名

基本使用

<!-- 基本数据类型 -->
<select id="countUsers" resultType="int">
    SELECT COUNT(*) FROM user
</select>

<select id="getUserNames" resultType="string">
    SELECT name FROM user WHERE status = 1
</select>

<!-- JavaBean映射 -->
<select id="selectUser" parameterType="long" resultType="User">
    SELECT id, name, age, email, create_time
    FROM user 
    WHERE id = #{id}
</select>

<!-- Map映射 -->
<select id="selectUserAsMap" parameterType="long" resultType="map">
    SELECT id, name, age, email
    FROM user 
    WHERE id = #{id}
</select>

<!-- 集合映射 -->
<select id="selectAllUsers" resultType="User">
    SELECT id, name, age, email, create_time
    FROM user 
    WHERE status = 1
    ORDER BY create_time DESC
</select>

类型别名配置

<!-- 在mybatis-config.xml中配置别名 -->
<typeAliases>
    <typeAlias alias="User" type="com.example.entity.User"/>
    <typeAlias alias="UserVO" type="com.example.vo.UserVO"/>
    <!-- 包扫描方式 -->
    <package name="com.example.entity"/>
</typeAliases>

自动映射配置

<settings>
    <!-- 开启驼峰命名转换 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!-- 自动映射策略:NONE, PARTIAL, FULL -->
    <setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>

resultMap

简要描述:resultMap提供了更精确的结果映射控制,可以处理复杂的映射关系、类型转换和关联映射。

核心概念

  • 精确映射:明确指定每个字段的映射关系
  • 类型处理:支持自定义类型处理器
  • 继承机制:支持resultMap继承
  • 关联映射:支持一对一、一对多关联

基本结构

<resultMap id="userResultMap" type="User">
    <!-- 主键映射 -->
    <id property="id" column="user_id" jdbcType="BIGINT"/>
    
    <!-- 普通字段映射 -->
    <result property="name" column="user_name" jdbcType="VARCHAR"/>
    <result property="age" column="user_age" jdbcType="INTEGER"/>
    <result property="email" column="user_email" jdbcType="VARCHAR"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    <result property="status" column="status" jdbcType="INTEGER" typeHandler="com.example.handler.StatusTypeHandler"/>
</resultMap>

<!-- 使用resultMap -->
<select id="selectUser" parameterType="long" resultMap="userResultMap">
    SELECT user_id, user_name, user_age, user_email, create_time, status
    FROM user 
    WHERE user_id = #{id}
</select>

复杂映射示例

<!-- 用户详细信息映射 -->
<resultMap id="userDetailResultMap" type="UserDetail">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="age" column="user_age"/>
    <result property="email" column="user_email"/>
    
    <!-- 枚举类型映射 -->
    <result property="gender" column="gender" 
            typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
    
    <!-- 日期类型映射 -->
    <result property="birthday" column="birthday" 
            jdbcType="DATE" javaType="java.time.LocalDate"/>
    
    <!-- JSON字段映射 -->
    <result property="preferences" column="preferences" 
            typeHandler="com.example.handler.JsonTypeHandler"/>
    
    <!-- 计算字段 -->
    <result property="fullName" column="full_name"/>
</resultMap>

<select id="selectUserDetail" parameterType="long" resultMap="userDetailResultMap">
    SELECT 
        user_id,
        user_name,
        user_age,
        user_email,
        gender,
        birthday,
        preferences,
        CONCAT(first_name, ' ', last_name) AS full_name
    FROM user 
    WHERE user_id = #{id}
</select>

resultMap继承

<!-- 基础映射 -->
<resultMap id="baseUserMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
</resultMap>

<!-- 继承基础映射 -->
<resultMap id="extendedUserMap" type="User" extends="baseUserMap">
    <result property="age" column="user_age"/>
    <result property="createTime" column="create_time"/>
    <result property="updateTime" column="update_time"/>
</resultMap>

关联映射

简要描述:关联映射用于处理对象间的关联关系,包括一对一(association)和一对多(collection)关联。

一对一关联(association)

核心概念

  • 嵌套查询:通过额外的查询获取关联对象
  • 嵌套结果:在一个查询中获取所有数据
  • 延迟加载:按需加载关联对象

嵌套查询方式

<!-- 用户和用户详情的一对一关联 -->
<resultMap id="userWithProfileMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
    
    <!-- 一对一关联:嵌套查询 -->
    <association property="profile" column="user_id" 
                 javaType="UserProfile" 
                 select="selectUserProfile"/>
</resultMap>

<!-- 主查询 -->
<select id="selectUserWithProfile" parameterType="long" resultMap="userWithProfileMap">
    SELECT user_id, user_name, user_email
    FROM user 
    WHERE user_id = #{id}
</select>

<!-- 关联查询 -->
<select id="selectUserProfile" parameterType="long" resultType="UserProfile">
    SELECT profile_id, user_id, avatar, bio, phone
    FROM user_profile 
    WHERE user_id = #{userId}
</select>

嵌套结果方式

<!-- 嵌套结果映射 -->
<resultMap id="userWithProfileNestedMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
    
    <!-- 一对一关联:嵌套结果 -->
    <association property="profile" javaType="UserProfile">
        <id property="profileId" column="profile_id"/>
        <result property="avatar" column="avatar"/>
        <result property="bio" column="bio"/>
        <result property="phone" column="phone"/>
    </association>
</resultMap>

<!-- 联表查询 -->
<select id="selectUserWithProfileNested" parameterType="long" resultMap="userWithProfileNestedMap">
    SELECT 
        u.user_id,
        u.user_name,
        u.user_email,
        p.profile_id,
        p.avatar,
        p.bio,
        p.phone
    FROM user u
    LEFT JOIN user_profile p ON u.user_id = p.user_id
    WHERE u.user_id = #{id}
</select>
一对多关联(collection)

核心概念

  • 集合映射:将多个记录映射为集合
  • ofType属性:指定集合元素的类型
  • 分组处理:处理重复的主记录

嵌套查询方式

<!-- 用户和订单的一对多关联 -->
<resultMap id="userWithOrdersMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
    
    <!-- 一对多关联:嵌套查询 -->
    <collection property="orders" column="user_id" 
                ofType="Order" 
                select="selectOrdersByUserId"/>
</resultMap>

<!-- 主查询 -->
<select id="selectUserWithOrders" parameterType="long" resultMap="userWithOrdersMap">
    SELECT user_id, user_name, user_email
    FROM user 
    WHERE user_id = #{id}
</select>

<!-- 关联查询 -->
<select id="selectOrdersByUserId" parameterType="long" resultType="Order">
    SELECT order_id, user_id, order_no, total_amount, create_time
    FROM orders 
    WHERE user_id = #{userId}
    ORDER BY create_time DESC
</select>

嵌套结果方式

<!-- 嵌套结果映射 -->
<resultMap id="userWithOrdersNestedMap" type="User">
    <id property="id" column="user_id"/>
    <result property="name" column="user_name"/>
    <result property="email" column="user_email"/>
    
    <!-- 一对多关联:嵌套结果 -->
    <collection property="orders" ofType="Order">
        <id property="orderId" column="order_id"/>
        <result property="orderNo" column="order_no"/>
        <result property="totalAmount" column="total_amount"/>
        <result property="createTime" column="order_create_time"/>
    </collection>
</resultMap>

<!-- 联表查询 -->
<select id="selectUserWithOrdersNested" parameterType="long" resultMap="userWithOrdersNestedMap">
    SELECT 
        u.user_id,
        u.user_name,
        u.user_email,
        o.order_id,
        o.order_no,
        o.total_amount,
        o.create_time AS order_create_time
    FROM user u
    LEFT JOIN orders o ON u.user_id = o.user_id
    WHERE u.user_id = #{id}
    ORDER BY o.create_time DESC
</select>

集合映射

简要描述:集合映射专门处理返回集合类型的查询结果,包括List、Set、Map等集合类型。

List集合映射

<!-- 返回用户列表 -->
<select id="selectUserList" resultType="User">
    SELECT user_id AS id, user_name AS name, user_email AS email
    FROM user 
    WHERE status = 1
    ORDER BY create_time DESC
</select>

<!-- 返回Map列表 -->
<select id="selectUserMapList" resultType="map">
    SELECT user_id, user_name, user_email, create_time
    FROM user 
    WHERE status = 1
</select>

Map集合映射

<!-- 返回以ID为key的Map -->
<select id="selectUserMap" resultType="User">
    SELECT user_id AS id, user_name AS name, user_email AS email
    FROM user 
    WHERE status = 1
</select>

<!-- 在Mapper接口中使用@MapKey注解 -->
@MapKey("id")
Map<Long, User> selectUserMap();

复杂集合映射

<!-- 分组统计映射 -->
<resultMap id="userStatMap" type="map">
    <result property="department" column="department"/>
    <result property="count" column="user_count"/>
    <result property="avgAge" column="avg_age"/>
</resultMap>

<select id="selectUserStatByDepartment" resultMap="userStatMap">
    SELECT 
        department,
        COUNT(*) AS user_count,
        AVG(age) AS avg_age
    FROM user 
    WHERE status = 1
    GROUP BY department
    ORDER BY user_count DESC
</select>

延迟加载配置

<!-- 全局配置 -->
<settings>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 关闭积极延迟加载 -->
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 延迟加载触发方法 -->
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

<!-- 单独配置延迟加载,局部的懒加载策略优先级要高于全局的懒加载策略 -->
<association property="profile" column="user_id" 
             javaType="UserProfile" 
             select="selectUserProfile"
             fetchType="lazy"/>  <!-- lazy或eager -->

动态SQL深入解析

简要描述:动态SQL是MyBatis的强大特性之一,它允许根据不同的条件动态构建SQL语句,避免了大量的字符串拼接工作。

核心概念

  • 条件判断:根据参数值决定SQL片段是否包含
  • 循环处理:处理集合类型的参数
  • SQL片段复用:提高代码复用性
  • OGNL表达式:使用OGNL表达式进行条件判断

if条件判断

简要描述:if标签是最常用的动态SQL标签,用于条件判断,只有当test条件为true时,才会包含标签内的SQL片段。

基本语法

<if test="条件表达式">
    SQL片段
</if>

实际应用案例

<!-- 动态查询用户 -->
<select id="selectUsersByCondition" parameterType="User" resultType="User">
    SELECT id, name, age, email, status
    FROM user
    WHERE 1=1
    <if test="name != null and name != ''">
        AND name LIKE CONCAT('%', #{name}, '%')
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
    <if test="email != null and email != ''">
        AND email = #{email}
    </if>
    <if test="status != null">
        AND status = #{status}
    </if>
</select>

<!-- 动态更新用户信息 -->
<update id="updateUserSelective" parameterType="User">
    UPDATE user
    SET update_time = NOW()
    <if test="name != null and name != ''">
        , name = #{name}
    </if>
    <if test="age != null">
        , age = #{age}
    </if>
    <if test="email != null and email != ''">
        , email = #{email}
    </if>
    <if test="status != null">
        , status = #{status}
    </if>
    WHERE id = #{id}
</update>

常用条件表达式

<!-- 字符串判断 -->
<if test="name != null and name != ''">  <!-- 非空字符串 -->
<if test="name != null and name.length() > 0">  <!-- 字符串长度大于0 -->
<if test="name != null and !name.equals('')">  <!-- 不等于空字符串 -->

<!-- 数值判断 -->
<if test="age != null">  <!-- 非空数值 -->
<if test="age != null and age > 0">  <!-- 大于0的数值 -->
<if test="age != null and age >= 18 and age <= 65">  <!-- 范围判断 -->

<!-- 集合判断 -->
<if test="list != null and list.size() > 0">  <!-- 非空集合 -->
<if test="array != null and array.length > 0">  <!-- 非空数组 -->

<!-- 布尔判断 -->
<if test="isActive != null and isActive">  <!-- 布尔值为true -->
<if test="isDeleted == null or !isDeleted">  <!-- 未删除 -->

choose/when/otherwise多条件选择

简要描述:choose标签类似于Java中的switch语句,用于多条件分支判断。when相当于case,otherwise相当于default。

基本语法

<choose>
    <when test="条件1">
        SQL片段1
    </when>
    <when test="条件2">
        SQL片段2
    </when>
    <otherwise>
        默认SQL片段
    </otherwise>
</choose>

实际应用案例

<!-- 根据不同条件排序 -->
<select id="selectUsersWithDynamicOrder" parameterType="map" resultType="User">
    SELECT id, name, age, email, create_time
    FROM user
    WHERE status = 1
    ORDER BY
    <choose>
        <when test="orderBy == 'name'">
            name ASC
        </when>
        <when test="orderBy == 'age'">
            age DESC
        </when>
        <when test="orderBy == 'createTime'">
            create_time DESC
        </when>
        <otherwise>
            id ASC
        </otherwise>
    </choose>
</select>

<!-- 根据用户类型查询不同字段 -->
<select id="selectUserByType" parameterType="map" resultType="map">
    SELECT 
    <choose>
        <when test="userType == 'admin'">
            id, name, email, role, permissions, last_login_time
        </when>
        <when test="userType == 'vip'">
            id, name, email, vip_level, vip_expire_time
        </when>
        <otherwise>
            id, name, email
        </otherwise>
    </choose>
    FROM user
    WHERE id = #{userId}
</select>

where条件处理

简要描述:where标签用于智能处理SQL语句中的WHERE子句,自动去除多余的AND、OR等。

核心功能

  • 自动添加WHERE关键字
  • 去除第一个AND或OR
  • 如果没有条件,不添加WHERE
<!-- 使用where标签优化条件查询 -->
<select id="selectUsersByCondition" parameterType="User" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null and name != ''">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
        <if test="email != null and email != ''">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

set更新处理

简要描述:set标签用于智能处理SQL语句中的SET子句,自动去除最后一个逗号。

核心功能

  • 自动添加SET关键字
  • 去除最后一个逗号
  • 如果没有更新字段,会报错
<!-- 使用set标签优化更新操作 -->
<update id="updateUserSelective" parameterType="User">
    UPDATE user
    <set>
        <if test="name != null and name != ''">
            name = #{name},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
        <if test="email != null and email != ''">
            email = #{email},
        </if>
        <if test="status != null">
            status = #{status},
        </if>
        update_time = NOW()
    </set>
    WHERE id = #{id}
</update>

foreach循环处理

简要描述:foreach标签用于遍历集合,常用于IN查询、批量插入、批量更新等场景。

基本语法

<foreach collection="集合名称" item="元素变量名" index="索引变量名" 
         open="开始字符" close="结束字符" separator="分隔符">
    #{item}
</foreach>

实际应用案例

<!-- IN查询 -->
<select id="selectUsersByIds" parameterType="list" resultType="User">
    SELECT id, name, age, email
    FROM user
    WHERE id IN
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

<!-- 批量插入 -->
<insert id="batchInsertUsers" parameterType="list">
    INSERT INTO user (name, age, email, create_time)
    VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age}, #{user.email}, NOW())
    </foreach>
</insert>

trim自定义处理

简要描述:trim标签是where和set标签的通用版本,提供更灵活的前缀、后缀处理能力。

基本语法

<trim prefix="前缀" suffix="后缀" prefixOverrides="要去除的前缀" suffixOverrides="要去除的后缀">
    SQL片段
</trim>

实际应用案例

<!-- 等价于where标签 -->
<select id="selectUsers" parameterType="User" resultType="User">
    SELECT * FROM user
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </trim>
</select>

<!-- 动态INSERT语句 -->
<insert id="insertUserSelective" parameterType="User">
    INSERT INTO user
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="name != null">
            name,
        </if>
        <if test="age != null">
            age,
        </if>
        create_time
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="name != null">
            #{name},
        </if>
        <if test="age != null">
            #{age},
        </if>
        NOW()
    </trim>
</insert>

bind变量绑定

简要描述:bind标签用于创建变量并绑定到上下文中,常用于字符串拼接、复杂表达式计算等场景。

基本语法

<bind name="变量名" value="表达式"/>

实际应用案例

<!-- 模糊查询优化 -->
<select id="selectUsersByName" parameterType="string" resultType="User">
    <bind name="pattern" value="'%' + name + '%'"/>
    SELECT * FROM user
    WHERE name LIKE #{pattern}
</select>

<!-- 复杂字符串处理 -->
<select id="selectUsersByKeyword" parameterType="map" resultType="User">
    <bind name="namePattern" value="'%' + keyword + '%'"/>
    <bind name="emailPattern" value="'%' + keyword + '%'"/>
    SELECT * FROM user
    WHERE name LIKE #{namePattern}
       OR email LIKE #{emailPattern}
</select>

动态SQL最佳实践

性能优化建议

  • 避免过度使用动态SQL,简单查询直接写静态SQL
  • 合理使用缓存,相同条件的动态SQL会生成相同的缓存key
  • 注意SQL注入风险,使用#{}而不是${}

代码规范建议

  • 保持SQL的可读性,适当使用换行和缩进
  • 复杂的动态逻辑考虑拆分为多个方法
  • 使用有意义的变量名和注释

调试技巧

  • 开启SQL日志,观察生成的实际SQL
  • 使用MyBatis的SQL构建器进行单元测试
  • 注意空值和边界条件的处理

网站公告

今日签到

点亮在社区的每一天
去签到